清单 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 scriptupstart首先在系统引导时首先运行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 <[email protected]>" 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 <[email protected]>" 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 看作一个基于目标的系统。其目标就是要启动的服务。没有进行显式的规划;相反,依赖关系简单地定义了服务初始化的流程,这个过程中隐含着并行化的操作。