TA的每日心情 | 开心 2014-1-27 10:34 |
---|
签到天数: 67 天 连续签到: 1 天 [LV.6]常住居民II
|
前面主要学习如何配置、编译、烧录;接下来我们开始读代码,写代码。
如果在路由器的终端上输入ps命令,可以看到pid为1的进程是procd。procd就是今天的故事了。
没错,今天来学习一下OpenWrt开机之后都发生了 什么 。主要有三个主角:
- /etc/preinit,对应源码在 package/base-files/files/etc/preinit
- /sbin/procd,对应源码在 build_dir/target-mipsel_mips32_uClibc-0.9.33.2/procd-2015-10-29.1
- /sbin/init,对应源码在 build_dir/target-mipsel_mips32_uClibc-0.9.33.2/procd-2015-10-29.1/initd
当kernel初始化完成后,就会运行/etc/preinit脚步:- <font size="3">// build_dir/toolchain-mipsel_mips32_gcc-4.8-linaro_uClibc-0.9.33.2/linux/init/main.c
- if (!try_to_run_init_process("/etc/preinit") ||
- !try_to_run_init_process("/sbin/init") ||
- !try_to_run_init_process("/etc/init") ||
- !try_to_run_init_process("/bin/init") ||
- !try_to_run_init_process("/bin/sh"))
- return 0;</font>
复制代码 就这样男一号登场了。我们的男一号的第一句对白(代码)是:
- <font size="3">[ -z "$PREINIT" ] && exec /sbin/init</font>
复制代码 不用怀疑,这时候变量$PREINIT是没有设置的,所以就执行了 后面的后句,运行了/sbin/init。
接下来让我们来揭开女一号init的裙子,啊不,神秘的面纱。当我们查看init.c的main函数的时候,会看到这里先是设置了信号处理函数,然后调用了一个early()函数。这个early函数又分别调用了early_mounts()和 early_env():- <font size="3">early(void)
- {
- if (getpid() != 1)
- return;
- early_mounts();
- early_env();
- LOG("Console is alive\n");
- }</font>
复制代码 early_mounts()挂载了一些系统用的虚拟分区,例如/proc, /sysfs, /dev/shm等等,还顺便把标准输入输出错误等设置成/dev/console。因为init是所有进程的爹,当其他进程被创建时就会把标准输入、输出、错误这三个基本的文件描述符继承过去。
而early_env简单的设置了PATH变量。
接着往下看init.c的main函数。接下来运行了/sbin/kmodloader /etc/modules-boot.d/,加载目录下的内核驱动。
最后运行preinit()函数。preinit()函数做了两件微不足道的小事(暴力膜不可取啊):
- 运行我们的男二号procd:/sbin/procd -h /etc/hotplug-preinit.json
- 运行 /bin/sh /etc/preinit 脚步,并将进程pid注册到uloo,在preinit脚本退出时调用spawn_procd()函数
啊,又回到我们的男一号了。不过,往回退一点, /sbin/init在运行/etc/preinit之前,设置了一个重要的环境变量PREINIT :- <font size="3">setenv("PREINIT", "1", 1);</font>
复制代码 如果你往前看我们男一号登场的场景,就会发现这个PREINIT变量就是我们男一号/etc/preinit的第一句对白里提到的变量。上次我们找不到他,现在有了,是1。所以脚步会跳过第一句,往下走了。/etc/preinit 脚步后面的部分我们先不讲。我们接着看男二号procd。打开procd.c的main函数,我们只关心一句话:
- <font size="3">procd_state_next();</font>
复制代码 这就开始了procd的状态机了。procd的状态机代码在state.c中,有以下这几个状态:
- <font size="3">enum {
- STATE_NONE = 0,
- STATE_EARLY,
- STATE_UBUS,
- STATE_INIT,
- STATE_RUNNING,
- STATE_SHUTDOWN,
- STATE_HALT,
- __STATE_MAX,
- };</font>
复制代码 state变量用来存储当前的状态,被初始化为STATE_NONE:
- <font size="3">static int state = STATE_NONE;</font>
复制代码 我们把重点放在STATE_INT这个状态,在state_enter函数可以看到状态的实现:
- <font size="3">case STATE_INIT:
- LOG("- init -\n");
- procd_inittab();
- procd_inittab_run("respawn");
- procd_inittab_run("askconsole");
- procd_inittab_run("askfirst");
- procd_inittab_run("sysinit");</font>
复制代码 procd_inittab()函数负责解析/etc/inittab配置,我们先来看一下这个文件的内容:
- <font size="3">::sysinit:/etc/init.d/rcS S boot
- ::shutdown:/etc/init.d/rcS K shutdown
- ::askconsole:/bin/ash --login</font>
复制代码 打开procd_inittab()的源码,可以看到这个函数用正则表达式解析inittab每一行,这里我们只关注第一行sysinit的解析结果:
得到的action为sysinit,argv为{"/etc/init.d/rcS", "S", "boot"}
回到STATE_INIT状态机,最后运行了procd_inittab_run("sysinit");
再看procd_inittab_run源码:- <font size="3">static struct init_handler handlers[] = {
- {
- .name = "sysinit",
- .cb = runrc,
- }, {
- .name = "shutdown",
- .cb = runrc,
- }, {
- .name = "askfirst",
- .cb = askfirst,
- .multi = 1,
- }, {
- .name = "askconsole",
- .cb = askconsole,
- .multi = 1,
- }, {
- .name = "respawn",
- .cb = rcrespawn,
- .multi = 1,
- }
- };
- // 中间略去
- void procd_inittab_run(const char *handler)
- {
- struct init_action *a;
- list_for_each_entry(a, &actions, list)
- if (!strcmp(a->handler->name, handler)) {
- if (a->handler->multi) {
- a->handler->cb(a);
- continue;
- }
- a->handler->cb(a);
- break;
- }
- }</font>
复制代码 所以,这里就相当于运行了runrc,runrc再调用_rc,_rc()函数调用/etc/rc.d/下面所有文件名以S开头(就是刚才解析/etc/inittab文件得到的argv[1])的脚步,调用参数为"boot"(也就是argv[2])。注意/etc/rc.d目录下的脚步运行顺序是按照所谓的glob顺序,所以文件名S后面的两位数字就是用来规定运行先后的顺序的。
而这个目录下的脚步其实是OpenWrt的UCI机制产生的符号链接,当/etc/init.d/下的脚步被用enable参数调用时,就会在/etc/rc.d目录下创建一个对应的符号链接,从而在开机时自动运行。UCI我们后面再来学习。
还记得刚才我们说/sbin/init运行/etc/preinit的时候将preinit脚本进程pid注册到uloop,在preinit脚本退出时调用spawn_procd()函数。需要注意的 是,此时init进程的pid是1。而运行spawn_procd()就会再次运行/sbin/procd,不过这次没有用fork(),没有创建新的进程,/sbin/procd接替了init,成了pid 1的进程。
好了,差不多就到这里,谢谢大家。
|
|