在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
3.2 配置文件的解析
3.2.2 链表结构(接上篇)
上篇分析到读取配置文件,解析字符串的过程。接下来我们就一起看看这些信息是如何被装载到一个结构体中并作为一个链表的节点挂载到链表中去的。
首先,我们看一下链表的节点定义。节点的定义并不复杂,与配置文件相比,只多了两个成员:pid和next。只有后继指针说明这是一个单向链表,因此操作会相对简单一些。
/* busybox源码树/init/init.c */
/* Set up a linked list of init_actions, to be read from inittab */
struct init_action {
struct init_action *next; /*指向下一个节点(后继)的指针*/
int action; /*action对应的编号*/
pid_t pid; /*process id*/
char command[INIT_BUFFS_SIZE]; /*process name*/
char terminal[CONSOLE_NAME_SIZE]; /*terminal name*/
};
接下来我们看一下链表新节点的插入过程。
/* busybox源码树/init/init.c */
static void new_init_action(int action, const char *command, const char *cons)
{
struct init_action *new_action, *a, *last;
/* ASKFIRST事件只能在有终端的情形下生效 */
if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
return;
/* Append to the end of the list
从链表头init_action_list找到链表尾last。
这里的技巧是使用了两个指针a和last,last是
坐镇中军的指挥官,而a是排头兵,是在前面扫雷的,
遇到链表尾部的雷(next指针指向NULL),
排头兵自己死翘翘了,但是保全了领导last*/
for (a = last = init_action_list; a; a = a->next) {
/* don't enter action if it's already in the list,
* but do overwrite existing actions */
if ((strcmp(a->command, command) == 0)
&& (strcmp(a->terminal, cons) == 0)
) {
a->action = action;
return;
}
last = a;
}/*至此,a的使命已完成,可以去领盒饭了~*/
/* 创建新节点,并且将新节点挂在链表尾部 */
new_action = xzalloc(sizeof(struct init_action));
if (last) {
last->next = new_action;
} else { /* 第一个节点走这里(从头节点的处理可以看出头节点和普通节点并没有差异) */
init_action_list = new_action;
}
/* 给新节点填充内容(填充内容和链表插入先后顺序无所谓)*/
strcpy(new_action->command, command);
new_action->action = action;
strcpy(new_action->terminal, cons);
messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
new_action->command, new_action->action, new_action->terminal);
}
至此,配置文件中的多行字符串全部转换成链表结构了。这里插一句:其实Python中Json模块将Json文件解析成字典的实现思路和这个是一脉相承的。只不过高级语言将这个过程封装简化了而已。
3.3 分阶段运行
解析完配置文件之后, init_main() 会开始执行一些特定的action,比如SYSINIT、WAIT等,详见下述源码。
/* busybox源码树/init/init.c */
int init_main(int argc, char **argv)
{
struct init_action *a;
pid_t wpid;
...
/* Now run everything that needs to be run */
/* First run the sysinit command
运行action为SYSINIT的应用程序 */
run_actions(SYSINIT);
/* Next run anything that wants to block
运行action为WAIT的应用程序 */
run_actions(WAIT);
/* Next run anything to be run only once
运行action为ONCE的应用程序 */
run_actions(ONCE);
...
/* Now run the looping stuff for the rest of forever */
while (1) {
/* run the respawn stuff
运行action为RESPAWN的应用程序 */
run_actions(RESPAWN);
/* run the askfirst stuff
运行action为ASKFIRST的应用程序 */
run_actions(ASKFIRST);
/* Don't consume all CPU time -- sleep a bit */
sleep(1);
/* Wait for a child process to exit */
wpid = wait(NULL); /*会被阻塞,直到有一个子进程退出*/
while (wpid > 0) {
/* Find out who died and clean up their corpse */
for (a = init_action_list; a; a = a->next) {
if (a->pid == wpid) {
/* Set the pid to 0 so that the process gets
* restarted by run_actions() */
/* 将其置为0,标识该程序已经退出,下次循环执行
run_actions(RESPAWN/ASKFIRST);时会复活该进程 */
a->pid = 0;
message(L_LOG, "process '%s' (pid %d) exited. "
"Scheduling it for restart.",
a->command, wpid);
}
}
/* see if anyone else is waiting to be reaped
看看在这个时间段内是否还有其他子进程退出。
使用WNOHANG(wait no hang)参数不阻塞 */
wpid = waitpid(-1, NULL, WNOHANG);
}
}
}
接下来对 run_actions() 函数做一个分析,并对这些actions做一个简单的分类:
类别 | 包含的actions | 实现思路 |
---|---|---|
主动运行一次,不阻塞 | ONCE | init进程创建一个子进程,但是init进程并不阻塞 |
主动运行一次,阻塞 | SYSINIT、WAIT | init进程创建一个子进程,并且init进程阻塞等待子进程执行完毕 |
被动触发一次,阻塞 | CTRLALTDEL、SHUTDOWN、RESTART | 使用信号触发,后处理是init进程创建一个子进程,并且等待子进程执行完毕 |
主动执行,退出后复活 | RESPAWN、ASKFIRST | init进程创建子进程之后,监控有子进程退出后再次启动该应用程序 |
/* busybox源码树/init/init.c */
/* Run all commands of a particular type */
static void run_actions(int action)
{
struct init_action *a, *tmp;
/* 遍历整个链表 */
for (a = init_action_list; a; a = tmp) {
tmp = a->next;
if (a->action == action) {
/* a->terminal of "" means "init's console" */
if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
delete_init_action(a);
} else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
waitfor(a, 0); /*waitfor函数通过调用run和waitpid实现*/
delete_init_action(a); /*运行完之后从链表中删除*/
} else if (a->action & ONCE) {
run(a);
delete_init_action(a);
} else if (a->action & (RESPAWN | ASKFIRST)) {
/* Only run stuff with pid==0. If they have
* a pid, that means it is still running
这种需要复活的进程是不从链表种删除的 */
if (a->pid == 0) {
a->pid = run(a);
}
}
}
}
}
3.4 再谈配置/注册思想
我们对init进程的分析就到此为止了,接下来我们回顾一下init进程的设计。其中最亮眼的还是设计了配置文件,使得用户自己可以灵活定制需要启动的应用程序。设计的背后其实是配置/注册思想在做支撑。
配置/注册思想 其实是对 数据驱动思想 的一种应用:提前将数据注册好,等到运行时直接读取/解析这些注册好的数据即可。运行的程序不用变,变的是注册的数据。从这个角度来讲数据驱动思想也仅仅是对 变与不变隔离思想 的一种应用而已。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~