CentOS系统启动流程图文详解.
熟悉系统启动流程对于我们学习Linux系统是非常有帮助的,虽然基础,但能帮助我们更加理解Linux系统的工作机制。以下将以CentOS发行版为例来介绍Linux系统的启动流程,因为在CentOS 5、CentOS 6以及CentOS 7使用的初始化程序init各不相同,虽然CentOS 6和CentOS 7都有向后兼容,但在工作机制上仍有一些差异,因此以下主要介绍CentOS 5/6系统启动流程。
CentOS的启动流程总体顺序如下(以CentOS 6为例):
POST --> Boot Sequence --> bootloader(MBR) --> Kernel --> 加载rootfs --> switchroot --> /sbin/init --> (配置文件:/etc/inittab, /etc/init/*.conf) --> 根据init配置文件设置默认运行级别 --> 运行系统初始化脚本/etc/rc.d/rc.sysinit,完成系统初始化 --> 开启或关闭用户选定的对应运行级别下所对应的服务 --> 启动终端,打印登录提示符。
注意:红色部分代表内核空间的系统启动流程,紫色部分代表用户空间的系统启动流程。
接下来逐一详解:
第一步:POST加电自检
主要实现的功能是检测各个外围硬件设备是否存在而且能够正常运行起来,实现这一自检功能的是固化在主板上的ROM(主要代表为CMOS)芯片上的BIOS(Basic Input/Output System)程序;例如BIOS会检测CPU、Memory以及I/O设备是否能够正常运行,如果是个人计算机的话可能还会检测一下显示器。只要一通电,CPU就会自动去加载ROM芯片上的BIOS程序,是这样来实现的。而检测完成之后就进行硬件设备的初始化。
第二步:Boot Sequence(选择启动设备以加载MBR)
主要实现的功能是选择要启动的硬件设备,选择了之后就可以读取这个设备上位于MBR里头的bootloader了。这一步的实现是这样的:根据BIOS中对启动顺序的设定,BIOS自己会依次扫描各个引导设备,然后第一个被扫描到具有引导程序(bootloader)的设备就被作为要启动的引导设备。
第三步:加载bootloader(MBR)
这一步实现起来的步骤比较多,前面的BIOS通过读取并执行启动设备的MBR中的bootloader,而bootloader要实现的功能就是提供一个菜单给用户,让用户去选择要启动的系统或不同的内核版本,然后把用户选择的内核版本加载至RAM中的特定空间,接着在RAM中解压、展开,而后把系统控制权移交给内核。
grub是bootloader中的一种,就grub来说,为了打破在MBR中只有446Bytes用于存放bootloader这一限制,所以这一步的实现是这样的:grub是通过分成三个阶段来实现加载内核这一功能的,这三个阶段分别是:stage1, stage1.5以及stage2。其中:
stage1:存放于MBR的前446Bytes,用于加载stage1.5阶段,目的是为了识别并驱动stage2(或者/boot)所在分区的文件系统;
stage1.5:存放于MBR之后的扇区,加载stage2所在分区的文件系统驱动,让stage1中的bootloader能识别stage2所在分区的文件系统;
stage2:存放于磁盘分区之上,具体存放于/boot/grub目录之下,主要用于加载内核文件(vmlinuz-VERSION-RELEASE)以及ramdisk这个临时根文件系统(initrd-VERSION-RELEASE.img或initramfs-VERSION-RELEASE.img)。
概述:假如要启动的是硬盘设备,首先我们的硬件平台主板BIOS必须能够识别硬盘,然后BIOS才能加载硬盘中的bootloader,而bootloader自身加载后就能够直接识别当前主机上的硬盘设备了;不过,能够识别硬盘设备不代表能够识别硬盘设备中的文件系统,因为文件系统是额外附加的一层软件组织的文件结构,所以要对接一种文件系统,就必须要有对应的能够识别和理解这种文件系统的驱动,这种驱动就称为文件系统驱动。而stage1.5就是向grub提供文件系统驱动的,这样stage1就能访问stage2及内核所在的分区(/boot)了。
演示:
[root@localhost ~]
# ls -1F /boot/
config-2.6.32-642.el6.x86_64
//
此版本内核被编译时选择的功能与模块配置文件;
efi/
grub/
//
就是引导装载程序grub相关配置文件的目录;
initramfs-2.6.32-642.el6.x86_64.img
//
临时根文件系统,提供根文件系统所在分区的驱动;
initrd-2.6.32-642.el6.x86_64kdump.img
lost+found/
symvers-2.6.32-642.el6.x86_64.gz
System.map-2.6.32-642.el6.x86_64
//
内核功能放置到内存地址的映射表;
vmlinuz-2.6.32-642.el6.x86_64*
//
就是内核文件;
[root@localhost ~]
#
[root@localhost ~]
#
[root@localhost ~]
#
[root@localhost ~]
# ls -1F /boot/grub/
device.map
e2fs_stage1_5
fat_stage1_5
ffs_stage1_5
grub.conf
iso9660_stage1_5
jfs_stage1_5
menu.lst@
minix_stage1_5
reiserfs_stage1_5
splash.xpm.gz
stage1
stage2
ufs2_stage1_5
vstafs_stage1_5
xfs_stage1_5
//
可以看到stage1, 各种文件系统对应的stage1.5及stage2这几个阶段的文件都存放于此目录下;
查看grub的配置:
[root@localhost ~]
# vim /boot/grub/grub.conf
default=0
//
设定默认启动第一个菜单项(title);
timeout=5
//
等待用户选择菜单项的时长;
splashimage=(hd0,0)
/grub/splash
.xpm.gz
//
指明菜单项的背景图片的文件路径;
hiddenmenu
//
是否隐藏菜单的具体内容;
title CentOS (2.6.18-398.el5)
//
此处为菜单项的标题;
root (hd0,0)
//
指定grub查找stage2及Kernel文件所在设备的分区;可理解为grub的
"根"
;
kernel
/vmlinuz-2
.6.18-398.el5 ro root=
/dev/VolGroup00/LogVol00
//
启动的内核;
initrd
/initrd-2
.6.18-398.el5.img
//
与内核匹配的ramdisk文件;
//
注意:kernel和initrd的文件路径均以grub的
"根"
作为起始目录,且存放于stage2所在分区上;
需要注意的是,stage2、内核以及ramdisk文件通常放置于一个基本磁盘分区之上,因为grub无法驱动lvm、高级软raid等复杂逻辑设备,除非提供一个复杂的驱动接口,否则如果stage2及内核等文件都存放在lvm等复杂逻辑设备上将无法被stage1所识别,更别说加载了!
第四步:Kernel自身初始化
Kerenl在得到系统控制权之后,首先要进行自身初始化,而初始化的主要作用是:
(1)探测可识别到的所有硬件设备;
bootloader将系统控制权移交给内核就好比如后朝推翻前朝,统治者(内核)当权之后,首先要检查一下有哪些是前朝所留下的,例如有哪些领土、人力、财力、兵力可用等等。
(2)加载硬件驱动程序,即加载真正的根文件系统所在设备的驱动程序(有可能会借助于ramdisk加载驱动);
这就像统治者(内核)在得知底下存在的人力、财力之后,开始将可以“为我所用”的人力纳入麾下,听自己使唤,而不听使唤的杀掉;
(3)以只读方式挂载根文件系统;
如果有借助于ramdisk这个临时文件系统(虚根),则在这一步之后会执行根切换;否则不执行根切换。
(4)运行用户空间的第一个应用程序:/sbin/init.
到这里内核空间的启动流程就结束了,而接下来是用户空间完成后续的系统启动流程。
注意:
ramdisk和内核是由bootloader一同加载到内存当中的,ramdisk是用于实现系统初始化的、基于内存的磁盘设备,即加载至内存(的某一段空间)后把内存当磁盘使用,并在内存中作为临时根文件系统提供给内核使用,帮助内核挂载真正的根文件系统。而之所以能够帮助内核挂载根文件系统是因为在ramdisk这个临时文件系统的/lib/modules目录下有真正的根文件系统所在设备的驱动程序;除此之外,这个临时文件系统也遵循FHS,例如有这些固定目录结构:/bin, /sbin, /lib, /lib64, /etc, /mnt, /media, ...
因为Linux内核有一个特性就是通过使用缓冲/缓存来达到加速对磁盘上文件的访问的目的,而ramdisk是加载到内存并模拟成磁盘来使用的,所以Linux就会为内存中的“磁盘”再使用一层缓冲/缓存,但是我们的ramdisk本来就是内存,它只不过被当成硬盘来使用罢了,这就造成双缓冲/缓存了,而且不会起到提速效果,甚至影响了访问性能;CentOS 5系列以及之前版本的ramdisk文件为initrd-VERSION-RELEASE.img,就会出现上述所说到的问题;而为了解决一问题,CentOS 6/7系列版本就将其改为initramfs-VERSION-RELEASE.img,使用文件系统的方式就可以避免双缓冲/缓存了,我们可以说这是一种提速机制。
需要注意的是,系统发行商为了适应于各个不同的硬件接口,因此将各个不同的硬件接口的驱动组装打包起来,例如在用户第一次使用光盘安装完系统之后,会动态探测当前主机上的硬件设备并调用与之对应的设备驱动再做成ramdisk文件的。所以,ramdisk文件并非必须的,如果只是为了将Linux安装于特定硬件平台上,就可以直接把对应的驱动编译进内核即可,而不需要去使用ramdisk文件了。
第五步:init管理用户空间服务进程
init可以理解成是内核派来管理用户空间的使者,就好像天使代表上帝来访问人间一样。init这个初始化程序会根据其配置文件执行一系列操作。虽然CentOS 5、CentOS 6以及CentOS 7的init配置 文件各不相同,但总体的启动流程是不变的。
这一步的流程是:/sbin/init --> 根据init配置文件设置默认运行级别 --> 运行系统初始化脚本/etc/rc.d/rc.sysinit,完成系统初始化 --> 关闭或启动用户选定的默认运行级别所对应的服务 --> 启动终端,打印登录提示符
(1)根据init配置文件设置默认运行级别
对于CentOS 5来说,初始化程序init是SysV init,其配置文件为:/etc/inittab;
对于CentOS 6来说,初始化程序init是upstart,其配置文件为:/etc/inittab, /etc/init/*.conf,也就是upstart将配置文件拆分成多个,在/etc/init/目录下以conf结尾的都是upstart风格的配置文件,而/etc/inittab仅用于设置默认运行级别;
对于CentOS 7来说,初始化程序init是systemd,其配置文件为:/usr/lib/system/systemd/*, /etc/systemd/system/*;
注意:
运行级别是为了系统的运行和维护等目的而设定的一种机制,对于CentOS 5/6来讲,一共有7种运行级别,即0-6,它们的意义如下:
0:关机模式,shutdown;
1:单用户模式(single user):不需要通过认证,登录进去之后为root用户身份,是维护模式;
2:多用户模式(multi user):会启动网络功能,但不会启动NFS,是维护模式;
3:多用户模式(multi user):为完全功能模式,提供文本界面;
4:预留级别,目前无特别使用目的,但习惯上以同3级别功能来使用;
5:多用户模式(multi user):为完全功能模式,提供图形界面;
6:重启模式,reboot;
CentOS 5配置文件详解:
每行定义一种action以及与之对应的process:
格式:
id
:runlevel:action:process
各字段解释:
id
:一个任务的标识符;
runlevel:在哪些级别下启动该任务;格式可以是
#,###,也可以为空(表示所有级别);
action:在什么条件下启动该任务;
process:任务;
action种类:
wait:等待切换至该任务所在的级别时执行一次;
respawn:一旦该任务终止,就会重新启动这个任务;
initdefault:设定默认运行级别;此时process是省略掉的;
sysinit:设定系统初始化方式,这里一般指定为
/etc/rc
.d
/rc
.sysinit脚本文件;
CentOS 6配置文件/etc/inittab如图:
CentOS 7配置文件/etc/inittab如图:
注意:CentOS 7使用target代替了runlevel,也就是在CentOS 7系列已经没有了运行等级这一概念了。而如果需要设置target则可参考该文件的后两行注释;
代码分析:
以下为CentOS 5的/etc/inittab文件
id
:3:initdefault:
//
设置默认运行级别,此处设置为3;
# System initialization.
si::sysinit:
/etc/rc
.d
/rc
.sysinit
//
指定系统初始化脚本;
//
根据前面获取到的运行级别信息,运行
/etc/rc
.d
/rc
脚本;
l0:0:wait:
/etc/rc
.d
/rc
0
l1:1:wait:
/etc/rc
.d
/rc
1
l2:2:wait:
/etc/rc
.d
/rc
2
l3:3:wait:
/etc/rc
.d
/rc
3
//
因为默认运行级别为3,因此只会开启或关闭
/etc/rc
.d
/rc3
.d/目录下的脚本所控制的服务;而其它
的
/etc/rc
.d
/rc
#.d/目录则别略过;
l4:4:wait:
/etc/rc
.d
/rc
4
l5:5:wait:
/etc/rc
.d
/rc
5
l6:6:wait:
/etc/rc
.d
/rc
6
(2)运行系统初始化脚本/etc/rc.d/rc.sysinit,完成系统初始化
运行初始化脚本是为了初始化系统环境,这一步初始化包括:
①设置主机名;
②设置欢迎信息;
③激活udev和selinux;
④挂载/etc/fstab文件中定义的所有文件系统;
⑤检测根文件系统,以读写方式重新挂载根文件系统;
⑥设置系统时钟;
⑦根据/etc/sysctl.conf文件来设置内核参数;
⑧激活lvm及软raid设备;
⑨激活swap设备;
⑩加载额外设备的驱动程序(因为内核只加载根文件系统所在分区的驱动程序);
清理操作;
注意:CentOS 7的systemd可能没有这一步,而是通过其他机制来实现。
(3)关闭或启动用户选定的默认运行级别下所对应的服务
要实现的功能是:根据前面用户通过init配置文件对默认运行级别的设定,关闭或启动这个运行级别下的服务。(注意:关闭在前,启动在后)
具体实现方式:运行/etc/rc.d/rc这个脚本文件,而初始化程序init根据前面获取的默认运行级别信息,将这个运行级别数字以参数方式传递给/etc/rc.d/rc脚本中的变量$runlevel;然后,这个脚本会以glob方式去把/etc/rc.d/rc$runlevel.d/S*匹配到的脚本所控制的服务开启,而把/etc/rc.d/rc$runlevel.d/K*匹配到的脚本所控制的服务开启。
/etc/rc.d/rc#.d/目录下的脚本文件命名格式:
(1)K
##*:表示要停止的服务;'##'表示关闭优先级,数字越小,越是优先关闭;依赖到其他服务的服
务会优先关闭,而被依赖的服务则后关闭;
(2)S
##*:表示要启动的服务;'##'表示启动优先级,数字越小,越是优先启动;被依赖的服务会优先
启动,而依赖到其他服务的服务则后启动;
例如,runlevel为3时,则匹配到的是/etc/rc.d/rc3.d/目录下的脚本文件;runlevel为5时,则匹配到的是/etc/rc.d/rc5.d/目录下的脚本文件等;而这些目录下的脚本有些是以S打头的,有些是以K打头的,这样的控制机制很容易想明白:其中以S打头的代表要开启的服务,以K打头的代表要关闭的服务。这就是我们的主机启动之后为什么有些服务会自动启动,而有些服务不会自动启动的原因了。
需要注意的是,在这些/etc/rc.d/rc#.d/目录下,除了S99local这个服务脚本之外,其他服务都链接到/etc/init.d/目录下对应的服务脚本,而S99local则链接到/etc/rc.d/rc.local(或/etc/rc.local)。所以,如果我们有不方便或者没必要写成服务脚本的程序,但又希望这样的程序能够在开机自动开启时,可以直接写进/etc/rc.d/rc.local这个脚本文件中就行了!
以下为/etc/rc.d/rc脚本文件代码节选:
# First, run the KILL scripts.
for
i
in
/etc/rc
$runlevel.d
/K
* ;
do
//
这是一个
for
循环,根据前面获取的运行级别来匹配并关闭
/etc/rc
#.d/目录下所有以K打头的脚本
所控制的服务;
check_runlevel
"$i"
||
continue
# Check if the subsystem is already up.
subsys=${i
#/etc/rc$runlevel.d/K??}
[ -f
/var/lock/subsys/
$subsys -o -f
/var/lock/subsys/
$subsys.init ] \
||
continue
# Bring the subsystem down.
if
LC_ALL=C
egrep
-q
"^..*init.d/functions"
$i ;
then
$i stop
//stop
,关闭服务!
else
action $
"Stopping $subsys: "
$i stop
fi
done
# Now run the START scripts.
for
i
in
/etc/rc
$runlevel.d
/S
* ;
do
//
这是一个
for
循环,根据前面获取的运行级别信息,匹配并开启
/etc/rc
#.d/目录下所有以S打头的
脚本所控制的服务;
check_runlevel
"$i"
||
continue
# Check if the subsystem is already up.
subsys=${i
#/etc/rc$runlevel.d/S??}
[ -f
/var/lock/subsys/
$subsys -o -f
/var/lock/subsys/
$subsys.init ] \
&&
continue
# If we're in confirmation mode, get user confirmation
if
[ -f
/var/run/confirm
];
then
confirm $subsys
test
$? = 1 &&
continue
fi
update_boot_stage
"$subsys"
# Bring the subsystem up.
if
[
"$subsys"
=
"halt"
-o
"$subsys"
=
"reboot"
];
then
export
LC_ALL=C
exec
$i start
fi
if
LC_ALL=C
egrep
-q
"^..*init.d/functions"
$i \
|| [
"$subsys"
=
"single"
-o
"$subsys"
=
"local"
];
then
$i start
//start
,开启服务!
else
action $
"Starting $subsys: "
$i start
fi
done
演示:
查看/etc/rc.d/目录下的所有S99local脚本:
[root@localhost ~]
# find /etc/rc.d/ -name S99local -exec ls -l {} \;
lrwxrwxrwx. 1 root root 11 Feb 20 06:11
/etc/rc
.d
/rc2
.d
/S99local
-> ..
/rc
.
local
lrwxrwxrwx. 1 root root 11 Feb 20 06:11
/etc/rc
.d
/rc5
.d
/S99local
-> ..
/rc
.
local
lrwxrwxrwx. 1 root root 11 Feb 20 06:11
/etc/rc
.d
/rc3
.d
/S99local
-> ..
/rc
.
local
lrwxrwxrwx. 1 root root 11 Feb 20 06:11
/etc/rc
.d
/rc4
.d
/S99local
-> ..
/rc
.
local
//
所有S99local服务脚本都链接至
/etc/rc
.d
/rc
.
local
;
[root@localhost ~]
# ll /etc/rc.local
lrwxrwxrwx. 1 root root 13 Feb 20 06:11
/etc/rc
.
local
-> rc.d
/rc
.
local
//
服务脚本
/etc/rc
.
local
链接至
/etc/rc
.d
/rc
.
local
;
查看/etc/rc.d/目录下的文件及目录:
[root@localhost ~]
# ls -1F /etc/rc.d/
init.d/
rc*
rc0.d/
rc1.d/
rc2.d/
rc3.d/
rc4.d/
rc5.d/
rc6.d/
rc.
local
*
rc.sysinit*
//
可以看到多个运行级别多对应的
/etc/rc
.d
/rc
#目录;
查看/etc/rc.d/rc0.d/目录下的服务脚本:
[root@localhost ~]
# ll /etc/rc.d/rc0.d/
total 320
lrwxrwxrwx 1 root root 17 Feb 20 06:08 K01dnsmasq -> ..
/init
.d
/dnsmasq
lrwxrwxrwx 1 root root 16 Feb 20 06:09 K01smartd -> ..
/init
.d
/smartd
lrwxrwxrwx 1 root root 22 Feb 20 06:20 K02avahi-daemon -> ..
/init
.d
/avahi-daemon
lrwxrwxrwx 1 root root 24 Feb 20 06:20 K02avahi-dnsconfd -> ..
/init
.d
/avahi-dnsconfd
.....(中间省略).....
lrwxrwxrwx 1 root root 25 Feb 20 06:09 K99readahead_early -> ..
/init
.d
/readahead_early
lrwxrwxrwx 1 root root 25 Feb 20 06:09 K99readahead_later -> ..
/init
.d
/readahead_later
lrwxrwxrwx 1 root root 17 Feb 20 06:08 S00killall -> ..
/init
.d
/killall
lrwxrwxrwx 1 root root 14 Feb 20 06:08 S01halt -> ..
/init
.d
/halt
//
因为运行级别0代表关机模式,因此统统都为以K打头的链接文件;
注意:CentOS 7采用的是systemd这个初始化程序,在系统启动过程中不会启动服务,而是在用户需要用到时则立即启动对应的服务。这是为了提高系统启动速度的一种非常精巧的设计。
(4)启动终端,打印登录提示符
根据前面获取的运行级别来启动终端,mingetty程序是用于启动终端的,它会调用登录程序login,这样就能显示出登录提示符了,类似mingetty这种用于打开终端的程序还有getty等。
而如果默认运行级别为5,则会打开图形界面。
以下为CentOS 5的/etc/inittab文件中部分代码:
# Run gettys in standard runlevels
1:2345:respawn:
/sbin/mingetty
tty1
2:2345:respawn:
/sbin/mingetty
tty2
3:2345:respawn:
/sbin/mingetty
tty3
4:2345:respawn:
/sbin/mingetty
tty4
5:2345:respawn:
/sbin/mingetty
tty5
6:2345:respawn:
/sbin/mingetty
tty6
//
在配置文件这里指明打开留个文本终端,分别对应于[Ctrl]+[Alt]+F1至F6;
# Run xdm in runlevel 5
x:5:respawn:
/etc/X11/prefdm
-nodaemon
第六步:用户登录
系统启动结束!
说了这么多,还是绘制成图更为直观吧!
技术链接