清单 1. inittab 文件摘录
# The default runlevel
id:2:initdefault
# Boot-time system configuration/initialization script
si::sysinit:/etc/init.d/rcS
# Runlevels
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
z6:6:respawn:/sbin/sulogin
# How to react to ctrl-alt-del
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
在 init 加载 /etc/inittab 之后,就会将系统切换到 initdefault 操作所定义的运行级别。如 清单 1 所示,即运行级别 2。我们可以将运行级别看作是系统的状态。例如,运行级别 0 定义了系统挂起状态,运行级别 1 是单用户模式。运行级别 2 到 5 是多用户状态,运行级别 6 表示重启。(注意有些发行版对于运行级别的表示是不同的)。还可以以另一种方式考虑运行级别,即它是一种定义可以执行哪些进程(定义系统状态的进程)的方法。
# inittab is only used by upstart for the default runlevel.
#
# ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
#
# System initialization is started by /etc/init/rcS.conf
#
# Individual runlevels are started by /etc/init/rc.conf
#
# Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf
#
# Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
# with configuration in /etc/sysconfig/init.
#
# For information on how to write upstart event handlers, or how
# upstart works, see init(5), init(8), and initctl(8).
#
# Default runlevel. The runlevels used are:
# 0 - halt (Do NOT set initdefault to this)
# 1 - Single user mode
# 2 - Multiuser, without NFS (The same as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
#
id:5:initdefault:
Fedora的默认运行级别为5,是多用户的x-windows图形界面。与传统的sysvinit有所不同,在upstart中,只会为默认运行级别使用inittab文件,要添加其他的运行级别,应该放到/etc/init/rc.conf中,而不是inittab中。upstart系统现在首先运行的是/etc/init/rcS.conf,其内容如下(Fedora 14中):
start on startup
stop on runlevel
task
# Note: there can be no previous runlevel here, if we have one it's bad
# information (we enter rc1 not rcS for maintenance). Run /etc/rc.d/rc
# without information so that it defaults to previous=N runlevel=S.
console output
exec /etc/rc.d/rc.sysinit
post-stop script
if [ "$UPSTART_EVENTS" = "startup" ]; then
[ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
[ -z "$runlevel" ] && runlevel="3"
for t in $(cat /proc/cmdline); do
case $t in
-s|single|S|s) runlevel="S" ;;
[1-9]) runlevel="$t" ;;
esac
done
exec telinit $runlevel
fi
end script
upstart首先在系统引导时首先运行rc.sysinit脚本,然后搜索inittab的initdefault,用telinit切换到initdefault的级别来运行。upstart把原来/etc/inittab的功能被分散到/etc/init下的各个conf文件中。
# rcS - System V single-user mode compatibility
#
# This task handles the old System V-style single-user mode, this is
# distinct from the other runlevels since running the rc script would
# be bad.
description "System V single-user mode compatibility"
author "Scott James Remnant "
start on runlevel S
stop on runlevel [!S]
console owner
script
if [ -x /usr/share/recovery-mode/recovery-menu ]; then
exec /usr/share/recovery-mode/recovery-menu
else
exec /sbin/sulogin
fi
end script
post-stop script
# Don't switch runlevels if we were stopped by an event, since that
# means we're already switching runlevels
if [ -n "${UPSTART_STOP_EVENTS}" ]
then
exit 0
fi
# Switch, passing a magic flag
start --no-wait rc-sysinit FROM_SINGLE_USER_MODE=y
end script
这里先做一些前期检查,与Fedora不同的是,第一个执行的脚本换成了/etc/init/rc-sysinit.conf,其内容如下:
# rc-sysinit - System V initialisation compatibility
#
# This task runs the old System V-style system initialisation scripts,
# and enters the default runlevel when finished.
description "System V initialisation compatibility"
author "Scott James Remnant "
start on filesystem and net-device-up IFACE=lo
stop on runlevel
# Default runlevel, this may be overriden on the kernel command-line
# or by faking an old /etc/inittab entry
env DEFAULT_RUNLEVEL=2
# There can be no previous runlevel here, but there might be old
# information in /var/run/utmp that we pick up, and we don't want
# that.
#
# These override that
env RUNLEVEL=
env PREVLEVEL=
console output
env INIT_VERBOSE
task
script
# Check for default runlevel in /etc/inittab
if [ -r /etc/inittab ]
then
eval "$(sed -nre 's/^[^#][^:]*:([0-6sS]):initdefault:.*/DEFAULT_RUNLEVEL="\1";/p' /etc/inittab || true)"
fi
# Check kernel command-line for typical arguments
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
# Run the system initialisation scripts
[ -n "${FROM_SINGLE_USER_MODE}" ] || /etc/init.d/rcS
# Switch into the default runlevel
telinit "${DEFAULT_RUNLEVEL}"
end script
可见Ubuntu是在rc-sysinit.conf中才处理inittab并切换到initdefault级别来运行。
init的完整初始化过程如下(包括启动字符界面和图形界面):
/sbin/init
--->/etc/init/rcS.conf
--->exec /etc/rc.d/rc.sysinit 执行第一个脚本(Ubuntu中为/etc/init/rc-sysinit.conf)
--->/bin/hostname 获取主机名(设置$HOSTNAME)
--->/etc/sysconfig/network 配置网络基本参数
--->/proc/mounts 检测并挂载procfs,sysfs到/proc,/sys
--->/etc/init.d/functions 包含一些通用函数,会被/etc/init.d(是到rc.d/init.d的链接)下的脚本用到
--->/etc/sysconfig/i18n 设置终端字符集
--->/etc/sysconfig/init 设置终端和图形界面的一些参数
--->deamon(),killproc(),pidofproc() 一些通用函数
--->status(),echo_success(),
--->update_boot_stage(),strstr()
--->/selinux/enforce 检查SELinux的状态
--->/etc/system-release 打印熟悉的发行版信息 “Welcome to Fedora ..."
--->/proc/cmdline 获取内核启动的命令行参数
--->/proc/sys/kernel/modprobe 获取modprobe的位置(为/sbin/modprobe)
--->/sbin/sysctl 初始化硬件(通过sysctl设置运行时内核参数)
--->kill $nashpid 杀死所有的nash进程(我们在initrd中使用的shell)
--->/sbin/start_udev 启动udev((动态设备管理进程)
--->/bin/taskset 设置进程的默认CPU亲合值(即优先使用哪个CPU,用在多处理器环境中)
--->/etc/sysconfig/modules/*.modules 加载其他用户自定义的模块
--->sysctl -e -p /etc/sysctl.conf 配置内核参数
--->/proc/devices 获取设备号及相应设备名,以便进行设备初始化
--->/sbin/dmraid 激活software raid
--->/sbin/kpartx “/dev/mapper/..." 为software raid上的每块硬盘创建设备映射
--->/.autofsck 是否自动执行文件系统检查
--->sulogin 若为单用户模式,执行单用户登录程序
--->plymouth --show-splash 显示启动时的背景画面
--->/etc/sysconfig/readonly-root 设置root文件系统挂载方式
--->从/etc/fstab挂载暂存设备
--->/etc/rwtab, /etc/rwtab.d/* 挂载其他有卷标的分区
--->ip addr show 获取并设置网上ip地址
--->从/etc/fstab挂载持久数据的存储设备
--->/etc/statetab, /etc/statetab.d/* 持载其他持久数据的存储设备
--->/sbin/fsck 检查文件系统
--->umount -a & reboot -f 如果检查失败,卸载文件系统并重启
--->以读写方式重新挂载root文件系统 如果文件系统检查没有失败
--->挂载所有其他的文件系统
--->cat /var/lib/random-seed > /dev/urandom 初始化伪随机数生成器
--->/usr/sbin/system-config-keyboard,passwd,... 配置机器相关参数(如果有需要的话)
--->/etc/sysconfig/network 重新读取网络配置数据,并重设hostname
--->清除相关的/, /var,/tmp数据
--->/sbin/swapon 开启各个交换区分(根据/proc/swaps)
--->/usr/sbin/system-config-network-cmd 执行引导时的网络配置(传递内核启动的netprofile参数)
--->dmesg -s 131072 > /var/log/dmesg 转储内核启动的消息信息
--->/etc/inittab
--->id:5:initdefault: 查找initdefault定义的运行级别(为5,图形用户界面)
--->telinit $runlevel 切换到对应级别运行
--->/etc/init/rc.conf
--->exec /etc/rc.d/rc $RUNLEVEL
--->/etc/profile.d/lang.sh 设置语言环境
--->/etc/rc.d/rc5.d/KNxxxx 先关闭相关服务(在关闭系统时也会执行)
--->/etc/rc.d/init.d/xxxx
--->/etc/rc.d/rc5.d/SNxxxx 再开启相关服务
--->etc/rc.d/rc5.d/xxxx
--->/etc/rc.d/rc.local 在所有init脚本运行完之后运行,可在些添加自己的初始化命令(Ubuntu中为/etc/rc.local)
--->/etc/init/start-ttys.conf 启动tty1-tty6设备
--->/etc/sysconfig/init 指定tty设备,通常为/dev/tty1-/dev/tty6
--->/etc/init/tty.conf
--->exec /sbin/mingetty $TTY 在每个tty设备上启动mingetty
--->成功后就可以通过Ctrl+Alt+F1..F6在各个不同的tty之间切换
################################################# 字符界面 ################################################
--->fork()--->/sbin/mingetty 运行mingetty程序,出现字符登录界面
--->/etc/issue 在登录界面上显示发行版信息
--->exec("/bin/login",...) 运行/bin/login程序,验证用户名和口令
--->/etc/passwd 读取passwd文件核对用户名和口令
--->jackzhou:x:500:500:jackzhou:/home/jackzhou:/bin/bash
--->切换到工作目录/home/jackzhou
--->初始化环境变量$HOME,$PATH等
--->/etc/motd 显示当天的消息
--->检查新邮件
--->exec("/bin/bash",...) 运行bash程序
--->/etc/profile 执行这些脚本中的命令
--->.bash_profile或.bashrc
--->ENV=$HOME/.anyfilename; export ENV 运行$ENV指向的脚本(如果设置了的话)
--->bash运行中 mingetty,login最后替换成了bash,登录成功
################################################## 图形界面 #############################################
--->/etc/init/prefdm.conf
--->exec /etc/X11/prefdm -nodaemon 准备启动指定的X图形界面(X Display Manager)
--->/etc/sysconfig/i18n 设置语言环境
--->/etc/sysconfig/desktop 读取指定的DM配置(如果有的话)
--->exec /usr/sbin/gdm 启动指定的DM(gdm, kdm, wdm或xdm,默认为/usr/sbin/gdm)
--->启动X server窗口
--->/etc/gdm/custom.conf 根据配置在X窗口中显示登录界面
--->用户选择语言、键盘布局、会话等
--->/usr/share/xsessions/gnome.desktop 读取会话要显示的名称
--->Exec=gnome-session 指定默认的会话程序
--->用户输入用户名和密码
--->用/bin/login验证用户名和密码
--->/etc/gdm/PreSession/* 执行会话前的一些任务(比如更改X窗口的默认背景)
--->/etc/gdm/PostLogin/* 执行一些登录后立即需要运行的命令
--->/etc/gdm/Xsession gnome-session--->/etc/X11/xinit/Xsession 启动GNOME会话
--->/etc/X11/xinit/xinitrc-common 导入Xsession与xinitrc共用的代码
--->/etc/profile.d/lang.sh 设置i18n环境
--->/etc/X11/Xresources 读取用户登录时需要载入的全局资源
--->/etc/X11/Xmodmap 读取的全局的键盘配置(用于xdm和xinit,用startx启动图形界面时要用到)
--->/etc/X11/xinit/xinitrc.d/* 运行所有的xinitrc脚本
--->exec -l $SHELL -c gnome-session 执行特定的环境设置(以前是执行./Xclients.d/Xclients.gnome-session.sh)
--->/etc/X11/xinit/Xclients 运行各个X客户端的脚本(或者$HOME/.xsession,或者$HOME/.Xclients)
--->/etc/sysconfig/desktop 读取指定的会话程序配置(如果有的话)
--->exec "$(type -p gnome-session)" 默认运行gnome-session,进入GNOME桌面
--->GNOME桌面运行中 mingetty,login最后替换成了gnome程序,登录成功
--->/etc/gdm/PostSession/* GNOME会话结束时运行的脚本
#################################### 在字符界面下通过startx启动图形界面 ##############################################
--->/bin/bash 在字符界面的Shell下
--->/usr/bin/startx
--->记录$HOME目录和/etc/X11/xinit下的.xinitrc和.xserverrc文件 以$HOME目录下的为优先
--->解析用户指定的client、server、display参数及其选项
--->没有指定参数时就设为前面记录的.xinitrc和.xserverrc文件
--->XAUTHORITY=$HOME/.Xauthority 设置XAUTHORITY环境变量
--->设置X server的权限信息
--->xinit $client $clientargs -- $server $display $serverargs 启动X server和第一个X client
--->/etc/X11/xinit/xinitrc 用来运行各个X client(上面没有指定第一个client时)
--->/etc/X11/xinit/xinitrc-common 导入Xsession与xinitrc共用的代码
--->/etc/profile.d/lang.sh 设置i18n环境
--->/etc/X11/Xresources 读取用户登录时需要载入的全局资源
--->/etc/X11/Xmodmap 读取全局的键盘配置
--->/etc/X11/xinit/xinitrc.d/* 运行所有的xinitrc脚本
--->/etc/X11/xinit/Xclients 运行各个X client的脚本(或者$HOME/.Xclients)
--->/etc/sysconfig/desktop 读取指定的会话程序配置(如果有的话)
--->exec "$(type -p gnome-session)" 默认运行gnome-session,进入GNOME桌面
--->GNOME桌面运行中 mingetty,login最后替换成了gnome程序,登录成功
--->/etc/gdm/PostSession/* GNOME会话结束时运行的脚本
注意在rc.sysinit加载完/etc/sysconfig/modules/中(如果你希望额外加载一些比如遥控器之类的模块,你可以在这里增加脚本)的用户自定义的模块后,就会由update_boot_stage通知图形化的启动界面,准备进入启动画面,内核启动的命令行参数(在grub中可以看到)中会指定rhgb程序。rhgb程序的作用是在启动的时候建立一个临时的仅使用loopback网络的X窗口服务器,然后在这个窗口上显示启动进度,init程序的其他部分可以通过rhgb-client程序向这个进度窗口发送消息。在“配置机器相关参数”这一步中,如果存在/.unconfigured文件,会先调用rhgb-client向进度窗口发送消息,然后调用/usr/bin/system-config-keyboard配置键盘,调用 /usr/bin/passwd root配置超级用户密码,调用/usr/sbin/system-config-network-tui配置网络,调用/usr/sbin/timeconfig配置时区,调用 /usr/sbin/authconfig-tui --nostart配置网络登录,调用/usr/sbin/ntsysv配置默认的运行级别。然后清空包括/var/lock/,/var/run/, /tmp等在内的临时目录,并开启交换空间。运行完rc.sysinit后,rcS.conf就会查找inittab中的默认运行级别并切换到这个级别。
转到rc.conf,它调用/etc/rc.d/rc脚本,运行指定级别目录下的各个启动脚本。首先按照名称顺序运行那些K打头的脚本,然后按照名称顺序运行那些S打头的脚本。启动脚本(是符号链接)中的数字是怎么来的呢,它是由你指定的,如果你要增加自己的启动脚本到相应的启动级别中去,这个数字当然应该由你指定,因为只有你才知道这个脚本应该以什么样的优先级启动。但是对于那些已经存在的启动脚本,它们的优先级是在脚本中最前面的注释行中的chkconfig这一行指定的,在这一行中,你可以看到类似# chkconfig: 35 99 95的字样,它的含义是:这个脚本应该增加到运行级别3和运行级别5中,启动的优先级是99,关闭的优先级是95,当然,这些数字是由那些Fedora的开发者测试过没有问题,所以才写在这里的。二进制的程序/sbin/chkconfig将会读取这一行,并且在将服务增加到启动级别中去的时候自动生成文件名。文件名中的第一个字符S和K代表了什么含义呢?它代表了你在services控制面板中选择了打开这个服务还是关闭这个服务,如果你在那里打开了这个服务,则以S作为前导符,否则为K。结合我们前面介绍的启动过程,你就可以知道,在启动的时候,Fedora会首先保证那些K打头的脚本是关闭的(通过以stop参数调用这个脚本),然后才会逐个启动那些S打头的脚本(通过以start参数调用这个脚本)。对于每个启动脚本文件,如果想知道启动了时候都做了些什么,可以查看相应脚本中的start()函数,比如对于avahi-dnsconfd这个脚本,我们可以看到,它只是运行了/usr/sbin/avahi-dnsconfd -D这个命令。
xsetroot -solid gray &
xclock -g 50x50-0+0 -bw 0 &
xterm -g 80x24+0+0 &
xterm -g 80x24+0-0 &
twm
当xinit启动时,它会先启动X server,然后启动一个clock,两个xterm,最后启动窗口管理器twm。请注意最后一个命令不能后台运行,否则所有命令都后台运行的话xinit就会返回退出,同样的,除最后一个命令外都必须后台运行,否则后面的命令将只有在该命令退出后才能运行。
X&
export DISPLAY=:0.0
xterm
这个实现完全正确,然而却并没有完全实现xinit所具有的功能,xinit的功能就是当最后一个启动的client(如上面第二个例子中的twm窗口管理器)退出后,X服务器也会退出。而我们的脚本实现中当我们退出xterm后并不会退出X server。
在Fedora 14中,只有/etc/X11/xinit/xinitrc文件,由它来运行Xclients脚本,这个脚本用于运行各个指定的X client,其中的第一个X client即为gnome-session,这就是GNOME桌面环境。从代码可知,xinitrc的功能与Xsession几乎一样,只有一些细微的差别(在Ubuntu中xinitrc是直接调用Xsession的)。
完整的Linux init程序启动过程如下图:
图1 Linux init程序启动过程
service system/my_service {
need = system/initial net/all;
exec start = /sbin/my_service --start --option;
exec stop = /sbin/my_service --stop --option;
}
您可以使用服务定义对整个系统进行编码,如清单 2 所示。那些没有依赖关系的服务可以立即(并行地)启动,而具有依赖关系的服务则必须等待以安全启动。您可以将 initng 看作一个基于目标的系统。其目标就是要启动的服务。没有进行显式的规划;相反,依赖关系简单地定义了服务初始化的流程,这个过程中隐含着并行化的操作。