《构建根文件系统(二)分析busybox源码》

1.busybox

  平时我们在开发板中输入ls、cp、mv等命令,都是在/bin文件中。而通过ls -l就可以发现

  

  这些命令都是放在busybox中的。并且在内核启动后,通过ps命令,可以看到有一个init进程正在运行。

  

 

   因此就先来分析一下,这个第一个运行的进程init。

 

2.init进程分析

init_main函数:

int
init_main(int argc, char **argv) { ... ... console_init(); //初始化控制台,在init_post()中只是打开了控制台设备 ... ... if (argc > 1 //在init_post()中执行的”_init_process("/sbin/init");”,所以argc=1, argv=/sbin/init && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))) {... ...} //此处省略,因为不执行 else { parse_inittab(); //argc==1,执行else,读取解析init 表(解析配置文件) } .... ... //运行应用程序 }

  因为argc=1,所以直接跳转到parse_inittab。

parse_inittab函数:
#if ENABLE_FEATURE_USE_INITTAB
 char *token[4];
 parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
 if (parser == NULL)
#endif

  首先如果定义了ENABLE_FEATURE_USE_INITTAB的话,就会去读取/etc/inittab的内容。

  那么怎么知道ENABLE_FEATURE_USE_INITTAB有没有被定义?

  可以make menuconfig

  然后查找inittab

  

 

  进入init utilitire的init

  

 

   这个就可以了。不过一般默认就是选中的。

  通过在源码中搜索inittab可以找到两个文件,一个是inittab的相关文档说明,一个是inittab的配置文档。

inittab说明文档:

# Format for each entry: <id>:<runlevels>:<action>:<process>

  以上说明了inittab的格式。

  id:/dev/id,用作终端,就是我们的标准输入、标准输出、标准错误。

  runlevels:可以忽略。

  action:执行时间。

  process:应用程序或脚本。

parse_inittab函数:    

if (parser == NULL) #endif { /* No inittab file - set up some default behavior */ /* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "reboot", ""); /* Umount all filesystems on halt/reboot */ new_init_action(SHUTDOWN, "umount -a -r", ""); /* Swapoff on halt/reboot */ if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", ""); /* Prepare to restart init when a QUIT is received */ new_init_action(RESTART, "init", ""); /* Askfirst shell on tty1-4 */ new_init_action(ASKFIRST, bb_default_login_shell, ""); //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users new_init_action(ASKFIRST, bb_default_login_shell, VC_2); new_init_action(ASKFIRST, bb_default_login_shell, VC_3); new_init_action(ASKFIRST, bb_default_login_shell, VC_4); /* sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, ""); return; }

  如果parser为NULL,那么将会执行默认的操作。也就是如果没有inittab这个配置文件的话,系统也会默认执行一些操作。

   new_init_action(ASKFIRST, bb_default_login_shell, VC_2)以这个为例解析一下:

  #define ASKFIRST    0x10

  bb_default_login_shell是一个被声明的char的变量,查看一下可以发现#define LIBBB_DEFAULT_LOGIN_SHELL     "-/bin/sh"

  # define VC_2 "/dev/tty2"

  所以等价为new_init_action(0x10,   "-/bin/sh"l,"/dev/tty2")

  

  接下来分析一下new_init_action函数:

new_init_action函数:

    struct init_action *a, **nextp;

    nextp = &init_action_list;
    while ((a = *nextp) != NULL) {
        /* Don't enter action if it's already in the list,
         * This prevents losing running RESPAWNs.
         */
        if (strcmp(a->command, command) == 0
         && strcmp(a->terminal, cons) == 0
        ) {
            /* Remove from list */
            *nextp = a->next;
            /* Find the end of the list */
            while (*nextp != NULL)
                nextp = &(*nextp)->next;
            a->next = NULL;
            break;
        }
        nextp = &a->next;
    }    

/* A linked list of init_actions, to be read from inittab */
struct init_action {
 struct init_action *next;
 pid_t pid;
 uint8_t action_type;
 char terminal[CONSOLE_NAME_SIZE];
 char command[COMMAND_SIZE];
};

  定义了一个结构体变量a和nextp,这个结构体的内容其实就是传递进来的参数。然后利用一个链表,进行查询。

  如果init_action_list中已经有了,则跳过。直到查询到最后一个链表。

new_init_action函数

    if (!a)
        a = xzalloc(sizeof(*a));
    /* Append to the end of the list */
    *nextp = a;
    a->action_type = action_type;
    safe_strncpy(a->command, command, sizeof(a->command));
    safe_strncpy(a->terminal, cons, sizeof(a->terminal));    

  如果在init_action_list没有找到,则将传递进来的参数进行填充。

 

  所以从几个默认的new_init_action可以知道配置文件:

  首先从inittab文档中看到inittab的格式是:

  ::sysinit:/etc/init.d/rcS

  Format for each entry: <id>:<runlevels>:<action>:<process>

   <action>: Valid actions include: sysinit, respawn, askfirst, wait, once,
                              restart, ctrlaltdel, and shutdown.

  所以action是sysinit:   process是/etc/init.d/rcS

 

  new_init_action(CTRLALTDEL, "reboot", "") ---> ::ctrlaltdel:reboot

  new_init_action(SHUTDOWN, "umount -a -r", "") ---> ::shutdown:umount -a -r    

  new_init_action(RESTART, "init", "") ---> ::restart:init

  new_init_action(ASKFIRST, bb_default_login_shell, "") ---> ::askfirst:-/bin/sh

  new_init_action(ASKFIRST, bb_default_login_shell, VC_2) ---> tty2::askfirst:-/bin/sh

  new_init_action(ASKFIRST, bb_default_login_shell, VC_3) ---> tty3::askfirst:-/bin/sh

  new_init_action(ASKFIRST, bb_default_login_shell, VC_4) ---> tty4::askfirst:-/bin/sh

  new_init_action(SYSINIT, INIT_SCRIPT, "") ---> ::sysinit:/etc/init.d/rcS

 

  接下来就是对new_init_action完后,然后执行这些操作

  

init_main函数

    /* Now run everything that needs to be run */
    /* First run the sysinit command */
    run_actions(SYSINIT);
    check_delayed_sigs();
    /* Next run anything that wants to block */
    run_actions(WAIT);
    check_delayed_sigs();
    /* Next run anything to be run only once */
    run_actions(ONCE);

  很明显就是运行一些需要开机运行的程序。执行顺序:SYSINIT类、WAIT类、ONCE类。

  run_actions里面的源码就不进去分析了,和new_init_action是一样的。

 

   接下来就会进入一个while(1)的循环,其中run_actions(RESPAWN | ASKFIRST),还会检测一些信号,然后执行相对应的动作。这个while部分后面再详细分析

init_main函数

while(1){
        int maybe_WNOHANG;

        maybe_WNOHANG = check_delayed_sigs();

        /* (Re)run the respawn/askfirst stuff */
        run_actions(RESPAWN | ASKFIRST);
        maybe_WNOHANG |= check_delayed_sigs();

        /* Don't consume all CPU time - sleep a bit */
        sleep(1);
        maybe_WNOHANG |= check_delayed_sigs();

        /* Wait for any child process(es) to exit.
         *
         * If check_delayed_sigs above reported that a signal
         * was caught, wait will be nonblocking. This ensures
         * that if SIGHUP has reloaded inittab, respawn and askfirst
         * actions will not be delayed until next child death.
         */
        if (maybe_WNOHANG)
            maybe_WNOHANG = WNOHANG;
        while (1) {
            pid_t wpid;
            struct init_action *a;

            /* If signals happen _in_ the wait, they interrupt it,
             * bb_signals_recursive_norestart set them up that way
             */
            wpid = waitpid(-1, NULL, maybe_WNOHANG);
            if (wpid <= 0)
                break;

            a = mark_terminated(wpid);
            if (a) {
                message(L_LOG, "process '%s' (pid %d) exited. "
                        "Scheduling for restart.",
                        a->command, wpid);
            }
            /* See if anyone else is waiting to be reaped */
            maybe_WNOHANG = WNOHANG;
        }
    }

 

 

 

总结:

  我们使用的文件系统的命令都是链接到busybox,而busybox开始都会运行init进程。

  init进程都做了什么事情:

  1.打开/etc/inittab

  2.根据配置文件里面指定的应用程序,去运行。如果没有读取到,init会有默认的配置项

  

 

 

 

 

 

  

posted @ 2019-09-20 16:02  一个不知道干嘛的小萌新  阅读(885)  评论(0编辑  收藏  举报