本篇主要分析传统的Linux启动方式 SysV init启动模式。(注:当前Linux发行版大多采用Systemd 启动模式来替代传统的 SysV init启动模式。)
分析如下:
1.内核初始后,创建Init进程。 init 进程是所有进程的起点,一般对应执行文件为/sbin/init。
# $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
# $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 miquels@cistron.nl
#
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
#!/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 miquels@cistron.nl
#
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 的执行
root@genericx86:~# 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 <miquels@cistron.nl>
# Bruce Perens <Bruce@Pixar.com>
#
# Version: @(#)rc 2.78 07-Nov-1999 miquels@cistron.nl
#
#设置环境变量
. /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 $@..."
case "$1" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
scriptname=$1
shift
. $scriptname
)
;;
*)
"$@"
;;
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开头的脚本,如下所示。
root@genericx86:/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
root@genericx86:/etc/rcS.d#
5. 执行/etc/initab 中运行级别对应的脚本
接下来执行 l5:5:wait:/etc/init.d/rc 5 对应级别的脚本,即/etc/rc5.d 文件夹下的脚本。
root@genericx86:/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
root@genericx86:/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 $@
脚本S09xserver-nodm内容如下:
root@genericx86:/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