/dev/initctl怎么玩

晨曦之光 发布于 2012/04/10 15:01
阅读 1K+
收藏 0

【深度】AI、5G时代下,算力网络与内生安全最全解析!>>>

/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, 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, 0, 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;
        }
...
    }
...
}


原文链接:http://blog.csdn.net/dog250/article/details/5303623
加载中
返回顶部
顶部