Busybox init进程启动过程 (资料收集)

 

busybox的init


busybox中的init程序可以完成初始化的基本功能而且去掉了System V系统init程序的多余功能,init程序执行的顺序如下:

  <1>.创建init的信号处理
  <2>.初始化控制台
  <3>.分析inittab文件,/etc/inittab
  <4>.执行系统初始化脚本,/etc/init.d/rcS
  <5>.执行所有inittab中类型为wait的程序
  <6>.执行所有inittab中类型为once的程序
    完成以上步骤后,init循环执行所有inittab中类型为respawn和askfirst的程序。文件inittab中的每一行配置遵照格式如下:
    id:runlevel:action:process
    虽然这个格式与传统的System V init 类似,但是id 域的含义不同。id 通常是用来指定控制启动进程运行的tty。如果id域为空则代表这个进程的运行不需要与shell交互。在busybox中,runlevel域 可以完全忽略。process 指明执行程序以及所带参数选项。 action 域可以选择一下八个类型:
    sysinit提供给init初始化脚本所在的路径;
    respawn当进程终止后将其重新启动;
    askfirst与respawn类似,不同之处是在终端提示“Please press Enter to activate this console.”;
    wait代表init等待这个程序执行完后才继续执行;
    once表示执行一次而且不需要等待完成;
    ctrlaltdel表示当按下Ctrl-Alt-Delete时执行;
    shutdown表示程序在关机时执行;
    restart表示程序在重启时执行,通常是init本身。
  
  
  
  askfirst改成respawn就得. 
  
  ttyS0上没shell,想要的话在/etc/inittab中加: 
  
  ttyS0::respawn:-/bin/sh 
  
  
  BusyBox - The Swiss Army Knife of Embedded Linux 
  Prev Chapter 3. BusyBox Commands Next 
  
  --------------------------------------------------------------------------------
  
  init
  Usage: init 
  
  Init is the parent of all processes. 
  
  This version of init is designed to be run only by the kernel. 
  
  BusyBox init doesn't support multiple runlevels. The runlevels field of the /etc/inittab file is completely ignored by BusyBox init. If you want runlevels, use sysvinit. 
  
  BusyBox init works just fine without an inittab. If no inittab is found, it has the following default behavior: 
  
   ::sysinit:/etc/init.d/rcS
   ::askfirst:/bin/sh
  
   
  
  
  If it detects that /dev/console is _not_ a serial console, it will also run: 
  
   tty2::askfirst:/bin/sh
  
   
  
  
  If you choose to use an /etc/inittab file, the inittab entry format is as follows: 
  
  :::
  
   
  
  
  id
  WARNING: This field has a non-traditional meaning for BusyBox init! The id field is used by BusyBox init to specify the controlling tty for the specified process to run on. The contents of this field are appended to "/dev/" and used as-is. There is no need for this field to be unique, although if it isn't you may have strange results. If this field is left blank, the controlling tty is set to the console. Also note that if BusyBox detects that a serial console is in use, then only entries whose controlling tty is either the serial console or /dev/null will be run. BusyBox init does nothing with utmp. We don't need no stinkin' utmp. 
  
  runlevels
  The runlevels field is completely ignored. 
  
  action
  Valid actions include: sysinit, respawn, askfirst, wait, once, and ctrlaltdel. 
  
  The available actions can be classified into two groups: actions that are run only once, and actions that are re-run when the specified process exits. 
  
  Run only-once actions: 
  
  'sysinit' is the first item run on boot. init waits until all sysinit actions are completed before continuing. Following the completion of all sysinit actions, all 'wait' actions are run. 'wait' actions, like 'sysinit' actions, cause init to wait until the specified task completes. 'once' actions are asyncronous, therefore, init does not wait for them to complete. 'ctrlaltdel' actions are run immediately before init causes the system to reboot (unmounting filesystems with a 'ctrlaltdel' action is a very good idea). 
  
  Run repeatedly actions: 
  
  'respawn' actions are run after the 'once' actions. When a process started with a 'respawn' action exits, init automatically restarts it. Unlike sysvinit, BusyBox init does not stop processes from respawning out of control. The 'askfirst' actions acts just like respawn, except that before running the specified process it displays the line "Please press Enter to activate this console." and then waits for the user to press enter before starting the specified process. 
  
  Unrecognized actions (like initdefault) will cause init to emit an error message, and then go along with its business. All actions are run in the reverse order from how they appear in /etc/inittab. 
  
  process
  Specifies the process to be executed and its command line. 
  
  Example /etc/inittab file
   # This is run first except when booting in single-user mode.
   #
   ::sysinit:/etc/init.d/rcS
  
   # /bin/sh invocations on selected ttys
   #
   # Start an "askfirst" shell on the console (whatever that may be)
   ::askfirst:-/bin/sh
   # Start an "askfirst" shell on /dev/tty2-4
   tty2::askfirst:-/bin/sh
   tty2::askfirst:-/bin/sh
   tty2::askfirst:-/bin/sh
  
   # /sbin/getty invocations for selected ttys
   #87501810
   tty4::respawn:/sbin/getty 38400 tty5
   tty5::respawn:/sbin/getty 38400 tty6
  
   # Example of how to put a getty on a serial line (for a terminal)
   #
   #::respawn:/sbin/getty -L ttyS0 9600 vt100
   #::respawn:/sbin/getty -L ttyS1 9600 vt100
   #
   # Example how to put a getty on a modem line.
   #::respawn:/sbin/getty 57600 ttyS2
  
   # Stuff to do before rebooting
   ::ctrlaltdel:/bin/umount -a -r
   ::ctrlaltdel:/sbin/swapoff
  
   
  mknod: /dev/ptyp1: File exists

  mknod: /dev/ttyp1: File exists



后台程序没有启动或fork进程自动退出---busybox init启动思考

我的busybox inittab有如下行:
ttyS1::sysinit:/etc/init.d/rcS
ttyS1::respawn:-/bin/sh

然后在rcS调用的启动进程脚本中,有文件S50update.sh:
upgrade_osd &

然后我发现启动过程中,upgrade_osd根本没有启动。
我到upgrade_osd的main中,fork全部过程,然后在S50update.sh:
upgrade_osd

然后发现启动了,但是又退出了,尽管我的upgrade_osd设计是不退出的。

后来了解busybox init的启动,尤其是inittab的描述,理解如下:
ttyS1::sysinit:/etc/init.d/rcS (其中sysinit表示启动是调用)
之后调用ttyS1::respawn:-/bin/sh
不是kill了之前的shell然后重新启动一个。

所以修改如下:
::sysinit:/etc/init.d/rcS
ttyS1::respawn:-/bin/sh


重要参考:
  如果存在/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/init.d/rcS内容分析(1)

     转自:    http://www.360doc.com/content/11/0926/14/7473909_151344097.shtml


由于init=/linuxrc,因此,在文件系统挂载后,运行的第一个程序就是根目录下的linuxrc,而这是一个指向/bin/busybox 的链接,也就是说,系统起来后运行的第一个程序就是busybox本身。                 

        busybox首先将试图解析/etc/inittab来获取进一步的初始化配置信息(参考busybox源代码init/init.c中的parse_inittab()函数)。而事实上,root_qtopia中并没有/etc/inittab这个配置文件,根据busybox的裸机,它将生成默认的配置。其中最重要的一个,就是new_init_action(SYSINIT,INIT_SCRIPT,""),也就决定了接下来初始化的脚本是INIT_SCRIPT所定义的值,这个宏的默认值是"etc/init.d/rcS"。

        下面是文件系统中/etc/init.d/rcS的内容,也是我们要分析的重点

1.PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:

2.runlevel=S

3.prevlevel=N

4.umask 022

5.export PATH runlevel prevlevel

##为启动环境设置必要的环境变量;

1./bin/hostname FriendlyARM

##设置机器名字;

1./bin/mount -n -t proc none /proc

2./bin/mount -n -t sysfs none /sys

3./bin/mount -n -t usbfs none /proc/bus/usb

4./bin/mount -t ramfs none /dev

##挂载"虚拟"文件系统"/proc"和"/sys",并且在/dev目录下挂载一个ramfs,相当于把原本nandflash上的只读的/dev目录"覆盖"上一块可写的空的SDRAM。

##这里要注意的是,/sys和挂载了ramfs的/dev是正确创建设备节点的关键。对于2.6.29内核来说,已经没有devfs的支持,创建设备节点只有通过两种办法由文件系统完成:

1)制作文件系统镜像前用mknod手动创建好系统所有的(包括可能有的)设备节点,并把这些节点文件一起做进文件系统镜像中;

2)在文件系统初始化过程中,通过/sys目录所输出的信息,在/dev目录下动态的创建系统中当前实际有的设备节点。

        显然,方法1)有很大的局限性,仅限于没有设备动态增加或减少的情况,不适用于很多设备热插拔的情况,比如U盘,SD卡等等。方法2)是目前大多数PC上的linux的做法(基于udev实现)。这种方法有2个前提:/sys目录挂载和一个可写的/dev目录。这也就是为什么我们这里需要挂载/sys和ramfs在/dev目录上,事实上,这种方法最早就是为热插拔设计的,你可以理解为当系统启动时,所有设备一下子全部"插入"了进来。

        这里有一点要说明的是,在文件系统初始化跑到这里之前,原来的/dev目录下必须有一个设备节点:/dev/console。

        其实,要搞清楚"程序"这种东西,没有什么好的办法,无非2个东西,源码和脚本

1.echo /sbin/mdev> /proc/sys/kernel/hotplug

2./sbin/mdev -s

3./bin/hotplug

##这几个就是用来完成上面所说的两个东西:1)通过mdev -s在/dev目录下创建必要的设备节点;2)设置内核的hotplug handler 为mdev,即当设备热插拔时,由mdev接受来自内核的消息并作出相应的回应,比如挂载U盘。

        对于mdev,需要注意的是,文件系统里存在/etc/mdev.conf文件,它包含了medv的配置信息。通过这个文件,我们可以自定义一些设备节点的名称或链接来满足特定的需要。这是root qtopia中mdev.conf的内容:

1.#system all-writable devices

2.full 0:0 0666

3.null 0:0 0666

4.ptmx 0:0 0666

5.random 0:0 0666

6.tty 0:0 0666

7.zero 0:0 0666

8.

9.#console devices

10.tty[0-9]* 0:5 0660

11vc/[0-9]* 0:5 0660

12.     
13.   # serial port devices   
14.   s3c2410_serial0    0:5    0666    =ttySAC0   
15.   s3c2410_serial1    0:5    0666    =ttySAC1   
16.   s3c2410_serial2    0:5    0666    =ttySAC2   
17.   s3c2410_serial3    0:5    0666    =ttySAC3   
18.                                                                                                                                                          19.   # loop devices   
20.   loop[0-9]*    0:0    0660    =loop/   
21.     
22.   # i2c devices   
23.   i2c-0        0:0    0666    =i2c/0   
24.   i2c-1        0:0    0666    =i2c/1   
25.     
26.   # frame buffer devices   
27.   fb[0-9]        0:0    0666   
28.     
29.   # input devices   
30.   mice        0:0    0660    =input/   
31.   mouse.*        0:0    0660    =input/   
32.   event.*        0:0    0660    =input/   
33.   ts.*        0:0    0660    =input/   
34.     
35.   # rtc devices   
36.   rtc0        0:0    0644    >rtc   
37.   rtc[1-9]    0:0    0644   
38.     
39.   # misc devices    

40.   mmcblk0p1    0:0    0600    =sdcard */bin/hotplug   
41.   sda1        0:0    0600    =udisk * /bin/hotplug

可以看到,原本串口驱动注册的设备名是 s3c2410_serial0, s3c2410_serial1 和
s3c2410_serial2,而 mdev 则会在/dev 目录下对应生成 ttySAC0, ttySAC1和ttySAC2以符合
应用程序对于串口设备名称的习惯。同样的,/dev/sdcard和/dev/udisk 永远分别指向 SD 卡和
U盘的第一个分区。(所以,用那些没有分区表的SD卡或U盘的兄弟知道原因了吧...)


BusyBox init及其inittab文件分析(转)



   转自:   http://www.xnmp.net/forum.php?mod=viewthread&tid=375


由于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

       static const 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 const 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" 
        ; 
        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) ; 
            } else if ( a- > action_type & ONCE) {

              /*action_type为ONCE的init_action,init则不会等待它执行完,并且将该节点从init_action_list中删除*/ 
                run( a) ; 
                delete_init_action( a) ; 
            } else if ( 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 the sysinit command */ 
    run_actions( SYSINIT) ; 

    /* Next run anything that wants to block */ 
    run_actions( WAIT) ; 

    /* Next run anything to be run only once */ 
    run_actions( ONCE) ; 

    /* Now run the looping stuff for the rest of forever */ 
    while ( 1) { 
        /* run the respawn/askfirst stuff */ 
        run_actions( RESPAWN | ASKFIRST) ; 

        /* Don't consume all CPU time -- sleep a bit */ 
        sleep ( 1) ; 

        /* Wait for any child process to exit */ 
        wpid = wait( NULL ) ; 
        while ( wpid > 0) { 
            /* Find out who died and clean up their corpse */ 
            for ( a = init_action_list; a; a = a- > next) { 
                if ( a- > pid = = wpid) { 
                     /* Set the pid to 0 so that the process gets
                     * restarted by run_actions() */ 
                    a- > pid = 0; 
                    message( L_LOG, "process '%s' (pid %d) exited. " 
                            "Scheduling for restart." , 
                            a- > command, wpid) ; 
                } 
            } 
            /* see if anyone else is waiting to be reaped */ 
            wpid = wait_any_nohang( NULL ) ; 
        } 
    } 




要写自己的inittab,需要理解busybox的inittab文件格式。 
busybox的inittab文件与通常的inittab不同,它没有runlevel的概念,语句功能上也有限制。inittab语句的标准格式是 
<id>:<runlevels>:<action>:<process> 各字段的含义如下 
<id>: id字段与通常的inittab中的含义不同,它代表的是这个语句中process执行所在的tty设备,内容就是/dev目录中tty设备的文件名。由于是运行process的tty设备的文件名,所以也不能象通常的inittab那样要求每条语句id的值唯一。 
<runlevels>: busybox不支持runlevel,所以此字段完全被忽略。 
<action>: 为下列这些值之一: 
sysinit, respawn, askfirst, wait,once, restart, ctrlaltdel, shutdown 
其 含义与通常的inittab的定义相同。特别提一下askfirst,它的含义与respawn相同,只是在运行process前,会打出一句话 “please press Enter to active this console”,然后等用户在终端上敲入回车键后才运行process。 
<process>: 指定要运行的process的命令行。


busybox init进程分析


转自: http://www.haogongju.net/art/1405286


本人喜欢用代码+伪代码的方式写笔记。文中的花括号可能是方便记录而已。

如:

hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module)
{
    问:怎么获得模块信息的?
    答:hardware\libhardware\Hardware.c

    ...........

}

原创分析, 转载请注明出处:http://www.cnblogs.com/langlang/

作者emaildayhappyhappy@163.com

 

busybox- 1.18. 5 分析
文件  init.c
int init_main( int argc UNUSED_PARAM,  char **argv)
{
     // 填充链表init_action_list
    parse_inittab();
    原型:new_init_action(uint8_t action_type,  const  char * command,  const  char *cons)
    {
         // 读取文件 /etc/inittab
        parser_t *parser = config_open2( " /etc/inittab ", fopen_for_read);
         // 假如 /etc/inittab不存在
         if (parser == NULL)
        {
            new_init_action(CTRLALTDEL,  " reboot """);
            原型 new_init_action(uint8_t action_type,  const  char *command,  const  char *cons)
            {
                 struct init_action *a, **nextp;
                {
                    注 :  struct init_action {
                                 struct init_action *next;       // 链表指针
                                pid_t pid;                     // 线程ID
                                uint8_t action_type;         //  action的类型  ①
                                 char terminal[CONSOLE_NAME_SIZE];    // 运行该command的终端  (*cons)
                                 char command[COMMAND_SIZE];          // command字段用来指定要执行命令(含路径),包括命令行选项。
                            };
                }
                nextp = &init_action_list;
                
                假如init_action_list不为空
                {
                    比较查找
                }
                假如为空
                {
                    a = xzalloc( sizeof(*a));
                }
                a->action_type = action_type;
                safe_strncpy(a->command, command,  sizeof(a->command));
                safe_strncpy(a->terminal, cons,  sizeof(a->terminal));
            }
            ..........
        }
         else
        {
             // 分析
            new_init_action( 1 << action, token[ 3], tty);
        }
    }

     /*  Now run everything that needs to be run  */
     /*  First run the sysinit command  */
    run_actions(SYSINIT);
    原型: void run_actions( int action_type)
    {
         struct init_action *a;
         for (a = init_action_list; a; a = a->next) {
             if (!(a->action_type & action_type))
                 continue// 继续查找

             if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
                pid_t pid = run(a);  // 执行
                {
                    创建进程
                     // ASKFIRST类型需要等待回车
                     if (BB_MMU && (a->action_type & ASKFIRST)) {
                         static  const  char press_enter[] ALIGN1 =
                             " \nPlease press Enter to activate this console.  ";
                         char c;
                         // 输出
                        dbg_message(L_LOG,  " waiting for enter to start '%s' "
                         " (pid %d, tty '%s')\n ",a->command, getpid(), a->terminal);
                        full_write(STDOUT_FILENO, press_enter,  sizeof(press_enter) -  1);
                         while (safe_read(STDIN_FILENO, &c,  1) ==  1 && c !=  ' \n ')
                             continue// 等待回车
                    }
                }
                 if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
                    waitfor(pid);  // 待它的command执行完,再继续执行。
            }
             if (a->action_type & (RESPAWN | ASKFIRST)) {
                 /*  Only run stuff with pid == 0. If pid != 0,
                * it is already running
                
*/
                 if (a->pid ==  0)
                    a->pid = run(a);  // init则不会等待它执行完
            }
        }
    }
    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);
     while ( 1) {
         /*  (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();
    
    }
}

① action_type
/*  为init提供初始化命令脚本的路径  */
#define SYSINIT     0x01
/*  告诉init必须等到相应的进程执行完成之后才能继续执行  */
#define WAIT        0x02
/*  仅执行相应的进程一次,而且不会等待它执行完成  */
#define ONCE        0x04
/*  每当相应的进程终止执行时,重新启动该进程  */
#define RESPAWN     0x08
/*   类似respawn,主要用途是减少系统上执行的终端应用程序的数量。
它将会促使init在控制台上显示
“Please press Enter to active this console”的信息,
并在重新启动进程之前等待用户按下“enter”键  
*/
#define ASKFIRST    0x10
/*  当按下Ctrl+Alt+Delete组合键时,执行相应的进程  */
#define CTRLALTDEL  0x20
/*  当系统关机时,执行相应的进程   */
#define SHUTDOWN    0x40
/*  当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身   */
#define RESTART     0x80 


分析busybox中init程序的运行过程




移植uboot的目的是启动内核,启动内核的目的是运行应用程序,从内核的启动流程中可以知道内核启动的第一个应用程序就是busybox里的/sbin/init进程!
但是我们的最终目的不是启动init进程,而是运行客户的程序!
那么init进程是如何选择性的运行客户的程序呢?我们猜测init进程肯定需要:
(1) 读取一个配置文件
(2) 解析该配置文件
(3) 根据配置文件执行客户的程序

下面我们来阅读busybox中init程序的源码,在init.c中的init_main()中:
1.首先是设置信号

  1.      signal(SIGHUP, exec_signal);
  2.     signal(SIGQUIT, exec_signal);
  3.     signal(SIGUSR1, shutdown_signal);
  4.     signal(SIGUSR2, shutdown_signal);
  5.     signal(SIGINT, ctrlaltdel_signal);
  6.     signal(SIGTERM, shutdown_signal);
  7.     signal(SIGCONT, cont_handler);
  8.     signal(SIGSTOP, stop_handler);
  9.     signal(SIGTSTP, stop_handler);
复制代码


2.初始化/dev/console
  1. console_init();
复制代码


3.解析配置文件

  1. if (argc > 1
  2.      && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
  3.     ) {
  4.     } else {
  5.         parse_inittab();
  6.     }
复制代码


内核启动/sbin/init是没有传如何参数,所以进入parse_inittab()函数,
我们进入到该函数:

  1. file = fopen(INITTAB, "r");
  2. #define INITTAB      "/etc/inittab"
复制代码

由此可以知道init进程读取的配置文件就是/etc/inittab,busybox中的inittab文件中规定了/etc/inittab内容的填写格式如下:
<id>:<runlevel>:<action>:<process>
Id:id会加上一个/dev前缀作为一个控制终端(stdin,stdout,stderr)
Runlevel:忽略
Action:执行的时机,包括SYSINIT,WAIT,ONCE, RESPAWN,ASKFIRST等
Process:要执行的应用程序或者脚本
继续分析parse_inittab():
  1.     if (file == NULL) 
  2. {
  3.     new_init_action(CTRLALTDEL, "reboot", "");                new_init_action(SHUTDOWN, "umount -a -r", "");
  4.     new_init_action(RESTART, "init", "");
  5.     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);
  6.     new_init_action(SYSINIT, INIT_SCRIPT, "");
  7.     return;
  8. #if ENABLE_FEATURE_USE_INITTAB
  9. }
复制代码


如果配置文件/etc/inittab不存在的话则执行if语句,也就是说如果没/etc/inittab的话init进程会直接调用new_init_action来构造默认配置项,根据if语句里的内容,我们可以反推出等效的/etc/inittab的内容如下:
::CTRLALTDEL:reboot
::SHUTDOWN:umount -a –r
::RESTART:init
::ASKFIRST:-/bin/ah
tty2:: ASKFIRST:-/bin/sh
tty3:: ASKFIRST:-/bin/sh
tty4:: ASKFIRST:-/bin/sh
::SYSINIT:/etc/init.d/rcS

继续分析parse_inittab():
后面就是对配置文件/etc/inittab里的内容里的解析了,如#则视作注释等等,最后就调用new_init_action(a->action, command, id);将/etc/inittab里的每一条配置项做成一个init_action结构体并添加到具有相同执行时机的init_action_list中去。说到底解析/etc/inittab这个配置文件就是为了把各配置项添加到对应的init_action_list中去。
这样parse_inittab()这个函数就结束了,我们继续看init_main()这个函数:
4.开始运行parse_inittab()帮我们添加到init_action_list中的程序或脚本:

   
  1. run_actions(SYSINIT);
  2.     run_actions(WAIT);
  3.     run_actions(ONCE);
  4. while (1) 
  5. {
  6. run_actions(RESPAWN);
  7. run_actions(ASKFIRST);
  8. }
复制代码

run_actions为运行一类程序或脚本,这里的一类就是按照执行时机来分类的。由上面这段代码我们就可以看出执行时机的优先级了:SYSINIT> WAIT> ONCE> RESPAWN> ASKFIRST,具体的可以继续分析源码



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值