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#
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