Linux 启动过程(三)

内核被加载后,第一个运行的程序是 /sbin/initUbuntu 下该程序读取 /etc/init/rc-sysinit.conf 文件,依据此文件来设定 Linuxrunlevel,进行对应的初始化工作。


runlevel

runlevel 可以认为是系统状态,进入每个 runlevel 需要启动或关闭相应的一系列服务 (services),这些服务以初始化脚本的方式存放在 /etc/init.d 目录下。

/etc 目录下有7个名为 rcN.d 的目录,即 rc0.d, rc1.d, rc2.d, rc3.d, rc4.d, rc5.d, rc.6rcN.d 目录下都是符号链接文件,它们全部指向 /etc/init.d 目录下的 service 脚本文件。rcN.d 目录下脚本文件的命名规则为 “K+两位数字+服务名” 或 “S+两位数字+服务名”。系统会根据指定的 runlevel 进入对应的 rcN.d 目录,按文件名字母顺序检索目录下的链接文件,对于以 K 开头的文件,系统将停止对应的服务,对于 S 开头的文件,系统将启动对应的服务,两位数字决定服务的执行顺序,数字越小越先被执行。

  • 查看当前的 runlevel 用命令 runlevel
  • 进入其它 runlevel 用命令 init N,比较常用的是 init 0 关机,init 6 重启。
  • Ubuntu 的 runlevel 0 是 Halt,runlevel 6 是 Reboot,runlevel 1 是 Single-user mode,禁止远程登录,拥有 root 权限,用于系统维护。我们通常使用的 runlevel 2 是 Graphical multi-user with networking, runlevel 3~5 没被使用,但是服务配置与 runlevel 2 是一样的,即 rc2.d, rc3.d, rc4.d, rc5d 目录下的链接文件具有相同的命令且指向相同的 service 脚本文件。

    runlevel2-5

  • update-rc.d 命令可以更新系统启动项,控制 /etc/init.d 文件夹中的服务按指定顺序、在指定运行级别中启动或关闭。


rc-sysinit.conf

/etc/init/rc-sysinit.conf 主要内容如下:

......
# 设定默认的 runlevel=2
env DEFAULT_RUNLEVEL=2

emits runlevel  
......
script  
    # 如果有 /etc/inittab 文件且可读,令 DEFAULT_RUNLEVEL=initdefault
    if [ -r /etc/inittab ]
    then
    eval "$(sed -nre 's/^[^#][^:]*:([0-6sS]):initdefault:.*/DEFAULT_RUNLEVEL="\1";/p' /etc/inittab || true)"
    fi

    # 根据命令行输入来设定 DEFAULT_RUNLEVEL
    for ARG in $(cat /proc/cmdline)
    do
    case "${ARG}" in
    -b|emergency)
        # Emergency shell
        [ -n "${FROM_SINGLE_USER_MODE}" ] || sulogin
        ;;
    [0123456sS])
        # Override runlevel
        DEFAULT_RUNLEVEL="${ARG}"
        ;;
    -s|single)
        # Single user mode
        [ -n "${FROM_SINGLE_USER_MODE}" ] || DEFAULT_RUNLEVEL=S
        ;;
    esac
    done

    # 如果不是 single-user mode,则执行 /etc/init.d/rcS 脚本
    [ -n "${FROM_SINGLE_USER_MODE}" ] || /etc/init.d/rcS

    # 切换系统的 runlevel 至 DEFAULT_RUNLEVEL
    telinit "${DEFAULT_RUNLEVEL}"
end script  

/etc/init.d/rcS 中只有一句有效命令 exec /etc/init.d/rc S。该命令执行 /etc/init.d/rc 脚本,并将 S 作为第一个参数传入。/etc/init.d/rc 脚本的主要内容如下:

......
# 将执行该脚本时传入的第一个参数设为 new runlevel
[ "$1" != "" ] && runlevel=$1
......
# 如果存在 /etc/rc$runlevel.d 文件夹,根据 $runlevel 执行对应的动作
if [ -d /etc/rc$runlevel.d ]  
then  
    case "$runlevel" in
        0|6)
            ACTION=stop
            ;;
        S)
            ACTION=start
            ;;
        *)
            ACTION=start
            ;;
    esac
......
# /etc/rc$runlevel.d 中对于以 *K* 开头的文件,系统将停止对应的服务,
# 对于 *S* 开头的文件,系统将启动对应的服务
......
fi

trap - EXIT # Disable emergency handler

exit 0  


rc.local

/etc/rcN.d 目录下的脚本文件之所以要有运行优先级是因为不同的服务之间可能存在依赖。那么哪个脚本文件被最后运行呢?

runlevel2

由截图可知最后一个被运行的脚本文件是 S99local,即 /etc/rc.d/rc.local/etc/rc.d/rc.local 主要功能是如果存在 /etc/rc.local 就运行它。/etc/rc.local 默认情况下是不执行任何操作,内容如下所示:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

exit 0  

用户可以根据自己的需求将一些执行命令或脚本写到 /etc/rc.local 中,它是在一切初始化工作后,留给用户进行个性化的地方。需要注意的是有时候 /etc/rc.local 中的命令执行时依赖的其他程序还没启动,我的偷懒解决方案是将命令写成一个可执行脚本,放在 /etc/rc.local 中延时执行。以下是我的 /etc/rc.local 内容:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
iptables-restore < /home/jimmy/backup/iptables.conf

/usr/local/bin/ssserver -c /etc/shadowsocks/config.json -d start

#/usr/bin/at now +1 min < /home/jimmy/my_shell/start_ghost_with_forever.sh

exit 0  


至此启动完成。