前端之家收集整理的这篇文章主要介绍了
/dev/initctl怎么玩,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
/dev/initctl是一个管道文件,很多人知道它,但是知道怎么用。要想知道怎么用还是得看init程序的源代码,在init.c中就用到了 /dev/initctl管道文件。可以通过/dev/initctl改变系统的运行级别,但是怎么改变呢?比如说当前运行级别是2,我想将运行级别提到 3,那么想当然的做法就是:
[root@localhost zhaoy]# touch level
[root@localhost zhaoy]# echo '3'>level
[root@localhost zhaoy]# cat level >/dev/initctl
但是得到的结果却是:
INIT: got bogus initrequest
于是,我在init.c中搜以上错误字符串,在check_init_fifo中找到了它,以下看一下check_init_fifo函数的相关部分:
void check_init_fifo(void)
{
struct init_request request;
struct timeval tv;
struct stat st,st2;
fd_set fds;
int n;
int quit = 0;
if (stat(INIT_FIFO,&st2) < 0 && errno == ENOENT)
(void)mkfifo(INIT_FIFO,0600); //如果没有这个initctl管道,那么就建立它。
if (pipe_fd >= 0) { //如果已经打开了,那么就看看现在是否它的属性发生了变化
fstat(pipe_fd,&st);
if (stat(INIT_FIFO,&st2) < 0 ||
st.st_dev != st2.st_dev ||
st.st_ino != st2.st_ino) {
close(pipe_fd);
pipe_fd = -1;
}
}
if (pipe_fd < 0) { //原来打开却发生了变化或者根本没有打开都需要打开该管道
if ((pipe_fd = open(INIT_FIFO,O_RDWR|O_NONBLOCK)) >= 0) {
fstat(pipe_fd,&st);
if (!S_ISFIFO(st.st_mode)) {
initlog(L_VB,"%s is not a fifo",INIT_FIFO);
close(pipe_fd);
pipe_fd = -1;
}
}
if (pipe_fd >= 0) {
...//打开成功
}
}
if (pipe_fd >= 0) while(!quit) {//等待数据,如果没有数据就返回
FD_ZERO(&fds);
FD_SET(pipe_fd,&fds);
tv.tv_sec = 5;
tv.tv_usec = 0;
n = select(pipe_fd + 1,&fds,NULL,&tv);
if (n <= 0) {
if (n == 0 || errno == EINTR) return;
continue;
}
n = read(pipe_fd,&request,sizeof(request)); //读出一个??request是个什么东西,这个一会再谈。
if (n == 0) {
close(pipe_fd);
pipe_fd = -1;
return;
}
if (n <= 0) {
if (errno == EINTR) return;
continue;
}
console_init();
if (request.magic != INIT_MAGIC || n != sizeof(request)) { //检查魔术字
initlog(L_VB,"got bogus initrequest"); //终于找到那个出错信息,这就是切入点
continue;
}
switch(request.cmd) { //所有验证都通过,是一个合法请求
case INIT_CMD_RUNLVL: //要求改变运行级
sltime = request.sleeptime;
fifo_new_level(request.runlevel); //开始着手改变运行级
quit = 1;
break;
}
}
}
void fifo_new_level(int level)
{
int oldlevel;
if (level == runlevel)
return;
oldlevel = runlevel;
runlevel = read_level(level); //read_level检查level参数返回新的运行级别并赋给全局变量runlevel
if (runlevel == 'U') {
runlevel = oldlevel;
re_exec();
} else {
if (oldlevel != 'S' && runlevel == 'S') console_stty();
if (runlevel == '6' || runlevel == '0' || runlevel == '1')
console_stty();
read_inittab(); //重新加载/etc/inittab文件
fail_cancel();
setproctitle("init [%c]",runlevel);
}
}
check_init_fifo函数就是在那个init_main的主循环里被调用的,由此可见init进程时刻检测/dev/initctl管道,一旦 有请求马上处理,这真是太好了,我们也可以写一把/dev/initctl管道,但是怎么写呢?肯定得有一定的格式了,这个格式就是上面的??,实际上是一个结构体,我下面给出一个写/dev/initctl管道的完整代码,代码内部说一下那个initctl需要的数据结构:
#include
#include
#include
#define INIT_MAGIC 0x03091969 #define INIT_CMD_RUNLVL 1 struct init_request_bsd { char gen_id[8]; char tty_id[16]; char host[64]; char term_type[16]; int signal; int pid; char exec_name[128]; char reserved[128]; }; struct init_request { //这个结构代表一个init请求,将一个请求写入/dev/initctl就等于发出了请求 int magic; int cmd; int runlevel; int sleeptime; union { struct init_request_bsd bsd; char data[368]; } i; }; int main() { struct init_request is; memset(is,sizeof(is)); is.magic = INIT_MAGIC; //设置魔术字,在init进程需要检查 is.cmd = INIT_CMD_RUNLVL; //告诉init进程我们需要改变运行级,当然还可以有别的要求,比如ups电源检测到掉电事件等等 is.runlevel = 52; //将52赋给runlevel,代表ascii的4,注意runlevel是int型,到init进程就要被转化为char型。 is.sleeptime = 0; int fd = open("/dev/initctl",O_WRONLY); //打开管道 write(fd,is,sizeof(is)); //将上述的init_request结构体is写入管道 } 编译上述代码,执行之,运行级别就改到4了。写入请求以后,在init进程检测/dev/initctl的时候检测到我们写入的请求,就一直到 fifo_new_level了,从而改变了系统的运行级。这个原理我们懂了,那么有没有别的请求可写呢?当然有了,以下就是定义: #define INIT_CMD_START 0 #define INIT_CMD_RUNLVL 1 #define INIT_CMD_POWERFAIL 2 #define INIT_CMD_POWERFAILNOW 3 #define INIT_CMD_POWEROK 4 #define INIT_CMD_BSD 5 #define INIT_CMD_SETENV 6 #define INIT_CMD_UNSETENV 7 看到上面的请求类别中以电源相关的居多,我们就举个例子说吧,比如INIT_CMD_POWERFAILNOW,在check_init_fifo会执行到它的case: switch(request.cmd) { ... case INIT_CMD_POWERFAILNOW: sltime = request.sleeptime; do_power_fail('L'); quit = 1; break; ... 然后就到了do_power_fail里面: void do_power_fail(int pwrstat) { CHILD *ch; //注释:ch->flags &= ~XECUTED的意思就是清除已经执行过的标记,言外之意就是马上要执行,在哪里执行呢?看我前面的文章吧(当然在主循环的 start_if_needed里面执行哦) for (ch = family; ch; ch = ch->next) { //寻找出电源问题的CHILD if (pwrstat == 'O') { //电源问题解决了 if (ch->action == POWEROKWAIT) ch->flags &= ~XECUTED; } else if (pwrstat == 'L') { //电池电量低,需要提醒,这是我们的情况 if (ch->action == POWERFAILNOW) ch->flags &= ~XECUTED; } else { //立马关机 if (ch->action == POWERFAIL || ch->action == POWERWAIT) ch->flags &= ~XECUTED; } } } 这一切到底是怎么一回事呢?原来在/etc/inittab里有意行 pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down" 注意它的action是powerfail,映射到init的数字就是POWERFAIL,和电源相关的在init.c定义了一个宏: #define ISPOWER(i) ((i) == POWERWAIT || (i) == POWERFAIL || (i) == POWEROKWAIT || (i) == POWERFAILNOW || (i) == CTRLALTDEL) 然后在read_inittab中解析将要在初始化时运行的进程的时候将ISPOWER为真的action对应的CHILD设置为已经执行过,这样就避免了初始化的时候执行电源相关的进程,具体就是: if (ISPOWER(ch->action)) { ch->flags |= XECUTED; ... 这样在start_if_needed->startup中执行if (ch->flags & XECUTED) break;的时候就不会执行相关的进程了,但是电源万一真的出问题了,那么就是要放开那些进程的时候了,怎么放呢?就是简单的清除掉XECUTED标志 即可。linux设计得就是好,一切能分离的尽量分离,电源出问题了一旦用户空间的进程知道了,那么它有很多种方法报告,可以发送电源信号,然后init 进程捕获该信号,可以操作/dev/initctl,然后init进程处理,具体咋处理那些进程不用管,由/etc/inittab的策略管好了 下面还有个问题,我们通过各种方式改变了系统的运行级别,那么怎么保证系统不会执行比我们的运行级别高的级别的进程呢?还是看代码: void start_if_needed(void) { for(ch = family; ch; ch = ch->next) { if (ch->flags & WAITING) break; if (ch->flags & RUNNING) continue; delete = 1; if (strchr(ch->rlevel,runlevel) || //保证了ch->rlevel中有当前runlevel,满足条件的才执行下面的startup函数 ((ch->flags & DEMAND) && !strchr("#*Ss",runlevel))) { startup(ch); delete = 0; } ... } ... }
原文链接:https://www.f2er.com/vb/262236.html