1. 程式人生 > >Linux 啟動過程分析 (SysV init啟動模式)

Linux 啟動過程分析 (SysV init啟動模式)


      本篇主要分析傳統的Linux啟動方式  SysV init啟動模式。(注:當前Linux發行版大多采用Systemd 啟動模式來替代傳統的 SysV init啟動模式。)


     分析如下:

     1.核心初始後,建立Init程序。 init 程序是所有程序的起點,一般對應執行檔案為/sbin/init。

      2. /sbin/init 讀取/etc/initab 檔案,根據裡面內容進行初始化。

        參考文獻 /etc/inittab詳解: http://blog.csdn.net/newnewman80/article/details/8133797
     

# $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $
#/etc/initab內容
# The default runlevel. 預設執行級別為5
#
#runlevel用來表示在init程序結束之後的系統狀態,在系統的硬體中沒有固定的資訊來表示runlevel
#Runlevel 0是讓init關閉所有程序並終止系統。
#Runlevel 1是用來將系統轉到單使用者模式
#Runlevel 2是允許系統進入多使用者的模式,但並不支援檔案共享,這種模式非常少應用。
#Runlevel 3是最常用的執行模式,主要用來提供真正的多使用者模式,也是多數伺服器的預設模式。
#Runlevel 4一般不被系統使用,使用者能設計自己的系統狀態並將其應用到runlevel 4階段,儘管非常少使用,但使用該系統能#實現一些特定的登入請求。
#Runlevel 5是將系統初始化為專用的X Window終端。對功能強大的Linux系統來說,這並不是好的選擇,但使用者如果需要這樣,也能通過在runlevel啟動來實現該方案。
#Runlevel 6是關閉所有執行的程序並重新啟動系統

id:5:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
#首次執行的指令碼,si 是系統初始化的程序
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin


# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
#每個執行級別對應執行的指令碼
l0:0:wait:/etc/init.d/rc 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
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6 

# Normally not reached, but fallthrough in case of emergency
z6:6:respawn:/sbin/sulogin
#S0:12345:respawn:/sbin/getty -L 115200 ttyS0
# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
#  <id>:<runlevels>:<action>:<process>
#

1:2345:respawn:/sbin/getty 38400 tty1
2:2345:respawn:/sbin/getty 38400 tty2
3:2345:respawn:/sbin/getty 38400 tty3
4:2345:respawn:/sbin/getty 38400 tty4
5:2345:respawn:/sbin/getty 38400 tty5
6:2345:respawn:/sbin/getty 38400 tty6


3. 執行/etc/init.d/rcS 指令碼

   #!/bin/sh
    #
    # rcS           Call all S??* scripts in /etc/rcS.d in
    #               numerical/alphabetical order.
    #
    # Version:      @(#)/etc/init.d/rcS  2.76  19-Apr-1999  [email protected]
    #


    PATH=/sbin:/bin:/usr/sbin:/usr/bin
    runlevel=S
    prevlevel=N
    umask 022
    export PATH runlevel prevlevel


    #    Make sure proc is mounted
    #
    [ -d "/proc/1" ] || mount /proc


    #
    #       Source defaults.
    # 設定環境變數
    . /etc/default/rcS


    #
    #       Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
    #
    trap ":" INT QUIT TSTP


    #
    #   Call all parts in order.
    # 
exec /etc/init.d/rc S

    4.  /etc/init.d/rc S 的執行

  
  [email protected]:~# cat /etc/init.d/rc

#!/bin/sh
#
# rc     This file is responsible for starting/stopping
#        services when the runlevel changes.
#
#               Optimization feature:
#               A startup script is _not_ run when the service was
#               running in the previous runlevel and it wasn't stopped
#               in the runlevel transition (most Debian services don't
#               have K?? links in rc{1,2,3,4,5} )
#
# Author:       Miquel van Smoorenburg <[email protected]>
#               Bruce Perens <[email protected]>
#
# Version:      @(#)rc  2.78  07-Nov-1999  [email protected]
#
#設定環境變數
. /etc/default/rcS
export VERBOSE

#設定啟動程序,計算啟動步驟
startup_progress() {
    step=$(($step + $step_change))
    if [ "$num_steps" != "0" ]; then
        progress=$((($step * $progress_size / $num_steps) + $first_step))
    else
        progress=$progress_size
    fi
    #echo "PROGRESS is $progress $runlevel $first_step + ($step of $num_steps) $step_change $progress_size"
    #if type psplash-write >/dev/null 2>&1; then
    #    TMPDIR=/mnt/.psplash psplash-write "PROGRESS $progress" || true
    #fi
    if [ -e /mnt/.psplash/psplash_fifo ]; then
        echo "PROGRESS $progress" > /mnt/.psplash/psplash_fifo
    fi
}
#
# Start script or program.
#啟動指令碼
startup() {
  # Handle verbosity
  [ "$VERBOSE" = very ] && echo "INIT: Running [email protected]"
  case "$1" in
        *.sh)
                # Source shell script for speed.
                (
                        trap - INT QUIT TSTP
                        scriptname=$1
                        shift
                        . $scriptname
                )
                ;;
        *)
                "[email protected]"
                ;;
  esac
  startup_progress
}
  # Ignore CTRL-C only in this shell, so we can interrupt subprocesses.
  trap ":" INT QUIT TSTP
  # Set onlcr to avoid staircase effect.
  stty onlcr 0>&1

 # Limit stack size for startup scripts
  #設定啟動指令碼的棧大小
 [ "$STACK_SIZE" == "" ] || ulimit -S -s $STACK_SIZE
  # Now find out what the current and what the previous runlevel are.
  runlevel=$RUNLEVEL
  # Get first argument. Set new runlevel to this argument.
  [ "$1" != "" ] && runlevel=$1
  if [ "$runlevel" = "" ]
  then
        echo "Usage: $0 <runlevel>" >&2
        exit 1
  fi
  previous=$PREVLEVEL
  [ "$previous" = "" ] && previous=N
  export runlevel previous

 echo "runlevel...." $runlevel
  echo "previous....." $previous

 # Is there an rc directory for this new runlevel?
  if [ -d /etc/rc$runlevel.d ]
  then
        # Find out where in the progress bar the initramfs got to.
        PROGRESS_STATE=0
        #if [ -f /dev/.initramfs/progress_state ]; then
        #    . /dev/.initramfs/progress_state
        #fi
        # Split the remaining portion of the progress bar into thirds
        progress_size=$(((100 - $PROGRESS_STATE) / 3))
        case "$runlevel" in
                0|6)
                        # Count down from -100 to 0 and use the entire bar
                        first_step=-100
                        progress_size=100
                        step_change=1
                        ;;
                S) 
                        # Begin where the initramfs left off and use 2/3
                        # of the remaining space
                        first_step=$PROGRESS_STATE
                        progress_size=$(($progress_size * 2))
                        step_change=1
                        ;;
                *)
                        # Begin where rcS left off and use the final 1/3 of
                        # the space (by leaving progress_size unchanged)
                        first_step=$(($progress_size * 2 + $PROGRESS_STATE))
                        step_change=1
                        ;;
        esac
        num_steps=0
       #計算步伐num_steps
        for s in /etc/rc$runlevel.d/[SK]*; do
            case "${s##/etc/rc$runlevel.d/S??}" in
                gdm|xdm|kdm|reboot|halt)
                    break
                    ;;
            esac
            num_steps=$(($num_steps + 1))
        done
     
        step=0
        # First, run the KILL scripts.
        # 如果前一個狀態不為N,執行K開頭指令碼,關閉或殺死指令碼服務
        if [ $previous != N ]
        then
                for i in /etc/rc$runlevel.d/K[0-9][0-9]*
                do
                        # Check if the script is there.
                        [ ! -f $i ] && continue
                        # Stop the service.
                        startup $i stop
                done
        fi
        #現在開始執行對應執行級別的啟動指令碼,S開頭
        #第一次的執行級別為:runlevel為S,previous為N,執行 /etc/rcS.d/S*
        #第二次的執行級別: runlevel為3或5,previous為S
        #關機時,第三次的執行級別:runlevel為6,previous為5或3
        # Now run the START scripts for this runlevel.
        for i in /etc/rc$runlevel.d/S*
        do
                [ ! -f $i ] && continue
                if [ $previous != N ] && [ $previous != S ]
                then
                        #
                        # Find start script in previous runlevel and
                        # stop script in this runlevel.
                        #
                        suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}
                        stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix
                        previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
                        #
                        # If there is a start script in the previous level
                        # and _no_ stop script in this level, we don't
                        # have to re-start the service.
                        #
                        [ -f $previous_start ] && [ ! -f $stop ] && continue
                fi
                case "$runlevel" in
                        0|6)
                                startup $i stop
                                ;;
                        *)
                                #啟動指令碼
                                startup $i start
                                ;;
                esac
        done
  fi
#Uncomment to cause psplash to exit manually, otherwise it exits when it sees a VC switch
if [ "x$runlevel" != "xS" ] && [ ! -x /etc/rc${runlevel}.d/S??xserver-nodm ]; then
    if type psplash-write >/dev/null 2>&1; then
        TMPDIR=/mnt/.psplash psplash-write "QUIT" || true
        umount -l /mnt/.psplash
    fi
fi


     通過分析/etc/init.d/rc 指令碼,接下來執行 /etc/rcS.d/中S開頭的指令碼,如下所示。

     [email protected]:/etc/rcS.d# ls
     S00fbsetup      S04udev          S29read-only-rootfs-hook.sh  S38dmesg.sh
     S01keymap.sh    S05modutils.sh   S30urandom                   S39alsa-state
     S02banner.sh    S05psplash.sh    S36udev-cache                S39hostname.sh
     S02sysfs.sh     S06checkroot.sh  S37populate-volatile.sh      S55bootmisc.sh
     S03mountall.sh  S07bootlogd      S38devpts.sh
     [email protected]:/etc/rcS.d#

         其中:S04udev 指令碼的start 入口 動態載入系統驅動

   5. 執行/etc/initab 中執行級別對應的指令碼

    接下來執行  l5:5:wait:/etc/init.d/rc 5 對應級別的指令碼,即/etc/rc5.d 資料夾下的指令碼。

[email protected]:/etc/rc5.d# ls  

S01networking   S10dropbear     S20hwclock.sh    S64neard       S02dbus-1              S12rpcbind               S20syslog           S99rmnologin.sh   S05connman       S15mountnfs.sh  S21avahi-daemon  S99stop-        bootlogd  S09xserver-nodm  S20acpid        S22ofono  

[email protected]:/etc/rc5.d#

 

其中 ,S01 networking 用於設定和配置網路介面

           09xserver-nodm用於啟動XServer系統,其執行步驟如下:

   (1)執行/etc/X11/Xserver的指令碼;

   (2)/etc/X11/Xserver 接著執行 exec xinit /etc/X11/Xsession -- $XSERVER $DISPLAY $ARGS $*  

       其中,xinit 命令首先執行Xserver 命令即$XSERVER $DISPLAY $ARGS $*,接著執行X 客戶端命令即 /etc/X11/Xsession指令碼;            

    (3)/etc/X11/Xsession 接著執行/etc/X11/Xsession.d 下的指令碼 ,其中,90XWindowManager.sh 為視窗管理器腳 本,其執行 /usr/bin/x-session-manager指令碼;                   

   (4)/usr/bin/x-session-manager指令碼,接著執行/usr/local/AutoStart/StartSys、OpenBox-session 指令碼等。

S

 /usr/bin/x-session-manager指令碼如下

#!/bin/sh
#
# Very simple session manager for matchbox tools
#
#if [ -e /usr/bin/openbox-session]
#then
exec /usr/local/AutoStart/StartSys &
exec openbox-session
#fi
# Uncomment below to enable parsing of debian menu entrys
# export MB_USE_DEB_MENUS=1
if [ -e $HOME/.matchbox/session ]
then
exec $HOME/.matchbox/session
fi
if [ -e /etc/matchbox/session ]
then
exec /etc/matchbox/session
fi
# Default files to run if $HOME/.matchbox/session or /etc/matchbox/session
# dont exist.
matchbox-desktop &
matchbox-panel &
exec matchbox-window-manager [email protected]   

  指令碼S09xserver-nodm內容如下:

  [email protected]:/etc/rc5.d# cat S09xserver-nodm

#!/bin/sh
#
### BEGIN INIT INFO
# Provides: xserver
# Required-Start: $local_fs $remote_fs dbus
# Required-Stop: $local_fs $remote_fs
# Default-Start:     5
# Default-Stop:      0 1 2 3 6
### END INIT INFO
#根據程序名殺死程序
killproc() {            # kill the named process(es)
        pid=`/bin/pidof $1`
        [ "$pid" != "" ] && kill $pid
}
#讀取傳入的核心的命令列引數
read CMDLINE < /proc/cmdline
for x in $CMDLINE; do
        case $x in
#禁止啟動X11
        x11=false)
                echo "X Server disabled"
                exit 0;
                ;;
        esac
done

case "$1" in
#啟動分支
  start)
       . /etc/profile
       username=root
       echo "Starting Xserver"
       if [ -f /etc/X11/Xusername ]; then
           username=`cat /etc/X11/Xusername`
           # setting for rootless X
           chmod o+w /var/log
           chmod g+r /dev/tty[0-3]
           # hidraw device is probably needed
           if [ -e /dev/hidraw0 ]; then
               chmod o+rw /dev/hidraw*
           fi
       fi
       # Using su rather than sudo as latest 1.8.1 cause failure [YOCTO #1211]
       su -l -c '/etc/X11/Xserver&' $username
       # Wait for the desktop to say its finished loading
       # before loading the rest of the system
       # dbus-wait org.matchbox_project.desktop Loaded
  ;;
  stop)
        echo "Stopping XServer"
        killproc xinit
  ;;
  restart)
        $0 stop
        sleep 1
        $0 start
  ;;
  *)
        echo "usage: $0 { start | stop | restart }"
  ;;
esac
exit 0