inittab脚本启动解析

本文深入剖析Linux系统从开机到用户登录的全过程,包括内核启动、init进程、inittab配置、rc脚本执行顺序及环境变量设置,揭示Linux系统启动背后的复杂机制。
  1. Linux 开机脚本启动顺序: 
    第一步:启动内核
    第二步:执行init (配置文件/etc/inittab)
    第三步:启动相应的脚本,执行inittab脚本,并且执行里面的脚本/etc/init.d rc.sysinit rc.d rc.local。。。
    第四步:启动login登录界面 login
    第五步:在用户登录的时候执行sh脚本的顺序:每次登录的时候都会完全执行的 /etc/profile /etc/bashrc /root/.bashrc /root/.bash_profile
        
    inittab脚本:
    init的进程号为1,是所有进程的父进程,内核初始化完毕之后,init程序开始运行。其他软件也同时开始运行。init程序通过/etc/inittab文件进行配置。
    /etc/inittab文件每一行包括四个字段:label:runlevel:action:process。详细解释如下。
    1.label
    登记项标志符,是一个任意指定的、4个字符以内的序列标号,在本文件内必须唯一。
    label是1到4个字符的标签,用来标示输入的值。一些系统只支持2个字符的标签。鉴于此原因,多数人都将标签字符的个数限制在2个以内。该标签可以是任意字符构成的字符串,但实际上,某些特定的标签是常用的,在Red Hat Linux中使用的标签是:
    id 用来定义缺省的init运行的级别
    si 是系统初始化的进程
    ln 其中的n从1~6,指明该进程可以使用的runlevel的级别
    ud 是升级进程
    ca 指明当按下Ctrl+Alt+Del是运行的进程
    pf 指当UPS表明断电时运行的进程
    pr 是在系统真正关闭之前,UPS发出电源恢复的信号时需要运行的进程
    x 是将系统转入X终端时需要运行的进程

    2.runlevels
    系统运行级,即执行登记项的init级别。用于指定相应的登记项适用于哪一个运行级,即在哪一个运行级中被处理。如果该字段为空,那么相应的登记项将适用于所有的运行级。在该字段中,可以同时指定一个或多个运行级,其中各运行级分别以数字0, 1, 2, 3, 4, 5, 6或字母a, b, c表示,且无须对其进行分隔。
    0-->Halt,关闭系统.
    1-->单用户,在grub启动时加上为kernel加上参数single即可进入此运行等级
    2-->无网络多用户模式.
    3-->有网络多用户模式.
    4-->有网络多用户模式.
    5-->X模式
    6-->reboot重启系统
    S/s-->同运行等级1
    a,b,c-->自定义等级,通常不使用.

    3.action
    表示进入对应的runlevel时,init应该运行process字段的命令的方式,有效的action值如下。
    boot:只有在引导过程中,才执行该进程,但不等待该进程的结束。当该进程死亡时,也不重新启动该进程。
    bootwait:只有在引导过程中,才执行该进程,并等待进程的结束。当该进程死亡时,也不重新启动该进程。实际上,只有在系统被引导后,并从单用户模式进入多用户模式时,这些登记项才被处理;如果系统的默认运行级设置为2(即多用户模式),那么这些登记项在系统引导后将马上被处理。
    initdefault:指定系统的默认运行级。系统启动时,init将首先查找该登记项,如果存在,init将依据此决定系统最初要进入的运行级。具体来说,init将指定登记项"run_level"字段中的最大数字(即最高运行级)为当前系统的默认运行级;如果该字段为空,那么将其解释为"0123456",并以"6"作为默认运行级。如果不存在该登记项,那么init将要求用户在系统启动时指定一个最初的运行级。
    off:如果相应的进程正在运行,那么就发出一个告警信号,等待20秒后,再通过关闭信号强行终止该进程。如果相应的进程并不存在,那么就忽略该登记项。
    once:启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程终止时,init也不重新启动该进程。在从一个运行级进入另一个运行级时,如果相应的进程仍然在运行,那么init就不重新启动该进程。
    ondemand:与"respawn"的功能完全相同,但只用于运行级为a、b或c的登记项。
    powerfail:只在init接收到电源失败信号时,才执行该进程,但不等待该进程结束。
    powerwait:只在init接收到电源失败信号时,才执行该进程,并在继续对/etc/inittab文件进行任何处理前等待该进程结束。
    respawn:如果相应的进程还不存在,那么init就启动该进程,同时不等待该进程的结束就继续扫描/etc/inittab文件;当该进程终止时,init将重新启动该进程。如果相应的进程已经存在,那么init将忽略该登记项并继续扫描/etc/inittab文件。
    sysinit:只有在启动或重新启动系统并首先进入单用户模式时,init才执行这些登记项。而在系统从运行级1~6进入单用户模式时,init并不执行这些登记项。"action"字段为"sysinit"的登记项在"run_level"字段不指定任何运行级。
    wait:启动进程并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
    ctrlaltdel:用户在控制台键盘上按下Ctrl+Alt+Del组合键时,允许init重新启动系统。注意,如果该系统放在一个公共场所,系统管理员可将Ctrl+Alt+Del组合键配置为其他行为,比如忽略等。

    4.process
    具体应该执行的命令。并负责在退出运行级时将其终止(当然在进入的runlevel中仍要运行的程序除外)。当运行级别改变,并且正在运行的程序并没有在新的运行级别中指定需要运行时,那么init会先发送一个SIGTERM 信号终止,然后是SIGKILL。

    5.实例分析:
    /*************************/etc/inittab***********************************/
    //将系统切换到 initdefault 操作所定义的运行级别即运行级别5。我们可以将运行级别看作是系统的状态。运行级别 0 定义了系统挂起状态,运行级别 1 是单用户模式。运行级别 2 到 5 是多用户状态,运行级别 6 表示重启
    id:5:initdefault:

    //sysinit表示在进行其他工作之前先完成系统初始化.init在处理其它运行等级的脚本之前,首先会执行这一行.是系统的初始化进程.用于设置主机名,挂载文件系统,启动交换分区等.
    //rcS脚本会调用/etc/rcS.d目录下的所有脚本进行初始化
    si::sysinit:/etc/init.d/rcS //在运行boot或bootwait进程之前运行系统初始化的进程

    //下条语句可以让系统在重新启动、进入单用户模式的时候提示输入超级用户密码。
    //S同运行等级1,并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
    ~~:S:wait:/sbin/sulogin 

    //当运行级别为5时,以5为参数运行/etc/rc5.d下的脚本,init将等待其返回(wait)
    //rc.sysinit,rcS,rc这些都是shell的脚本,完成大量的系统初始化的工作。
    //主要工作包括:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。
    //执行rc脚本,传入参数为0-6,即会调用/etc/rc0.d-rc6.d目录下的所有文件
    //initdefault 指定默认的 init 级别是 5(多用户模式)。在定义初始的运行级别之后,则调用rc脚本以及参数5(运行级别)来启动系统,即rc脚本(参数5)会调用/etc/rc5.d下的所有脚本。
    l0:0:wait:/etc/init.d/rc 0 //使用级别0运行此程序
    l1:1:wait:/etc/init.d/rc 1
    l2:2:wait:/etc/init.d/rc 2
    l3:3:wait:/etc/init.d/rc 3
    l4:4:wait:/etc/init.d/rc 4
    //会运行该/etc/rc5.d下的3个脚本:
    //S10telnetd脚本:开启telnetd服务 start-stop-daemon --start --quiet --exec $telnetd
    //S20syslog脚本:开启syslog服务start-stop-daemon -S -b -n syslogd -a /sbin/syslogd -- -n $SYSLOG_ARGS start-stop-daemon -S -b -n klogd -a /sbin/klogd -- -n
    //S99rmnologin脚本:删除/etc/nologin文件 rm -f /etc/nologin /etc/nologin.boot
    l5:5:wait:/etc/init.d/rc 5
    l6:6:wait:/etc/init.d/rc 6

    z6:6:respawn:/sbin/sulogin//脚本运行等级为6时才执行 

    //在2、3、4、5级别上以ttyX为参数执行/sbin/mingetty程序,打开ttyX终端用于用户登录,
    //如果进程退出则再次运行mingetty程序(respawn),所以登录出错时,接着登录
    //缺省波特率是115200
    S2:2345:respawn:/sbin/mingetty ttyS2 //修改了mingetty和login程序,系统就可以在自动登录了


    /*************************etc/init.d/rcS***********************************/
    //设置PATH,runlevel,prevlevel环境变量,并export
    PATH=/sbin:/bin:/usr/sbin:/usr/bin
    runlevel=S
    prevlevel=N
    umask 022 //缺省的文件权限
    export PATH runlevel prevlevel

    if [ -x /sbin/unconfigured.sh ]//检查/sbin/unconfigured.sh是否可执行
    then
      /sbin/unconfigured.sh//如果可执行就执行unconfigured.sh,我的根文件系统不存在此文件
    fi

    //执行default目录下的rcS,设置一些变量
    //即source /etc/default/rcS
    . /etc/default/rcS

    //trap可以使你在脚本中捕捉信号。该命令的一般形式为:trap name signal(s)
    //name是捕捉到信号以后所采取的一系列操作。实际应用中, name一般是一个专门来处理所捕捉信号的函数。
    //name需要用双引号( “ ” )引起来。signal就是待捕捉的信号。
    //这里就是捕捉INT QUIT TSTP三个信号,执行“:”,实际就是忽略这三个信号,防止脚本执行时使用ctrl-C 就退出脚本
    trap ":" INT QUIT TSTP

    //将执行/etc/init.d中rc,传入参数为“S”,目的就是为了执行/etc/init.d/rcS.d目录下的所有脚本文件,都是连接到/etc/init.d/目录下的链接
    //S02banner脚本建立tty设备节点:/bin/mknod -m 0666 /dev/tty c 5 0
    //S03sysfs脚本挂载proc和sysfs文件系统:mount -t proc proc /proc mount sysfs /sys -t sysfs
    //S03udev脚本:开启udev服务,后台运行udevd程序:/sbin/udevd -d
    //S06alignment脚本,输出cpu信息到proc文件系统
    //S10checkroot脚本
    //S20modutils.sh脚本:insmod module
    //S30ramdisk脚本:
    //S35mountall.sh脚本:挂载Mount all filesystems
    //S37populate-volatile.sh脚本
    //S38devpts.sh脚本:挂载mount -t devpts devpts /dev/pts
    //S39hostname.sh脚本:输出主机名称(arago)写入/etc/hostname文件:hostname -F /etc/hostname
    //S40networking脚本:开启网络服务
    //S45mountnfs.sh脚本:挂载nfs
    //S55bootmisc.sh脚本:
    //S98configure脚本:opkg-cl configure
    //S99finish.sh脚本:结束脚本
    exec /etc/init.d/rc S 

    //若rc.boot是目录,则执行rc.boot所有的脚本程序
    [ -d /etc/rc.boot ] && run-parts /etc/rc.boot

    //若setup.sh可执行,就执行,没有此程序
    if [ -x /sbin/setup.sh ]
    then
      /sbin/setup.sh
    fi


    /*************************etc/init.d/rc***********************************/
    //这个脚本作用主要是运行/etc/rcS.d目录下的文件,
    //其中在 /etc/rcS.d/ 的目录下有一个 README 文本来说明该 /etc/rcS.d/ 目录下脚本的作用:
    //即 /etc/rcS.d/ 中是一些到 /etc/init.d/ 中脚本的符号连接。
    //执行完 /etc/rcS.d/ 中的脚本后,触发相应的 runlevel 事件,开始运行 /etc/rc.conf 脚本
    . /etc/default/rcS
    export VERBOSE //etc/default/rcS这个脚本中定义的VERBOSE=no

    startup_progress() {
        step=$(($step + $step_change))
        if [ "$num_steps" != "0" ]; then
            progress=$((($step * $progress_size / $num_steps) + $first_step))
        else
            progress=$progress_size
        fi
        if type psplash-write >/dev/null 2>&1; then
            TMPDIR=/mnt/.psplash psplash-write "PROGRESS $progress" || true
        fi
    }


    startup() {
      [ "$VERBOSE" = very ] && echo "INIT: Running $@..."//VERBOSE=no,所以后边的不打印

        //以.sh结尾的脚本是必须执行的脚本,不是以.sh结尾的脚本服务是可以开启或关闭的,通过start或stop参数
      case "$1" in//传入的第一个参数是要执行的文件名,第二个参数是start
        *.sh)
            (//若文件名是以.sh结尾的则执行这个脚本
                trap - INT QUIT TSTP
                scriptname=$1
                shift
                . $scriptname //执行这个脚本,不带参数
            )
            ;;
        *)//若不是以.sh结尾的
            /*实际上rc进程调用的脚本都称为初始化脚本。每个在/etc/init.d下的脚本都可以在执行时带上以下参数,如:start、stop、restart、pause、zap、status、ineed、iuse、needsme、usesme或者broken。
            要启动、停止或者重启一个服务(和所有依赖于它的服务),应该用参数start、stop和restart。*/
            "$@"//执行这个脚本带参数,比如传入的是$@=“/etc/rcS.d/S02banner start”,即带start参数执行这个脚本,这样可以灵活的控制服务的start或者stop
            ;;
      esac
      startup_progress
    }

        //忽略这三个信号,防止脚本执行时使用ctrl-C 就退出脚本
      trap ":" INT QUIT TSTP
        
        //stty用于设置终端特性。在命令行中设置一个stty选项,一般格式为:stty name character
        //以下将退格设置为^ H:stty erase '\^H',即ctrl+H在此脚本中是退格键
      //设置终端,将 CR 字符映射为 NL 字符,避免阶梯效应
      stty onlcr 0>&1

      //Now find out what the current and what the previous runlevel are.
      runlevel=$RUNLEVEL
      //得到第一个参数是“S”,表示等级1,得到当前运行等级1,runlevel=S
      [ "$1" != "" ] && runlevel=$1
      if [ "$runlevel" = "" ]//运行等级为空的话,则退出
      then
        echo "Usage: $0 " >&2
        exit 1
      fi
      previous=$PREVLEVEL
      [ "$previous" = "" ] && previous=N

        //传入参数是S的话,则$runleve=S $previous=N
      export runlevel previous

      //若$runlevel=“S”,即检查rcS.d是否为目录。
      if [ -d /etc/rc$runlevel.d ]
      then
        //rcS.d是目录
        PROGRESS_STATE=0

        //Split the remaining portion of the progress bar into thirds
        progress_size=$(((100 - $PROGRESS_STATE) / 3))//progress_size = 100/3 =33

        case "$runlevel" in//runlevel=S
            0|6)
                first_step=-100
                progress_size=100
                step_change=1
                ;;
         S)
                //Begin where the initramfs left off and use 2/3of the remaining space
                first_step=$PROGRESS_STATE ///progress_size = 100/3 =33
                progress_size=$(($progress_size * 2))//progress_size=66
                step_change=1
                ;;
            *)
                //Begin where rcS left off and use the final 1/3 ofthe space (by leaving progress_size unchanged)
                first_step=$(($progress_size * 2 + $PROGRESS_STATE))
                step_change=1
                ;;
        esac

        num_steps=0
        for s in /etc/rc$runlevel.d/[SK]*; //s取/etc/rcS.d目录下以S或K开头的文件名
        do
            //这句话的含义去掉变量s中所有的/etc/rcS.d/S??的部分
            //例:s=/etc/rc$runlevel.d/S10checkroot,那么去掉/etc/rc$runlevel.d/K??部分后,s为checkroot
        case "${s##/etc/rc$runlevel.d/S??}" in
            gdm|xdm|kdm|reboot|halt)//若s剩下的文件名中为这五个则跳出for语句
                break
                ;;
        esac
        num_steps=$(($num_steps + 1))//num_steps递加,表示查找到此目录下/etc/rcS.d有多少个脚本
      done//for语句结束
      
      step=0
        //首先运行KILL脚本
        if [ $previous != N ]//由于$previous=N,所以以下不执行
        then
            for i in /etc/rc$runlevel.d/K[0-9][0-9]*//取以K开头的文件名
            do
                //检查是否为常规文件
                [ ! -f $i ] && continue
                //Stop the service.
                startup $i stop
            done
        fi

        //然后运行这个级别的START脚本
        for i in /etc/rc$runlevel.d/S*//取得S开头的脚本
        do
            [ ! -f $i ] && continue//检查是否为常规文件,不是则进行下次循环

            if [ $previous != N ] && [ $previous != S ]//由于$previous=N,所以此if语句不执行
            then
                //Find start script in previous runlevel and stop script in this runlevel.
                suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}//获得i文件名的后缀,假如是S10checkroot,则suffix=checkroot
                stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix //得到stop文件名,假如/etc/rc$runlevel.d/K[0-9][0-9]checkroot
                previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
                //如果有起始脚本,并且没有停止脚本,则不进行这项服务,continue继续下一次循环
                [ -f $previous_start ] && [ ! -f $stop ] && continue
            fi
            
            case "$runlevel" in//runlevel = S
                0|6)
                    startup $i stop
                    ;;
                *)
                    startup $i start//调用start函数,参数就是脚本名称
                    ;;
            esac
        done//for循环结束
        
      fi


    /*************************etc/profile***********************************/
    //为启动shell设定一些环境变量
    PATH="/usr/local/bin:/usr/bin:/bin" 
    EDITOR="/bin/vi"            //needed for packages like cron
    test -z "$TERM" && TERM="vt100"    //Basic terminal capab. For screen etc.

    //若此文件存在,则设置时区
    if [ ! -e /etc/localtime ]; then
        TZ="UTC"                
        export TZ
    fi

    //显示用户ID是否为0,是0则设置用户的PATH路径
    if [ "`id -u`" -eq 0 ]; then
       PATH=$PATH:/usr/local/sbin:/usr/sbin:/sbin:
    fi

    //设置环境变量PS1
    if [ "$PS1" ]; then
       PS1='\u@\h:\w\$ '
    fi

    // /etc/profile.d是否为目录
    if [ -d /etc/profile.d ]; then
      for i in /etc/profile.d/*.sh /*遍历目录下所有以.sh结尾的脚本文件,并执行*/
      do
        if [ -r $i ]; then
          . $i
        fi
      done
      unset i
    fi

    //可以设置登录后自动运行的APP
    . /etc/init.d/autorun-f    

    export PATH PS1 OPIEDIR QPEDIR QTDIR EDITOR TERM

    umask 022

转自:http://blog.chinaunix.net/uid-17188120-id-4073497.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值