BusyBox init及其inittab文件分析

2019-07-12 23:17发布

由于BusyBox自身的一些特点,BusyBox init非常适合在嵌入式系统开发中使用,被誉为“嵌入式linux的瑞士军刀”,它可以为嵌入式系统提供只要的init功能,并且通过定制可以做得非常 精炼。inittab是帮助init完成系统配置的主要文件。     
       /* Line is: "id:runlevel_ignored:action:command" */ 这是BusyBox-1.11.1中init.c文件中的一句注释,该注释指明了inittab文件中每行的格式。以下对各字段进行简要解析: 1、id 尽管该格式与发行版linux的Sys V init类似,但是,id在BusyBox的init中具有不同的意义。对BusyBox而言,id用来指定启动进程的控制终端 。如果所启动的进程并不是可以交互的shell,例如BusyBox的sh(ash),应该会有个控制终端,如果控制终端不存在,BusyBox的sh会报错。 2、runlevel_ignored 由该字段的名称可知,BusyBox init忽略runlevel_ignored字段 ,所以配置inittab时空着它就行了。 4、command command字段用来指定要执行命令(含路径),包括命令行选项。 3、action  在BusyBox-1.11.1中init.c定义了以下8种action        static const char actions[ ] = 
            STR_SYSINIT "sysinit/0" 
            STR_RESPAWN "respawn/0" 
            STR_ASKFIRST "askfirst/0" 
            STR_WAIT "wait/0" 
            STR_ONCE "once/0" 
            STR_CTRLALTDEL "ctrlaltdel/0" 
            STR_SHUTDOWN "shutdown/0" 
            STR_RESTART "restart/0" 
        ;
其中,STR_SYSINIT、STR_RESPAWN、STR_ASKFIRST、STR_WAIT、STR_ONCE、 STR_CTRLALTDEL、STR_SHUTDOWN、STR_RESTART为action_type,即action的编码。它们各占1字节,具 体定义如下: # define STR_SYSINIT "/x01" 
# define STR_RESPAWN "/x02" 
# define STR_ASKFIRST "/x04" 
# define STR_WAIT "/x08" 
# define STR_ONCE "/x10" 
# define STR_CTRLALTDEL "/x20" 
# define STR_SHUTDOWN "/x40" 
# define STR_RESTART "/x80"
下表列举了这8种action的含义 :  action  含义  sysinit  为init提供初始化命令脚本的路径  respawn  每当相应的进程终止执行时,重新启动该进程  askfirst  类似respawn,主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动进程之前等待用户按下“enter”键  wait  告诉init必须等到相应的进程执行完成之后才能继续执行  once  仅执行相应的进程一次,而且不会等待它执行完成  ctratldel  当按下Ctrl+Alt+Delete组合键时,执行相应的进程  shutdown  当系统关机时,执行相应的进程  restart  当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身   下面简要介绍一下BusyBox init怎么对inittab进行分析执行的。由BusyBox-1.11.1中init.c文件可知,BusyBox init通过init_main方法对inittab文件的分析执行,大致过程如下:  1、init_main方法先通过parse_inittab分析inittab文件,将该文件中的每一行通过 new_init_action(uint8_t action_type, const char *command, const char *cons)添加到init_action_list列表中。其中cons就是每行的id字段。init_action_list的定义如下: /* Set up a linked list of init_actions, to be read from inittab */ /* inittab文件的每一行都会保存为一个init_action节点,并且所有 init_action节点会被链接成一个叫init_action_list的列表*/ 
struct init_action { 
    struct init_action * next; 
    pid_t pid;                           /* 实际执行该command的进程ID*/ 
    uint8_t action_type;                 /* action的类型 */ 
    char terminal[ CONSOLE_NAME_SIZE] ;    /* 运行该command的终端 */ 
    char command[ COMMAND_SIZE] ;          /* 保存command字段(含命令行选项)*/ 
} ; 

/* Static variables */ 
static struct init_action * init_action_list = NULL ;
若不支持ENABLE_FEATURE_USE_INITTAB或支持ENABLE_FEATURE_USE_INITTAB但inittab文件不存在,则执行一个默认的操作,如下:     if ( ENABLE_FEATURE_USE_INITTAB) 
        file = fopen ( INITTAB, "r" ) ; 
    else 
        file = NULL ; 

    /* No inittab file -- set up some default behavior */ 
    if ( file = = NULL ) { 
        /* 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, "" ) ; 
        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 ; 
    }
通过代码中的英文注释,应该都可以看懂该代码。需要解释可能只有INIT_SCRIPT。INIT_SCRIPT的定义如下: #define INIT_SCRIPT  "/etc/init.d/rcS" /* Default sysinit script. */ 即,BusyBox init默认的初始化脚本是/etc/init.d/rcS。 当支持ENABLE_FEATURE_USE_INITTAB且inittab文件存在时,BusyBox init会对inittab文件进行如下分析:     //循环获取inittab文件中的每一行到buf中     while ( fgets ( buf, COMMAND_SIZE, file ) ! = NULL ) {         //定义action的种类 
        static const char actions[ ] = 
            STR_SYSINIT "sysinit/0" 
            STR_RESPAWN "respawn/0" 
            STR_ASKFIRST "askfirst/0" 
            STR_WAIT "wait/0" 
            STR_ONCE "once/0" 
            STR_CTRLALTDEL "ctrlaltdel/0" 
            STR_SHUTDOWN "shutdown/0" 
            STR_RESTART "restart/0" 
        ; 
        char tmpConsole[ CONSOLE_NAME_SIZE] ; 
        char * id, * runlev, * action, * command; 
        const char * a; 
         /*通过跳过空格,并截取到换行/n为止,来获取本行的有效内容,并保存于id中*/         
        /* Skip leading spaces */ 
        id = skip_whitespace( buf) ; 
        /* Trim the trailing '/n' */ 
        * strchrnul( id, '/n' ) = '/0' ;
         //若为注释,跳出本次循环   
        /* Skip the line if it is a comment */ 
        if ( * id = = '#' | | * id = = '/0' ) 
            continue ; 

        /* Line is: "id:runlevel_ignored:action:command" */
         //获取runlev字段   
        runlev = strchr ( id, ':' ) ; 
        if ( runlev = = NULL /*|| runlev[1] == '/0' - not needed */ ) 
            goto bad_entry;
         //获取action字段   
        action = strchr ( runlev + 1, ':' ) ; 
        if ( action = = NULL /*|| action[1] == '/0' - not needed */ ) 
            goto bad_entry;
         //获取command字段 
        command = strchr ( action + 1, ':' ) ; 
        if ( command = = NULL | | command[ 1] = = '/0' ) 
            goto bad_entry; 
         /*循环遍历actions数组,查找数组中与action字段相同的元素。找到后,通过new_init_action方法,将该元素的第一个字符(即action_type编码)和id及command字段作为一init_action节点添加到init_action_list列表中。接着跳到下一行进行处理*/ 
        * command = '/0' ; /* action => ":action/0" now */ 
        for ( a = actions; a[ 0] ; a + = strlen ( a) + 1) {
              //查到数组actions中与action字段相同的元素   
            if ( strcmp ( a + 1, action + 1) = = 0) { 
                   //截取id字段,格式为"id/0"
                * runlev = '/0' ;                    //若id字段非空 
                if ( * id ! = '/0' ) {
                       //若id字段带前缀/dev/,先截掉该前缀 
                    if ( strncmp ( id, "/dev/" , 5) = = 0) 
                        id + = 5;
                       //复制字符串/dev/到tmpConsole临时缓存区中 
                    strcpy ( tmpConsole, "/dev/" ) ;
                       /*再将id字段复制到tmpConsole第5个字符之后,即/dev/之后。这样tmpConsole就成为了某一设备文件名(含路径)。对于BusyBox init来说,tmpConsole是终端设备*/ 
                    safe_strncpy( tmpConsole + 5, id, 
                        sizeof ( tmpConsole) - 5) ;
                       /*来到这里,应该就明白为什么BusyBox init的id字段是控制终端*/ 
                    id = tmpConsole; 
                }
                  //将action_type、command和控制终端id作为一init_action节点,添加到init_action_list列表中。从这里可以看出BusyBox init忽略了runlevel字段 
                new_init_action( ( uint8_t ) a[ 0] , command + 1, id) ; 
                goto next_line; 
            } 
        } 
        * command = ':' ; 
        /* Choke on an unknown action */ 
 bad_entry: 
        message( L_LOG | L_CONSOLE, "Bad inittab entry: %s" , id) ; 
 next_line: ; 
    }
 2、BusyBox init分析完inittab之后,就是执行各command了。BusyBox init通过run_actions(int action_type)方法,查找init_action_list列表中action类型为action_type的所有init_action,并 为符合条件的init_action节点调用run(const struct init_action *a)。而实际上,run方法中,是通过init_exec(a->command)来执行具体的command。run_actions的源码如 下: /* Run all commands of a particular type */ 
static void run_actions( int action_type) 
{ 
    struct init_action * a, * tmp; 
    // 遍历init_action_list列表,查找类型为action_type的节点 
    for ( a = init_action_list; a; a = tmp) { 
        tmp = a- > next;
        //查到类型为action_type的节点 
        if ( a- > action_type & action_type) { 
            
 // Pointless: run() will error out if open of device fails.

            
 ///* a->terminal of "" means "init's console" */

            
 //if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {

            
 //    //message(L_LOG | L_CONSOLE, "Device %s cannot be opened in RW mode", a->terminal /*, strerror(errno)*/);

            
 //    delete_init_action(a);

            
 //} else
            
            if ( a- > action_type & ( SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART) ){/*若该节点类型为SYSINIT、WAIT、CTRLALTDEL、SHUTDOWN 和RESTART的init_action,init会等待它的command执行完,再继续执行。并且command执行完后,删除该节点*/ 
                waitfor( run( a) ) ; 
                delete_init_action( a) ; 
            } else if ( a- > action_type & ONCE) {
              /*action_type为ONCE的init_action,init则不会等待它执行完,并且将该节点从init_action_list中删除*/ 
                run( a) ; 
                delete_init_action( a) ; 
            } else if ( a- > action_type & ( RESPAWN | ASKFIRST) ) { 
                
 /* Only run stuff with pid==0. If they have
                 * a pid, that means it is still running */
              /*当action_type为RESOAWN或ASKFIRST的init_action,且执行该init_action的command的进程已死 (通过a->pid == 0判断,已死RESOAWN或ASKFIRST的command进程,其init_action的pid域都会在init_main方法被置为0,具体见 本文最后一段源码)时,调用run方法fork一子进程(用于执行command),并将run返回的子进程ID保存于init_action的pid 域*/ 
                if ( a- > pid = = 0) { 
                    a- > pid = run( a) ; 
                } 
            } 
        } 
    } 
}
由run_actions源码可知:action_type为SYSINIT、WAIT、CTRLALTDEL、SHUTDOWN 、 RESTART和ONCE的init_action,执行其command后,都会通过delete_init_action(struct init_action *action)将它从init_action_list中删除,这样做的原因可能是:这些init_action节点只会被BusyBox init执行一次,并且删除它们可提高对init_action_list其它类型的init_action节点的查找效率并节省空间。结合以下 BusyBox init对各类init_action的执行顺序,可能会更好的理解这一点。 由init_main方法可知,BusyBox init对各类init_action的执行顺序如下 :     /* Now run everything that needs to be run */ 

    /* First run the sysinit command */ 
    run_actions( SYSINIT) ; 

    /* Next run anything that wants to block */ 
    run_actions( WAIT) ; 

    /* Next run anything to be run only once */ 
    run_actions( ONCE) ; 

    /* Now run the looping stuff for the rest of forever */ 
    while ( 1) { 
        /* run the respawn/askfirst stuff */ 
        run_actions( RESPAWN | ASKFIRST) ; 

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

        /* Wait for any 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() */
 
                    a- > pid = 0; 
                    message( L_LOG, "process '%s' (pid %d) exited. " 
                            "Scheduling for restart." , 
                            a- > command, wpid) ; 
                } 
            } 
            /* see if anyone else is waiting to be reaped */ 
            wpid = wait_any_nohang( NULL ) ; 
        } 
    } 
}