由于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
staticconst 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 constchar 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); } elseif (a->action_type& ONCE) { /*action_type为ONCE的init_action,init则不会等待它执行完,并且将该节点从init_action_list中删除*/ run(a); delete_init_action(a); } elseif (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 |