Busybox init进程启动过程分析

时间:2022-03-04 16:52:16

 

Busybox
    Busybo是一个遵循GPLv2协议的开源项目。Busybox将众多的Linux命令集合进一个很小的可执行程序中,可以用来替换GNU fileutils shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项较少,但是能够满足一般应用。Busybox为各种小型的或者嵌入式系统提供了一个比较完完全的工具集。
Busybox在编写过程中对文件大小进行了优化,并考虑了系统资源有限的情况。与一般的GNU工具集动辄几MB的体积相比,动态连接的Busybox只有几百KB,即使静态连接也只有1MB左右。有人将Busybox比喻成Linux工具中的瑞士军刀,简单的说就是好像是Linux的一个大的工具集,包括了Linux中的大部分命令和工具。嵌入式根目录下的bin,sbin和usr目录以及linuxc通常就是Busybox。Busybox会根据配置的不同自动的生成一些文件,但是有些根文件系统下的文件还是需要用户自己来建立。
二、Busybox启动流程分析
   init进程是由内核启动的第一个也是惟一的一个用户进程,它根据配置文件决定启动哪些程序,比如执行某些脚本,启动shell,运行用户指定的程序等。init进程是后续所有进程的发起者,比如init进程启动/bin/sh程序后,才能够在控制台上输入各种命令。
   init进程的执行程序通常是sbin/init,上面讲述的init进程的作用只不过是/sbin/init这个程序的功能。在嵌入式领域,通常使用Busybox集成的init程序.嵌入式根目录下的bin,sbin和usr目录以及linuxc通常就是Busybox。
1、在kernel/init/main.c的init函数中有如下代码:
 if(execute_command)
 execve(execute_command,argv_init,envp_init);
 execve("/sbin/init",argv_init,envp_init);
bootloader会传给内核的main函数 init=/linuxrc这个参数,于是就会执行下面的这句
execute_command = "linuxrc",busybox中_install目录下的linuxrc是Busybox的一个软链接,指向/bin/busybox,而/sbin/init也是/bin/busybox的符号链接,因此这个linxrc基本没有实际的意义只是一个连接作用。我们可以重写linuxrc,添加自己的一些初始化的东西。这样就可以把Linux内核中的init程序和Busybox中的init程序结合起来了。
2、Busybox init进程启动流程
   Busybox是目标板系统上执行的第一个应用程序,当调用Busybox它会执行Busybox自身的init进程。
 Busybox initt 程序对应的代码在init/init.c文件中。其对应的流程图如下:
 
 
Busybox init进程启动过程分析
    其中与构建根文件系统关系密切的是控制台的初始化,对inittab文件的解释及执行。从图中我们可以看出,Busybox init启动的第一个函数是int init_main(int argc UNUSED_PARAM, char **argv),在这里可以设置信号的处理函数,初始化控制台,最重要的是解析inittab中内容。
 在init_main()函数中会调用parse_inittab(void)函数,parse_inittab(void)函数可以使用一些默认的配置,当/etc/inittab没有配置时。
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
    char *token[4];
    parser_t *parser = config_open2("/etc/inittab", fopen_for_read);

    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;
 }
..............   
}

    其中最重要的一个是其中,最重要的一个,就是 new_init_action(SYSINIT, INIT_SCRIPT, ""),也就决定了接下去初始化的脚本是INIT_SCRIPT所定义的值。这个宏的默认值是"/etc/init.d/rcS".

 
#define INITTAB "/etc/inittab"    /* inittab file location */
#ifndef INIT_SCRIPT
#define INIT_SCRIPT "/etc/init.d/rcS"    /* Default sysinit script. */
#endif

  1)下面是分析文件系统中/etc/init.d/rcS的内容,这是我的rcS文件中的内容。这个文件会在inittab中使用。在inittab后启动rcS.

#!/bin/sh 
PATH=/sbin:/bin:/usr/sbin:/usr/bin 
runlevel=S 
prevlevel=N 
umask 022 
export PATH runlevel prevlevel  //上面几句为启动环境设置必要的环境变量
echo "----------munt all----------------" 
mount -a  //加载文件/etc/fstab文件中的选项,
echo /sbin/mdev>/proc/sys/kernel/hotplug 
mdev -s  //在/dev 目录下建立必要的设备节点;
echo "***********************************************" 
echo "****************Studying ARM Embedded *********************
echo "Kernel version:linux-2.6.32.1" 
echo "Author frank" 
echo "Date:2010.4.19" 
echo "***********************************************
/bin/hostname -F /etc/sysconfig/HOSTNAME  //设置主机的名字

 

//下面这一句是设置内核的hotplug handler 为 mdev, 即当设备热插拔时,由 mdev 接收来自内核的消息并作出相应的回应, 比如挂载U盘。
echo /sbin/mdev>/proc/sys/kernel/hotplug

2)下面是inittab文件的分析:如果存在/etc/inittab文件,Busybox init程序解析它,然后按照它的指示各种子进程,否则使用默认的配置创建子进程

#etc/inittab
::sysinit:/etc/init.d/rcS    //作为系统初始化文件.
s3c2410_serial0::askfirst:-/bin/sh  在串口启动一个登录会话
::ctrlaltdel:/sbin/reboot  //作为init重启执行程序.
::shutdown:/bin/umount -a –r
  //告诉init在关机时运行umount命令卸载所有的文件系统,如果卸载失败,试图以只读方式重新挂载。

/etc/inittab 文件中每个条目用来定义一个子进程,并确定它的启动方法,格式如下 :
<id>:<runlevels>:<action>:<process>
例如:
ttySAC0:askfirst:-/bin/sh
(1)<id>:表示这个进程要使用的控制台(即标准输入、标准输出、标准错误设备)。如果省略,则使用与init进程一样的控制台。
(2)<runlevels>:对于Busybox init程序,这个字段滑意义,可以省略。
(3)<action>:表示init程序如何控制这个子进程,
(4)<process>: 要执行的程序,它可以是可执行程序,也可以是脚本
如果<process>:字段有"-"字符,表示这个程序被称为“交互的”。在/etc/inittab/文件的控制下,init进程的行为总结如下:
(1)在系统启动前期,init进程首先启动<action>为sysinit wait once的3类子进程。
(2)在系统正常运行期间,init程序首先启动,<action>为respawn askfirst的两类子进程,并监视它们,发现某个子进程退出时重新启动它。
(3) 在系统退出时,执行<action> 为shutdown restart ctrlaltdel的3类子进程之一或全部。
如果根文件系统中没有/etc/initab文件,Busybox init程序将使用如下默认的inittab条目。

             /etc/inittab文件中<action>字段的意义

Action名称

执行条件

说明

Sysinit

系统启动后最先执行

只执行一次,init进程等待它结束才继续执行其它动作

Wait

系统执行完sysinit进程后

只执行一次,init进程等待它结束才继续执行其它动作

Once

系统执行完wait进程后

只执行一次,init进程不等待它结束

Respawn

启动完once进程后

Init进程监测发现子进程退出时,重新启动它

Askfirst

启动完respawn进程后

respawn类似,不过init进程先输出“Please press Enter to actvie this console”,等用户输入驾车键之后才启动子进程

Shutdown

当系统关机时

即重启关闭系统命令时

Restart

Busybox中配置了CONFIG_FEATURE_USE_INITTAB,并且init进程接收到SIGHUP信号时

先重新读取,解析/etc/initab文件,再执行restart程序

Ctrlatldel

按下Ctr+Alt+del组合键时

 

3)下面是etc/fstab文件内容,表示执行完“mount -a”命令后将挂载proc tmpfs等系统
 
#device mount-point type option dump fsck order
proc /proc proc defaults 0 0
temps /tmp rpoc defaults 0 0
none /tmp ramfs defaults 0 0
sysfs /sys sysfs defaults 0 0
mdev /dev ramfs defaults 0 0

4)/etc/profile文件

这个文件是sh用的,当用户获得一个shell后,sh就会根据这个文件配置用户的登陆环境,下面是我的profile文件。

#Ash profile
#vim:syntax=sh
#No core file by defaults
#ulimit -S -c 0>/dev/null 2>&1
USER="id -un"
LOGNAME=$USER
PS1='[/u@/h=W]#'
PATH=$PATH
HOSTNAME='/bin/hostname'
export USER LOGNAME PS1 PATH

    其中PATH环境变量指定当用户键入一个命令时,sh寻找这个命令的路径。PS1指定sh提示符的格式。其它的export命令,alias命令,busybox里面的ashbash非常相似

    这样,Busybox所需的基本的配置文件就完成了。这也是Busybox最基本的文件,当然还可以配置更多的文件,增强Busybox的功能。
以下是另外一篇文章:
busybox 初始化分析

下载busybox 1.00 http://busybox.net/downloads/busybox-1.00.tar.bz2
# 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,ash_main怎样进入命令行,怎样接收命令,下次再分析了

 

参考资料:http://www.itqun.net/content-detail/237959_2.html