制作根文件系统之Busybox init进程的启动过程分析 制作根文件系统之内核如何启动init进程

先来介绍一下什么是Busybox:它是将众多的UNIX命令集合进一个很小的可执行程序中。

制作根文件系统之内核如何启动init进程中遗留了一个问题是/linuxrc是内核启动的第一个应用程序,那么它是什么?我们看到移植好的根文件系统环境,输入ls -al /linuxrc。可以看到:

lrwxrwxrwx    1 1000     1000           11 Jul 26  2018 linuxrc -> bin/busybox

/linuxrc其实是指向Busybox的,它其实是Busybox下的一个程序,所以我们需要分析Busybox的源码来研究/linuxrc这个程序做的事情,当在移植好的根文件系统环境下输入/linuxrc可以看到:

# busybox linuxrc
init started: BusyBox v1.7.0 (2018-07-26 12:50:40 CST)
starting pid 796, tty '': '/etc/init.d/rcS'

Please press Enter to activate this console.

这就是执行linuxrc程序之后出现的界面,现在还不确定/linuxrc是Busybox下哪个文件,我们搜索“init started”,在busybox-1.7.0\init\Init.c中找到了它

935    message(MAYBE_CONSOLE | L_LOG, "init started: %s", bb_banner);

我们看到调用它的是init_main函数,接下去就是分析这个函数,init_main函数的功能大概可以概括为

1、设置处理信号函数

2、读取配置文件、解析配置文件

3、根据配置文件执行用户的程序或脚本

下面分别介绍

1、设置处理信号函数,restart, ctrlaltdel, and shutdown是一些按键事件,这些处理函数在接收到按下相关按键的信号后,进行处理

905        signal(SIGHUP, exec_signal);
906        signal(SIGQUIT, exec_signal);
907        signal(SIGUSR1, shutdown_signal);
908        signal(SIGUSR2, shutdown_signal);
909        signal(SIGINT, ctrlaltdel_signal);
910        signal(SIGTERM, shutdown_signal);
911        signal(SIGCONT, cont_handler);
912        signal(SIGSTOP, stop_handler);
913        signal(SIGTSTP, stop_handler);

2、读取和解析配置文件,先看一下配置文件的格式,文档位于example/inittab中

# inittab的格式为:
# <id>:<runlevels>:<action>:<process>
# id => /dev/id,用作终端:stdin、stdout、stderr:printf、scanf、err
# runlevels:忽略
# action     :执行时机Valid actions include: sysinit, respawn, askfirst, wait, once,
                                              restart, ctrlaltdel, and shutdown.
# process   :应用程序或脚本

接着回到init_main函数,看到

967    parse_inittab();//解析inittab参数

找到parse_inittab函数,位于busybox-1.7.0\init\Init.c中

755    static void parse_inittab(void)
756    {
            ...
            ...
764            file = fopen(INITTAB, "r");//以只读方式打开/etc/inittab    
            ...
788            while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {
                ...
836                        for (a = actions; a->name != 0; a++) {
837                            if (strcmp(a->name, action) == 0) {
838                                if (*id != '\0') {
839                                    if (strncmp(id, "/dev/", 5) == 0)//去掉开头的/dev/
840                                        id += 5;
841                                    strcpy(tmpConsole, "/dev/");
842                                    safe_strncpy(tmpConsole + 5, id,
843                                        sizeof(tmpConsole) - 5);
844                                    id = tmpConsole;
845                                }
846                                new_init_action(a->action, command, id);//创建一个init_action结构,以解析出来的参数填充它,然后将这个结构放入init_action_list链表
847                                break;
848                            }
849                        }
                ...
                }
857    }

这个函数的功能解释为打开/etc/inittab文件然后处理它,这个文件是以配置文件的格式存放的。看到函数最后会创建一个init_action结构,这个结构就是将配置文件里的内容一个个取出来,然后放在里面。最后链接到init_action_list链表中

72    struct init_action {
73        struct init_action *next;
74        int action;
75        pid_t pid;//进程号  by andy
76        char command[INIT_BUFFS_SIZE];//要执行的脚本命令
77        char terminal[CONSOLE_NAME_SIZE];//终端
78    };

这里再举个例子,下面是某个配置文件。以第一个为例子。action为askfirst执行时机;command为执行的命令或脚本为-/bin/sh;terminal为使用的中断为console

console::askfirst:-/bin/sh
::sysinit:/etc/init.d/rcS

3、根据配置文件执行用户的程序或脚本

接着看到,每个执行时机执行的内容,以下均为简写。

busybox-> init_main
        parse_inittab 
            file = fopen(INITTAB, "r");//打开配置文件/etc/inittab
            new_init_action    //a、创建一个init_action结构,填充
                    //b、将这个结构放入init_action_list链表
        run_actions(SYSINIT);
            waitfor(a, 0);             //执行应用程序,等待它执行完毕
                run(a)       //创建process子进程
                waitpid(runpid, &status, 0);//等待它结束
            delete_init_action(a);//在init_action_list链表里删除
        run_actions(WAIT);
            waitfor(a, 0);             //执行应用程序,等待它执行完毕
                run(a)       //创建process子进程
                waitpid(runpid, &status, 0);//等待它结束
            delete_init_action(a);//在init_action_list链表里删除
        run_actions(ONCE);
            run(a);                       //创建process子进程
            delete_init_action(a);//在init_action_list链表里删除
        while(1)   {
            run_actions(RESPAWN);
                if (a->pid == 0) {
                    a->pid = run(a);
                }
            run_actions(ASKFIRST);
                if (a->pid == 0) {
                    a->pid = run(a);
                        打印:Please press Enter to activate this console.
                        等待回车
                        创建子进程
                }
            wpid = wait(NULL);//等待子进程退出
            while (wpid > 0) {
            a->pid = 0;//退出后就设置pid=0
               }
        其余三个restart, ctrlaltdel, and shutdown执行时机在按下按键的时候发生事件
        }

大致总结一下每个执行时机:

SYSINIT:第一个执行,创建子进程,等待进程结束,将它从init_action_list链表里删除

WAIT:第二个执行,创建子进程,等待进程结束,将它从init_action_list链表里删除

ONCE:第三个执行,创建子进程,将它从init_action_list链表里删除

RESPAWN:循环执行

ASKFIRST:循环执行,打印:Please press Enter to activate this console,等待回车后,创建子程序-/bin/sh,就不再返回了

restart, ctrlaltdel, and shutdown:在按键按下的时候随时执行

这样就分析完了Busybox的init程序,ASKFIRST时机相关的脚本或命令执行以后,因为脚本有-/bin/sh,所以最终会进入sh进程。它位于shell\Ash.c下的ash_main函数,这个函数就不分析了。到这里从uboot开始一直到根文件系统下的第一个用户程序全部分析完成。

猜你喜欢

转载自www.cnblogs.com/andyfly/p/9419359.html