构建根文件系统2----init进程分析

时间:2021-08-01 16:30:52

转自http://www.linuxidc.com/Linux/2011-12/50292p3.htm

我们通常使用Busybox来构建根文件系统的必要的应用程序。Busybox通过传入的参数来决定执行何种操作。当init进程启动时,实际上调用的是Busybox的init_main()函数,下面我们来分析这个函数,看init进程究竟是怎样一个流程。我分析的Busybox源码是1.7.0版本的,其他版本会略有不同。部分代码省略我们只看关键性代码。


首先看init_main函数

  1. int init_main(int argc, char **argv);  
  2. int init_main(int argc, char **argv)  
  3. {  
  4.     ……………………………..  
  5.     ……………………………..  
  6.     //初始化控制台  
  7.     console_init();  
  8.     ………………………………  
  9.   
  10.     if (argc > 1  
  11.      && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))  
  12.     ) {  
  13.         new_init_action(RESPAWN, bb_default_login_shell, "");  
  14.     } else {  
  15.         //因为我们启动的init进程没有任何参数,所有argc==1,执行的是这一句  
  16.         parse_inittab();  
  17.     }  
  18.     …………………………………………  
  19.     …………………………………………  
  20.     run_actions(SYSINIT);   //运行inittab配置文件中acthion为SYSINIT的进程  
  21.     run_actions(WAIT);  //运行inittab配置文件中action为WAIT的进程  
  22.   
  23.   
  24.     run_actions(ONCE);  //运行inittba配置文件中action为ONCE的进程  
  25.     ………………………………………………  
  26.     while (1) {  
  27.         /*  
  28.         运行inittab配置文件中action为RESPAWN和ASKFIRST的进程,一旦退出则重新启动  
  29.         */  
  30.         run_actions(RESPAWN);     
  31.         run_actions(ASKFIRST);  
  32.   
  33.         wpid = wait(NULL);  
  34.         while (wpid > 0) {  
  35.                     a->pid = 0;  
  36.             }  
  37.             wpid = waitpid(-1, NULL, WNOHANG);  
  38.           
  39.     }  
  40. }  

parse_inittab实际上对/etc/inittab文件里面的配置进行解释,如果没有,则设置一些默认设置。

我们先来看看这个inittab这个文件里面的配置格式,这个在busybox文件里面的inittab文件里面有说明

<id>:<runlevels>:<action>:<process>

id表示输出输入设备,这个不需要设置,因为/etc/console已经设为标准输入输出了,如不设置,则从控制台输入输出。

runlevels 这个参数完全忽略

action 运行时机,它表示inittab解释后的运行顺序,它有sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, andshutdown.这个值可选择。

process 就是要启动的进程。

 

下面来看prase_inittab这个函数

  1. static void parse_inittab(void)  
  2. {  
  3. …………………………………………………  
  4. …………………………………………………  
  5.   
  6.     /*INITTAB是一个宏 #define INITTAB      "/etc/inittab"     
  7.     可以看得出来它打开了/etc/inittab这个文件*/  
  8.   
  9.     file = fopen(INITTAB, "r");  
  10.   
  11.     //如果没有这个文件,则调用new_init_action进行一些默认的操作  
  12.     if (file == NULL) {  
  13.         new_init_action(CTRLALTDEL, "reboot", "");  
  14.         new_init_action(SHUTDOWN, "umount -a -r", "");  
  15.         if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");  
  16.         new_init_action(RESTART, "init", "");  
  17.         new_init_action(ASKFIRST, bb_default_login_shell, "");  
  18.         new_init_action(ASKFIRST, bb_default_login_shell, VC_2);  
  19.         new_init_action(ASKFIRST, bb_default_login_shell, VC_3);  
  20.         new_init_action(ASKFIRST, bb_default_login_shell, VC_4);                         new_init_action(SYSINIT, INIT_SCRIPT, "");  
  21.   
  22.         return;  
  23.     }  
  24.     …………………………………………………  
  25. …………………………………………………  
  26.         /*果inittab文件里面有内容就将里面的内容一行一行读出来,然后调用new_init_action进行操作*/  
  27.     while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {  
  28.         /* Ok, now process it */  
  29.         for (a = actions; a->name != 0; a++) {  
  30.             if (strcmp(a->name, action) == 0) {  
  31.                 if (*id != '\0') {  
  32.                     if (strncmp(id, "/dev/", 5) == 0)  
  33.                         id += 5;  
  34.                     strcpy(tmpConsole, "/dev/");  
  35.                     safe_strncpy(tmpConsole + 5, id,  
  36.                         sizeof(tmpConsole) - 5);  
  37.                     id = tmpConsole;  
  38.                 }  
  39.                 new_init_action(a->action, command, id);  
  40.                 break;  
  41.             }  
  42.         }  
  43.     …………………………………………………  
  44. …………………………………………………  
  45.     }  
  46.     fclose(file);  
  47. }  

这个new_init_action函数,它实际上是将inittab里面的action相同的操作串成一个链表。

下面我们再来分析init_main执行prase_inittab之后执行的操作

可以看出init_main执行prase_initab对inittab文件里面的配置进行解释之后,会先执行运行时机为SYSINIT的进程,让执行WAIT时机的,接着是ONCE的,然后在一个while(1)函数里面运行RESPAWN和ASKFIRST时机的,一旦这两个时机里面的进程被杀死,就会把他们的pid赋为0,然后跳到while(1)函数的开始处又去启动他们。所有说运行时机为RESPAWN和ASKFIRST的进程永远无法杀死,除非reboot或者shutdown。 

下面我们来总结一下init进程的启动过程
1.初始化控制台
2.解释inittab
3.执行inittab运行时机为SYSINIT的进程
4.执行inittab运行时机为WAIT的进程
5.执行inittab运行时机为ONCE的进程
6.执行inittab运行时机为RESPAWN和ASKFRIST的进程,有退出的则重新执行。



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

如果根文件系统中没有/etc/inittab文件,Busybox init程序使用默认的inittab条目:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
::restart:/sbin/init

/etc/inittab实例可参考Busybox的examples/inittab文件。