Linux之/etc/inittab文件


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

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

       staticconstchar 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 constchar 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);
            } elseif(a->action_type& ONCE){

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


 

参考资料:

busybox的init

http://www.diybl.com/course/6_system/linux/Linuxjs/2008831/139163.html

 

 

Mandrake9.0的启动过程(从init开始)(二)

http://www.bitscn.com/pdb/oracle/200604/17287.html

在内核引导结束并启动/sbin/init之后,系统就转入用户态的运行。在这之后创建的一切进程,都是在用户态进行。这里先要讲清楚一个概念:就是init进程虽然是从内核开始的,即在前面所讲的init/main.c中的init()函数在启动后就已经是一个核心线程,但在转到执行/sbin/init之后,内核中的init()就变成了/sbin/init程序,状态也转变成了用户态,也就是说核心线程变成了一个普通的进程。这样一来,内核中的init函数实际上只是init进程的入口,它在执行execve("/sbin/init",argv_init,envp_init)时改变成为一个普通的用户进程。这就是exec系列函数替换进程映像的变身大法,只要你学过unix环境下的多进程编程,应该都能理解这一点。
  除此之外,它们的代码来源也有差别,内核中的init()函数的源代码在/init/main.c中,是内核的一部分。而/sbin/init程序的源代码是sysvinit软件包的一部分,可以从ftp://ftp.cistron.nl/pub/people/miq...程序,如:halt, init, killall5, last, lastb (链接至 last), mesg, pidof (链接至 killall5), poweroff (链接至 halt), reboot (链接至 halt), runlevel, shutdown, sulogin, telinit (链接至 init), utmpdump 和 wall等等。对于启动过程而言,这里最重要的就是init程序。它启动之后,要完成很多任务:检查文件系统,启动各种后台服务进程,最后为每个终端和虚拟控制台启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的,因此它又是其它一切用户进程的父进程。
  init进程启动后,就要按照/etc/inittab的内容进程系统设置。下面就是manfrake9.0的inittab内容:
  #
  # inittab This file describes how the INIT process should set up
  # the system in a certain run-level.
  #
  # Author: Miquel van Smoorenburg, 
  # Modified for RHS Linux by Marc Ewing and Donnie Barnes
  #
  # Default runlevel. The runlevels used by Mandrake Linux are:
  # 0 - halt (Do NOT set initdefault to this)
  # 1 - Single user mode
  # 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
  # 3 - Full multiuser mode
  # 4 - unused
  # 5 - X11
  # 6 - reboot (Do NOT set initdefault to this)
  #
  id:5:initdefault:
  # System initialization.
  si::sysinit:/etc/rc.d/rc.sysinit
  l0:0:wait:/etc/rc.d/rc 0
  l1:1:wait:/etc/rc.d/rc 1
  l2:2:wait:/etc/rc.d/rc 2
  l3:3:wait:/etc/rc.d/rc 3
  l4:4:wait:/etc/rc.d/rc 4
  l5:5:wait:/etc/rc.d/rc 5
  l6:6:wait:/etc/rc.d/rc 6
  # Things to run in every runlevel.
  ud:nce:/sbin/update
  # Trap CTRL-ALT-DELETE
  ca::ctrlaltdel:/sbin/shutdown -t3 -r now
  # When our UPS tells us power has failed, assume we have a few minutes
  # of power left. Schedule a shutdown for 2 minutes from now.
  # This does, of course, assume you have powerd installed and your
  # UPS connected and working correctly.
  pf:owerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
  # If power was restored before the shutdown kicked in, cancel it.
  pr:12345owerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
  # Run gettys in standard runlevels
  1:2345:respawn:/sbin/mingetty tty1
  2:2345:respawn:/sbin/mingetty tty2
  3:2345:respawn:/sbin/mingetty tty3
  4:2345:respawn:/sbin/mingetty tty4
  5:2345:respawn:/sbin/mingetty tty5
  6:2345:respawn:/sbin/mingetty tty6
  从中可以看到,inittab的每一行由四个字段组成。分别是:
  id:runlevels:actionrocess
  id:是每一行的标识符,长度一般为2个字符而且在整个inittab中必须唯一。对于getty或其他login程序项,要求id与tty的编号相同,否则getty程序将不能正常工作。
  runlevels:指定运行级别,一般使用0-6以及S或s。运行级别是指init进程的整个系统的一个运行状态,它定义了系统所提供的服务,常见的运行级别如下:0,系统终止;1,单用户模式;3没有网络文件系统支持的多用户模式;4,保留;5,启动到Xwindow;6,重新启动,S和s意义相同,表示单用户模式且无需inittab文件,因此也不必在inittab中出现,实际上,进入单用户模式时,init直接在控制台(/dev/console)上运行/sbin/sulogin。实际上,7-9的运行级别也是可以使用的,只是传统的Unix系统没有定义这几个级别。级别可以是并列的多个值,以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功才会执行。。
  action:指出的是init程序执行process命令的方式。initdefault是一个特殊的action值,用于标识缺省的运行级别。当init进程由核心启动后,它将读取inittab中的initdefault项,取得其中的运行级别,并作为当前的运行级别。如果没有inittab文件,或者其中没有initdefault项,init将在控制台上请求输入运行级别。respawn 在进入相应runlevel时执行,如果该进程结束,init会再起一个进程执行同样的命令。once在进入有runlevels指定的运行级别时运行并且只执行一次。wait的执行效果和once一样,但init会等待该命令结束。ctrlaltdel指定在用户按下Ctrl-Alt-Del时执行的命令。powerfail 在系统接收到掉电信号时启动相关进程。powerwait 和powerfail一样,在系统接收到掉电信号时启动相关进程,并等待进程终止,在进程终止前不做任何其它操作。off 若该项相关进程已经存在则强行终止,否则忽略。sysinit、boot、bootwait等action将在系统启动时无条件运行,而忽略其中的运行级别,其中sysinit 指定需要运行的第一个程序(或脚本),boot将在sysinit之后执行,bootwait的执行效果和boot一样,但init会等待该命令结束,其余的action(不含initdefault)都与某个运行级别相关,可以在inittab的man手册中找到各个action的定义的详细描述。
  process:给出每行要执行的命令。
  知道inittab的字段意义以后,我们来看一下inittab的结构。inittab的第一行是设定系统的运行级别,如下所示:
  id:5:initdefault:
  从以上的介绍中可以知道,系统的运行级别是5,也就是系统启动时自动进入xwindow。接着进行系统初始化:
  si::sysinit:/etc/rc.d/rc.sysinit
  这样init进程就会执行/etc/rc.d/rc.sysinit。这个脚本最常见的动作就是激活交换分区,检查磁盘,加载硬件模块,这些动作无论哪个运行级别都是需要优先执行的。只有在rc.sysinit执行完以后init进程才会执行其他的boot或bootwait动作。如果没有boot或bootwait动作,init进程就接着执行下面的内容:
  l0:0:wait:/etc/rc.d/rc 0
  l1:1:wait:/etc/rc.d/rc 1
  l2:2:wait:/etc/rc.d/rc 2
  l3:3:wait:/etc/rc.d/rc 3
  l4:4:wait:/etc/rc.d/rc 4
  l5:5:wait:/etc/rc.d/rc 5
  l6:6:wait:/etc/rc.d/rc 6
  这里,系统将会根据运行级别选择要执行的命令。从前面我们已经知道系统的运行级别是5,所以实际上只有这一行被执行了:
  l5:5:wait:/etc/rc.d/rc 5
  这一行的最后是以参数5运行/etc/rc.d/rc,实际上就是运行/etc/rc.d/rc3.d下的所有程序(或脚本)。用ls命令查看/etc/rc.d/rc3.d目录,如下:
  [kj501@c4 kj501]$ ls /etc/rc.d/rc3.d/
  K09dm@ S16ypserv@ S56rawdevices@ S90crond@
  K55routed@ S17alsa@ S56xinetd@ S90postgresql@
  K90mysql@ S18sound@ S60cups@ S91smb@
  S01usb@ S20random@ S60nfs@ S92lisa@
  S03iptables@ S20xfs@ S60rwhod@ S95innd@
  S05harddrake@ S25netfs@ S66yppasswdd@ S95kheader@
  S10network@ S26apmd@ S75keytable@ S99devfsd@
  S11portmap@ S26ypxfrd@ S80postfix@ S99hamboot@
  S12syslog@ S40atd@ S85httpd@ S99linuxconf@
  S13partmon@ S40saslauthd@ S85numlock@ S99local@
  S14nfslock@ S55named@ S85proftpd@
  S15gpm@ S55sshd@ S89internet@
  这些文件都是一些系统服务程序,以“S”打头的文件用来在进入运行级别5时启动服务进程,以“K”打头的文件用来在系统关闭,离开运行级别5之前终止服务进程。后面的数字大小决定执行的先后次序,对于以”K“打头的服务进程,按照从高到低的顺序执行,对于以”S“打头的服务进程,按照从低到高的顺序执行。如果用ls -l 查看这些文件,就会发现它们大部分都是到/etc/rc.d/init.d目录下各个shell脚本的符号链接,而且这些脚本一般能接受start、stop、restart、status等参数,在启动时以start参数启动以“S”打头的脚本链接,如果存在一个同样的脚本链接但是以”K“打头,也会先执行以”K“打头的脚本链接,然后再执行以”S“打头的脚本链接,以保证这个服务进程是重新启动的。要注意在这些脚本的最后,都有一个S99local@的链接,这个链接是链接到/etc/rc.d/rc.local,而不是象其它脚本链接一样链接到/etc/rc.d/init.d下。可以在这个rc.local加入一些每个运行级别都要执行但又必须在系统服务进程启动后才能执行的命令,比如说对一些系统服务程序的参数设置。执行这些脚本之后,下面就要执行一个在每个运行级别都要执行的命令:
  ud:nce:/sbin/update
  这句话表明每一个运行级别都要运行命令update,此程序每隔30秒把内存缓冲区的内容回写一次,称为"同步",以防止系统崩溃或突然掉电造成的数据丢失和损坏

Logo

更多推荐