查看: 1169|回复: 1

[讨论] 跟我学openwrt4-开机流程

[复制链接]

39

主题

3

好友

4031

积分

QQ游客

  • TA的每日心情
    开心
    2014-1-27 10:34
  • 签到天数: 67 天

    连续签到: 1 天

    [LV.6]常住居民II

    发表于 2017-7-13 09:12:04 |显示全部楼层
    前面主要学习如何配置、编译、烧录;接下来我们开始读代码,写代码。

    如果在路由器的终端上输入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脚步:
    1. <font size="3">// build_dir/toolchain-mipsel_mips32_gcc-4.8-linaro_uClibc-0.9.33.2/linux/init/main.c
    2.         if (!try_to_run_init_process("/etc/preinit") ||
    3.             !try_to_run_init_process("/sbin/init") ||
    4.             !try_to_run_init_process("/etc/init") ||
    5.             !try_to_run_init_process("/bin/init") ||
    6.             !try_to_run_init_process("/bin/sh"))
    7.                 return 0;</font>
    复制代码
    就这样男一号登场了。我们的男一号的第一句对白(代码)是:
    1. <font size="3">[ -z "$PREINIT" ] && exec /sbin/init</font>
    复制代码
    不用怀疑,这时候变量$PREINIT是没有设置的,所以就执行了 后面的后句,运行了/sbin/init。
    接下来让我们来揭开女一号init的裙子,啊不,神秘的面纱。当我们查看init.c的main函数的时候,会看到这里先是设置了信号处理函数,然后调用了一个early()函数。这个early函数又分别调用了early_mounts()和 early_env():
    1. <font size="3">early(void)
    2. {
    3.         if (getpid() != 1)
    4.                 return;

    5.         early_mounts();
    6.         early_env();

    7.         LOG("Console is alive\n");
    8. }</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 :
    1. <font size="3">setenv("PREINIT", "1", 1);</font>
    复制代码
    如果你往前看我们男一号登场的场景,就会发现这个PREINIT变量就是我们男一号/etc/preinit的第一句对白里提到的变量。上次我们找不到他,现在有了,是1。所以脚步会跳过第一句,往下走了。/etc/preinit 脚步后面的部分我们先不讲。我们接着看男二号procd。打开procd.c的main函数,我们只关心一句话:
    1. <font size="3">procd_state_next();</font>
    复制代码
    这就开始了procd的状态机了。procd的状态机代码在state.c中,有以下这几个状态:
    1. <font size="3">enum {
    2.         STATE_NONE = 0,
    3.         STATE_EARLY,
    4.         STATE_UBUS,
    5.         STATE_INIT,
    6.         STATE_RUNNING,
    7.         STATE_SHUTDOWN,
    8.         STATE_HALT,
    9.         __STATE_MAX,
    10. };</font>
    复制代码
    state变量用来存储当前的状态,被初始化为STATE_NONE:
    1. <font size="3">static int state = STATE_NONE;</font>
    复制代码
    我们把重点放在STATE_INT这个状态,在state_enter函数可以看到状态的实现:
    1. <font size="3">case STATE_INIT:
    2.                 LOG("- init -\n");
    3.                 procd_inittab();
    4.                 procd_inittab_run("respawn");
    5.                 procd_inittab_run("askconsole");
    6.                 procd_inittab_run("askfirst");
    7.                 procd_inittab_run("sysinit");</font>
    复制代码
       procd_inittab()函数负责解析/etc/inittab配置,我们先来看一下这个文件的内容:
    1. <font size="3">::sysinit:/etc/init.d/rcS S boot
    2. ::shutdown:/etc/init.d/rcS K shutdown
    3. ::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源码:
    1. <font size="3">static struct init_handler handlers[] = {
    2.         {
    3.                 .name = "sysinit",
    4.                 .cb = runrc,
    5.         }, {
    6.                 .name = "shutdown",
    7.                 .cb = runrc,
    8.         }, {
    9.                 .name = "askfirst",
    10.                 .cb = askfirst,
    11.                 .multi = 1,
    12.         }, {
    13.                 .name = "askconsole",
    14.                 .cb = askconsole,
    15.                 .multi = 1,
    16.         }, {
    17.                 .name = "respawn",
    18.                 .cb = rcrespawn,
    19.                 .multi = 1,
    20.         }
    21. };
    22. // 中间略去
    23. void procd_inittab_run(const char *handler)
    24. {
    25.         struct init_action *a;

    26.         list_for_each_entry(a, &actions, list)
    27.                 if (!strcmp(a->handler->name, handler)) {
    28.                         if (a->handler->multi) {
    29.                                 a->handler->cb(a);
    30.                                 continue;
    31.                         }
    32.                         a->handler->cb(a);
    33.                         break;
    34.                 }
    35. }</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的进程。
        好了,差不多就到这里,谢谢大家。







    回复

    使用道具 举报

    113

    主题

    63

    好友

    2万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    该用户从未签到

    分区版主职务勋章

    发表于 2017-7-13 16:27:03 |显示全部楼层
    楼主教程不错啊
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    关闭

    站长推荐上一条 /3 下一条

    手机版|爱板网 |网站地图  

    GMT+8, 2017-9-25 19:33 , Processed in 0.184703 second(s), 10 queries , Memcache On.

    苏公网安备 32059002001056号

    Powered by Discuz!

    回顶部