BusyBox init及其inittab文件分析

转帖自:http://blog.chinaunix.net/space.php?uid=23089249&do=blog&cuid=2208026

由于BusyBox自身的一些特点,BusyBox init非常适合在嵌入式系统开发中使用,被誉为嵌入式linux的瑞士军刀,它可以为嵌入式系统提供只要的init功能,并且通过定制可以做得非常精炼。inittab是帮助init完成系统配置的主要文件。     
       /* Line is: "id:runlevel_ignored:action:command" */

这是BusyBox-1.11.1init.c文件中的一句注释,该注释指明了inittab文件中每行的格式。以下对各字段进行简要解析:

1id

尽管该格式与发行版linuxSys V init类似,但是,idBusyBoxinit中具有不同的意义。对BusyBox而言,id用来指定启动进程的控制终端。如果所启动的进程并不是可以交互的shell,例如BusyBoxshash),应该会有个控制终端,如果控制终端不存在,BusyBoxsh会报错。

2runlevel_ignored

由该字段的名称可知,BusyBox init忽略runlevel_ignored字段,所以配置inittab时空着它就行了。

4command

command字段用来指定要执行命令(含路径),包括命令行选项。

3action 

BusyBox-1.11.1init.c定义了以下8action

       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_SYSINITSTR_RESPAWNSTR_ASKFIRSTSTR_WAITSTR_ONCESTR_CTRLALTDELSTR_SHUTDOWNSTR_RESTARTaction_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"

下表列举了这8action的含义

 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.1init.c文件可知,BusyBox init通过init_main方法对inittab文件的分析执行,大致过程如下:

 1init_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_INITTABinittab文件不存在,则执行一个默认的操作,如下:

    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_SCRIPTINIT_SCRIPT的定义如下:

#define INIT_SCRIPT  "/etc/init.d/rcS" /* Default sysinit script. */

即,BusyBox init默认的初始化脚本是/etc/init.d/rcS

当支持ENABLE_FEATURE_USE_INITTABinittab文件存在时,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编码)和idcommand字段作为一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字段复制到tmpConsole5个字符之后,即/dev/之后。这样tmpConsole就成为了某一设备文件名(含路径)。对于BusyBox init来说,tmpConsole是终端设备*/
                    safe_strncpy(tmpConsole + 5, id,
                        
sizeof(tmpConsole) - 5);

                       /*来到这里,应该就明白为什么BusyBox initid字段是控制终端*/
                    id = tmpConsole;
                
}

                  //action_typecommand和控制终端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
: ;
    
}

 2BusyBox 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)来执行具体的commandrun_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)) {/*若该节点类型为SYSINITWAITCTRLALTDELSHUTDOWNRESTARTinit_actioninit会等待它的command执行完,再继续执行。并且command执行完后,删除该节点*/
                waitfor
(run(a));
                delete_init_action
(a);
            
} else if (a->action_type & ONCE) {

              /*action_typeONCEinit_actioninit则不会等待它执行完,并且将该节点从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_typeRESOAWNASKFIRSTinit_action,且执行该init_actioncommand的进程已死(通过a->pid == 0判断,已死RESOAWNASKFIRSTcommand进程,其init_actionpid域都会在init_main方法被置为0,具体见本文最后一段源码)时,调用run方法fork一子进程(用于执行command),并将run返回的子进程ID保存于init_actionpid*/
                
if (a->pid == 0) {
                    a
->pid = run(a);
                
}
            
}
        
}
    
}
}

run_actions源码可知:action_typeSYSINITWAITCTRLALTDELSHUTDOWNRESTARTONCEinit_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);
        
}
    
}
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值