《构建根文件系统(二)分析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函数:
char *token[4];
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
#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会有默认的配置项