:
下载busybox 1.00
# tar jxvf busybox-1.00.tar.bz2 # cd busybox-1.00 # make defconfig # make menuconfig 配置 # make # make install 需要cp到ramdisk的文件在_install目录中下面主要分析一下内核到busybox的启动流程 在kernel/init/main.c的init函数中有代码 if (execute_command) execve(execute_command,argv_init,envp_init); execve("/sbin/init",argv_init,envp_init); 一般启动命令行会给出 init=/linuxrc 这个参数,于是就有了 execute_command = "/linuxrc" busybox中_install目录下的 linuxrc 是busybox的一个软链接 而在我的ramdisk中已经被替换成一个shell脚本,其中的代码是 #!/bin/sh exec /sbin/init 所以其实这个linuxrc基本没什么用处,我就可以把 if (execute_command) execve(execute_command,argv_init,envp_init); 这句代码注释掉,于是内核就直接运行/sbin/init了 /sbin/init也是busybox的软链接,所以接着下来就要看busybox的源代码了 入口函数main在busybox-1.00/applets/busybox.c中 int main(int argc, char **argv) { const char *s; bb_applet_name = argv[0]; if (bb_applet_name[0] == '-') bb_applet_name++; for (s = bb_applet_name; *s != '\0';) { if (*s++ == '/') bb_applet_name = s; } #ifdef CONFIG_LOCALE_SUPPORT #ifdef CONFIG_INIT if(getpid()!=1) /* Do not set locale for `init' */ #endif { setlocale(LC_ALL, ""); } #endif run_applet_by_name(bb_applet_name, argc, argv); bb_error_msg_and_die("applet not found"); }
bb_applet_name = argv[0] 对于execve("/sbin/init",argv_init,envp_init) bb_applet_name = argv_init[0] = "init" 在进入shell后,执行命令 ls -l 同样也会调用main函数 这是 argv[0]="ls" argv[1]="-l" 接着是run_applet_by_name(bb_applet_name, argc, argv) 该函数的作用是找到bb_applet_name对应的主函数并执行,主要的代码如下 if ((applet_using = find_applet_by_name (name)) != NULL) { bb_applet_name = applet_using->name; exit ((*(applet_using->main)) (argc, argv)); } struct BB_applet * find_applet_by_name (const char *name) { return bsearch (name, applets, NUM_APPLETS, sizeof (struct BB_applet), applet_name_compare); } 可以看出find_applet_by_name从applets结构中寻找name对应的项。 const struct BB_applet applets[] = { #define APPLET(a,b,c,d) {#a,b,c,d}, #define APPLET_NOUSAGE(a,b,c,d) {a,b,c,d}, #define APPLET_ODDNAME(a,b,c,d,e) {a,b,c,d}, #ifdef CONFIG_TEST APPLET_NOUSAGE("[", test_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER) #endif #ifdef CONFIG_ADDGROUP APPLET(addgroup, addgroup_main, _BB_DIR_BIN, _BB_SUID_NEVER) #endif #ifdef CONFIG_ADDUSER APPLET(adduser, adduser_main, _BB_DIR_BIN, _BB_SUID_NEVER) #endif 。。。。} 这样看来如果想要在busybox中添加相应的命令,就只需在这里添加一项并提供 相应的主函数即可。找到我关注的是init项 #ifdef CONFIG_INIT APPLET(init, init_main, _BB_DIR_SBIN, _BB_SUID_NEVER) #endif 于是init_main函数被执行。 在busybox-1.00/init/init.c中找到init_main函数,分析一下其中的关键代码 parse_inittab(); run_actions(SYSINIT); run_actions(ASKFIRST); 主要是这三个调用 parse_inittab函数分析inittab文件并执行其中的命令方便一点可以把删除inittab文件,而我这里也是没有inittab文件的。 以下是没有inittab文件执行的代码 file = fopen(INITTAB, "r"); if (file == NULL) { /* No inittab file -- set up some default behavior */ /* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "/sbin/reboot", ""); /* Umount all filesystems on halt/reboot */ new_init_action(SHUTDOWN, "/bin/umount -a -r", ""); #if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) /* Swapoff on halt/reboot */ new_init_action(SHUTDOWN, "/sbin/swapoff -a", ""); #endif /* Prepare to restart init when a HUP is received */ new_init_action(RESTART, "/sbin/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; #ifdef CONFIG_FEATURE_USE_INITTAB } 只是调用了很多的new_init_action函数,这个函数其实是把这些init_action添加到 以init_action_list为头的链表里,这样便可以通过run_actions函数来调用。 run_actions(SYSINIT) 就会执行 INIT_SCRIPT 命令#define INIT_SCRIPT "/etc/init.d/rcS" /* Default sysinit script. */
于是就执行了/etc/init.d/rcS这个shell脚本,需要看一下run_actions这个函数 static void run_actions(int action) { struct init_action *a, *tmp; for (a = init_action_list; a; a = tmp) { tmp = a->next; if (a->action == action) { if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) { waitfor(a); delete_init_action(a); } else if (a->action & ONCE) { run(a); delete_init_action(a); } else if (a->action & (RESPAWN | ASKFIRST)) { if (a->pid == 0) a->pid = run(a); } } } } 对于SYSINIT运行的是waitfor(a),而对于ASKFIRST运行的是run(a) 看waitfor函数的代码知道其实它也调用了run(a)建了一个子进程,只是 父进程会等待子进程运行结束。run函数是比较长的,只取其中的关键代码看看 strcpy(buf, a->command); s = buf; for (tmpCmd = buf, i = 0; (tmpCmd = strsep(&s, " \t")) != NULL;) { if (*tmpCmd != '\0') { cmd[i] = tmpCmd; i++; } } cmd[i] = NULL; cmdpath = cmd[0]; if (*cmdpath == '-') { ++cmdpath; s = bb_get_last_path_component(cmdpath); /* make a new argv[0] */ if ((cmd[0] = malloc(strlen(s) + 2)) == NULL) { message(LOG | CONSOLE, bb_msg_memory_exhausted); cmd[0] = cmdpath; } else { cmd[0][0] = '-'; strcpy(cmd[0] + 1, s); } } execv(cmdpath, cmd); 其实就是处理a->command来获的要执行的文件路径和argv参数,调用execv函数执行它 对于run_actions(SYSINIT) 其a->command = "/etc/init.d/rcS" 处理后cmdpath = "/etc/init.d/rcS" ,cmd[0] = "/etc/init.d/rcS" run_actions(ASKFIRST) 其a->command = bb_default_login_shell const char * const bb_default_login_shell = LIBBB_DEFAULT_LOGIN_SHELL; #define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh" a->command = "-/bin/sh" 处理后cmdpath = "/bin/sh" ,cmd[0] = "-sh" 由于run_actions(SYSINIT)会一直等待子进程执行完, /etc/init.d/rcS就执行完了,基本的初始化也完成了,那么就可以进入shell了 这里就是execv(cmdpath, cmd)去执行shell的 一般会要求按回车才进shell,可以把run函数中以下注释掉来取消回车直接进入 if (a->action & ASKFIRST) { char c; messageD(LOG, "Waiting for enter to start '%s'(pid %d, terminal %s)\n", cmdpath, getpid(), a->terminal); bb_full_write(1, press_enter, sizeof(press_enter) - 1); while(read(0, &c, 1) == 1 && c != '\n') ; } /bin/sh同样是busybox的软链接,同样去执行main函数 这是的argv[0] = "-sh" 去掉-后,用"sh"去查找函数 #if defined(CONFIG_FEATURE_SH_IS_ASH) && defined(CONFIG_ASH) APPLET_NOUSAGE("sh", ash_main, _BB_DIR_BIN, _BB_SUID_NEVER) #elif defined(CONFIG_FEATURE_SH_IS_HUSH) && defined(CONFIG_HUSH) APPLET_NOUSAGE("sh", hush_main, _BB_DIR_BIN, _BB_SUID_NEVER) #elif defined(CONFIG_FEATURE_SH_IS_LASH) && defined(CONFIG_LASH) APPLET_NOUSAGE("sh", lash_main, _BB_DIR_BIN, _BB_SUID_NEVER) #elif defined(CONFIG_FEATURE_SH_IS_MSH) && defined(CONFIG_MSH) APPLET_NOUSAGE("sh", msh_main, _BB_DIR_BIN, _BB_SUID_NEVER) #endif busybox中有四种shell,在配置的时候注意下,一般选择默认的为ash 这样执行的函数就是ash_main。