/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) (void)mkfifo(INIT_FIFO, 0600); //如果没有这个initctl管道,那么就建立它。
if (pipe_fd >= 0) { //如果已经打开了,那么就看看现在是否它的属性发生了变化
fstat(pipe_fd, &st);
if (stat(INIT_FIFO, &st2) st.st_dev != st2.st_dev ||
st.st_ino != st2.st_ino) {
close(pipe_fd);
pipe_fd = -1;
}
}
if (pipe_fd 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 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 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 <sys><br>#include <sys><br>#include <fcntl.h><br>#define INIT_MAGIC 0x03091969 <br>#define INIT_CMD_RUNLVL 1 <br>struct init_request_bsd { <br> char gen_id[8]; <br> char tty_id[16]; <br> char host[64]; <br> char term_type[16]; <br> int signal; <br> int pid; <br> char exec_name[128]; <br> char reserved[128]; <br>}; <br>struct init_request { //这个结构代表一个init请求,将一个请求写入/dev/initctl就等于发出了请求 <br> int magic; <br> int cmd; <br> int runlevel; <br> int sleeptime; <br> union { <br> struct init_request_bsd bsd; <br> char data[368]; <br> } i; <br>}; <br>int main() <br>{ <br> struct init_request is; <br> memset(is, 0, sizeof(is)); <br> is.magic = INIT_MAGIC; //设置魔术字,在init进程需要检查 <br> is.cmd = INIT_CMD_RUNLVL; //告诉init进程我们需要改变运行级,当然还可以有别的要求,比如ups电源检测到掉电事件等等 <br> is.runlevel = 52; //将52赋给runlevel,代表ascii的4,注意runlevel是int型,到init进程就要被转化为char型。 <br> is.sleeptime = 0; <br> int fd = open("/dev/initctl",O_WRONLY); //打开管道 <br> write(fd,is,sizeof(is)); //将上述的init_request结构体is写入管道 <br>} <br>编译上述代码,执行之,运行级别就改到4了。写入请求以后,在init进程检测/dev/initctl的时候检测到我们写入的请求,就一直到 fifo_new_level了,从而改变了系统的运行级。这个原理我们懂了,那么有没有别的请求可写呢?当然有了,以下就是定义: <br>#define INIT_CMD_START 0 <br>#define INIT_CMD_RUNLVL 1 <br>#define INIT_CMD_POWERFAIL 2 <br>#define INIT_CMD_POWERFAILNOW 3 <br>#define INIT_CMD_POWEROK 4 <br>#define INIT_CMD_BSD 5 <br>#define INIT_CMD_SETENV 6 <br>#define INIT_CMD_UNSETENV 7 <br>看到上面的请求类别中以电源相关的居多,我们就举个例子说吧,比如INIT_CMD_POWERFAILNOW,在check_init_fifo会执行到它的case: <br>switch(request.cmd) { <br>... <br> case INIT_CMD_POWERFAILNOW: <br> sltime = request.sleeptime; <br> do_power_fail('L'); <br> quit = 1; <br> break; <br>... <br>然后就到了do_power_fail里面: <br>void do_power_fail(int pwrstat) <br>{ <br> CHILD *ch; //注释:ch-&gt;flags &amp;= ~XECUTED的意思就是清除已经执行过的标记,言外之意就是马上要执行,在哪里执行呢?看我前面的文章吧(当然在主循环的 start_if_needed里面执行哦) <br> for (ch = family; ch; ch = ch-&gt;next) { //寻找出电源问题的CHILD <br> if (pwrstat == 'O') { //电源问题解决了 <br> if (ch-&gt;action == POWEROKWAIT) <br> ch-&gt;flags &amp;= ~XECUTED; <br> } else if (pwrstat == 'L') { //电池电量低,需要提醒,这是我们的情况 <br> if (ch-&gt;action == POWERFAILNOW) <br> ch-&gt;flags &amp;= ~XECUTED; <br> } else { //立马关机 <br> if (ch-&gt;action == POWERFAIL || ch-&gt;action == POWERWAIT) <br> ch-&gt;flags &amp;= ~XECUTED; <br> } <br> } <br>} <br>这一切到底是怎么一回事呢?原来在/etc/inittab里有意行 <br>pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down" <br>注意它的action是powerfail,映射到init的数字就是POWERFAIL,和电源相关的在init.c定义了一个宏: <br>#define ISPOWER(i) ((i) == POWERWAIT || (i) == POWERFAIL || (i) == POWEROKWAIT || (i) == POWERFAILNOW || (i) == CTRLALTDEL) <br>然后在read_inittab中解析将要在初始化时运行的进程的时候将ISPOWER为真的action对应的CHILD设置为已经执行过,这样就避免了初始化的时候执行电源相关的进程,具体就是: <br>if (ISPOWER(ch-&gt;action)) { <br> ch-&gt;flags |= XECUTED; <br>... <br>这样在start_if_needed-&gt;startup中执行if (ch-&gt;flags &amp; XECUTED) break;的时候就不会执行相关的进程了,但是电源万一真的出问题了,那么就是要放开那些进程的时候了,怎么放呢?就是简单的清除掉XECUTED标志 即可。linux设计得就是好,一切能分离的尽量分离,电源出问题了一旦用户空间的进程知道了,那么它有很多种方法报告,可以发送电源信号,然后init 进程捕获该信号,可以操作/dev/initctl,然后init进程处理,具体咋处理那些进程不用管,由/etc/inittab的策略管好了 <br>下面还有个问题,我们通过各种方式改变了系统的运行级别,那么怎么保证系统不会执行比我们的运行级别高的级别的进程呢?还是看代码: <br>void start_if_needed(void) <br>{ <br> for(ch = family; ch; ch = ch-&gt;next) { <br> if (ch-&gt;flags &amp; WAITING) break; <br> if (ch-&gt;flags &amp; RUNNING) continue; <br> delete = 1; <br> if (strchr(ch-&gt;rlevel, runlevel) || //保证了ch-&gt;rlevel中有当前runlevel,满足条件的才执行下面的startup函数 <br> ((ch-&gt;flags &amp; DEMAND) &amp;&amp; !strchr("#*Ss", runlevel))) { <br> startup(ch); <br> delete = 0; <br> } <br>... <br> } <br>... <br>}</fcntl.h></sys></sys>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值