CentOS7启动流程

哈~结束了systemd和GRUB2的了解, 我们就可以更新之前CentOS5&6启动流程啦!

进入系统前

关于从我们摁下电源到BIOS legacy到bootloader的过程, 是没有什么变化的, 所以可以参考前面说过的: BIOS到BootLoader

这次又学习到了一点更细致更具体的, 所以再来谈一谈开机流程这个事情.

传统BIOS开机流程

我们按下主机的电源键, 接着储存在主板上的EEPROM中的BIOS就会开始下面的工作:

初始化

每当我们电脑打开, CPU就会自动重置成初始状态, 准备工作. BIOS boot block(开机区块)初始化阶段开始启动. 因为此时我们的内存啥的都是空的, 没有内容可以执行, 所以主板厂商会让CPU去寻找系统BIOS ROM中的reset vector(重置向量): 也就是用一个固定的位置来启动所谓的BIOS boot program开机程序.

一般来说, 我们的这个程序会出现在内存的0xFFFF的位置, 当然里面其实只有一个jump指令, 进一步真正的BIOS启动程序. 各个BIOS供应商也可以把程序放在不同的位置, 只要通过jump来指定就可以了.

POST(Power On Self Test开机自我检测)

接着BIOS就会开始实行POST, 在过程中检查电脑的各项组件及设定. 然后再物理内存的开头处构建实模式的中断向量表, 这样我们就可以通过特定的中断来寻找硬件了. 接着是BIOS的数据区 以及 中断程序(仅仅是用于实模式的, 在后面加载操作系统的时候就会被抹除.)

这些工作都是硬件层面设计好的 和操作系统没有一点关系.

寻找操作系统

OK~到了这个阶段, BIOS就会根据使用者的设定来决定搜索顺序, 产生INT_19来让CPU执行相应的中断服务程序. 这个程序把启动盘的第一扇区加载到物理内存的0x0700处, 这个也是硬件厂商设定的(因为不知道用户会安装什么操作系统). 这个扇区就是我们的引导程序所在的地方了. 接着就是我们的GRUB来做了. BIOS的使命结束.

UEFI 搭配 GPT

在这里顺便更新一下这种较新的组合是怎么搞得.

先来对比一下, 传统BIOS( BIOS legacy )和UEFI的区别.

比较项目 传统BIOS UEFI
使用语言 组合语言 C 语言
硬件资源控制 使用中断(IRQ)管理不可变的内存存取不可变的输入/输出存取 使用驱动程序与协定
处理器运行环境 16位 CPU 保护模式
扩充方式 透过IRQ 连结 直接载入驱动程式
第三方厂商支援 较差 较佳且可支持多平台
图形化能力 较差 较佳
内建简化作业系统前环境 不支持 支持

可以看出来, 这个UEFI要比传统的BIOS高级很多, 从WikiPedia中摘录一段, 我觉得写的挺清楚:

(UEFI)突破传统16位代码的寻址能力,达到处理器的最大寻址。它利用加载EFI驱动程序的形式,识别及操作硬件,不同于BIOS利用挂载真实模式中断的方式增加硬件功能。后者必须将一段类似于驱动程序的16位代码(如RAID卡的Option ROM)放置在固定的0x000C0000至0x000DFFFF之间存储区中,运行这段代码的初始化部分,它将挂载实模式下约定的中断向量向其他程序提供服务。例如,VGA图形及文本输出中断(INT 10h),磁盘访问中断服务(INT 13h)等等。由于这段存储空间有限(128KB),BIOS对于所需放置的驱动程序代码大小超过空间大小的情况无能为力。

另外,BIOS的硬件服务程序都以16位代码的形式存在,这就给运行于增强模式的操作系统访问其服务造成了困难。因此BIOS提供的服务在现实中只能提供给操作系统引导程序或MS-DOS类操作系统使用。而UEFI系统下的驱动程序可以由EFI Byte Code(EBC)编写而成,EFI Byte Code是一组专用于EFI驱动程序的虚拟机器语言,必须在EFI驱动程序运行环境(Driver Execution Environment,或DXE)下被解释运行。

加上EFI driver开发简单,所有的PC零组件厂商都可以参与,就像现代操作系统的开发模式,这样的模式曾使Windows系统短短几年就变得无比强大. 有了EFI driver,也可以让显卡在开机阶段就载入某种程度的功能,进而可以把传统文字界面为主的BIOS转成图形界面。

至于GPT, 是GUID Partition Table的缩写, 而GUID就是全局唯一标识符的简称. 下面的一张图就把GPT的状态说的很清楚啦.

**LBA是指逻辑区块地址 **

GPT

尽管GPT对分区数量没有限制 但是Windows最大就支持128个分区. 至于为什么是128,这是这是因为EFI标准的最小值.

最后补充一个小点, 我们的BIOS设定中由一个secure boot的选项, 这个选项是为了UEFI的安全, 开启后就只会运行经过事先写过的数字签名的验证的程序. 但是这一点遭到了RedHat的反对, 因为大部分的Windows用户的电脑上主板上仅仅写入了OEM和微软的数字签名, 这对Linux的安装就造成了麻烦. 尽管说会提供这个值的修改设置…但是你懂得

进入系统

载入内核和initramfs

虽然这个部分在之前还是说过了, 但是还是再提一遍…因为一些内容是新学到的~

我们通过 Bootloader的管理而开始读取内核的时候, 接下来, 整个内核就会被解压到物理内存中. 接着利用内核的功能来测试周边装置, 也就是各个硬件啦 储存装置,CPU,网卡,声卡等等. 其实这个时候这些硬件的信息已经有了, 这是BIOS扫描得到的. 但是我们的Linux内核是不相信这个结果的, 所以要再次扫描一遍.

有关的文件都放在/boot下了:

1
2
3
4
5
6
7
8
9
10
[root@WWW ~]# ls --format=single-column -F /boot 
config-3.10.0-229.el7.x86_64 <==此版本内核被编译时选择的功能与模块设定文件
grub/ < ==旧版grub1 没啥用了
grub2/ <==就是开机管理程序grub2相关目录
initramfs-0-rescue-309eb890d3d95ec7a.img <==下面几个都是虚拟文件系统 这一个是用来救援的
initramfs-3.10.0-229.el7.x86_64.img <==正常开机会用到的虚拟文件系统
initramfs-3.10.0-229.el7.x86_64kdump.img <==内核出问题时会用到的虚拟文件系统
System.map-3.10.0-229.el7.x86_64 <==内核功能放置到内存地址的对应表
vmlinuz-0-rescue-309eb890d09543d95ec7a* <==救援用的内核
vmlinuz-3.10.0- 229.el7.x86_64* <==这就是内核啦

我们知道Linux内核是可以动态加载模块的, 模块在哪里呢? 在我们的/lib/module/$VERSION下面.所以我们也可以自信的说他们在根目录下, 因此为了开机就一定要挂载根目录, 而担心影响到磁盘中的文件系统, 这个根会用只读的方式挂载.

接着又回到了那个难题. 如果你的linux是安装在SATA磁盘上的, 那么通过INT_13取得的Bootloader与kernel来开机, 接着kernel就会开始扫描硬件,尝试挂载根目录. 但是内核无法获得SATA磁盘, 所以需要驱动程序. 但是驱动程序在根目录下, 这就是那个经典的两难问题.

解决方法就是上面的虚拟文件系统. 跟着Bootloader一起载入, 接着在内存中模拟成根目录, 并且提供一堆可执行程序(通常是USB,RAID,LVM啥的驱动程序), 载入模块.

那么这个神奇的initramfs究竟有什么东西呢? 之前我一直傻傻的用cpio在复原, 其实有一个命令专门用来输出这个文件的内容的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[root@WWW boot]# lsinitrd initramfs-3.10.0-514.21.2.el7.x86_64.img | less


Image: initramfs-3.10.0-514.21.2.el7.x86_64.img: 19M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x 3 root root 0 Jun 25 08:40 .
-rw-r--r-- 1 root root 2 Jun 25 08:40 early_cpio
drwxr-xr-x 3 root root 0 Jun 25 08:40 kernel
drwxr-xr-x 3 root root 0 Jun 25 08:40 kernel/x86
drwxr-xr-x 2 root root 0 Jun 25 08:40 kernel/x86/microcode
-rw-r--r-- 1 root root 97280 Jun 25 08:40 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-033-463.el7_3.1
...(omitted)
drwxr-xr-x 3 root root 0 Jun 25 08:40 usr/lib/modules
drwxr-xr-x 3 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64
drwxr-xr-x 8 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel
drwxr-xr-x 3 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/arch
drwxr-xr-x 3 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/arch/x86
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/arch/x86/crypto
-rw-r--r-- 1 root root 21581 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/arch/x86/crypto/crc32c-intel.ko
-rw-r--r-- 1 root root 12037 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/arch/x86/crypto/crct10dif-pclmul.ko
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/crypto
-rw-r--r-- 1 root root 5021 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/crypto/crct10dif_common.ko
-rw-r--r-- 1 root root 6229 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/crypto/crct10dif_generic.ko
drwxr-xr-x 12 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata
-rw-r--r-- 1 root root 68437 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/ahci.ko
-rw-r--r-- 1 root root 13797 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/ata_generic.ko
-rw-r--r-- 1 root root 52829 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/ata_piix.ko
-rw-r--r-- 1 root root 53245 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/libahci.ko
-rw-r--r-- 1 root root 410141 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/libata.ko
-rw-r--r-- 1 root root 12685 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/ata/pata_acpi.ko
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/block
-rw-r--r-- 1 root root 27885 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/block/virtio_blk.ko
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/cdrom
-rw-r--r-- 1 root root 69893 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/cdrom/cdrom.ko
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/char
-rw-r--r-- 1 root root 53709 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/char/virtio_console.ko
drwxr-xr-x 2 root root 0 Jun 25 08:40 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/firmware
-rw-r--r-- 1 root root 15893 Jun 20 09:11 usr/lib/modules/3.10.0-514.21.2.el7.x86_64/kernel/drivers/firmware/iscsi_ibft.ko
...(omitted)

输出很庞大. 通过观察输出, 我们可以知道这个文件前面是一个文档宣告, 但是后面才是真正用到的部分. 里面有个init程序, 已经给systemd管理了:

1
lrwxrwxrwx   1 root     root           23 Jun 25 08:40 init -> usr/lib/systemd/systemd

之前说过, systemd的默认target取决于default.target这个软链:

1
lrwxrwxrwx   1 root     root           13 Jun 25 08:40 usr/lib/systemd/system/default.target -> initrd.target

那么这个initrd.target就是开机启动的第一个target了~ 怎么启动的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
initrd.target
● ├─dracut-cmdline.service
● ├─dracut-initqueue.service
● ├─dracut-mount.service
● ├─dracut-pre-mount.service
● ├─dracut-pre-pivot.service
● ├─dracut-pre-trigger.service
● ├─dracut-pre-udev.service
● ├─initrd-parse-etc.service
● ├─basic.target
● │ ├─firewalld.service
● │ ├─microcode.service
● │ ├─rhel-autorelabel-mark.service
● │ ├─rhel-autorelabel.service
● │ ├─rhel-configure.service
● │ ├─rhel-dmesg.service
● │ ├─rhel-loadmodules.service
● │ ├─selinux-policy-migrate-local-changes@targeted.service
● │ ├─paths.target
● │ ├─slices.target
● │ │ ├─-.slice
● │ │ └─system.slice
● │ ├─sockets.target
● │ │ ├─dbus.socket
● │ │ ├─dm-event.socket
● │ │ ├─systemd-initctl.socket
● │ │ ├─systemd-journald.socket
● │ │ ├─systemd-shutdownd.socket
● │ │ ├─systemd-udevd-control.socket
● │ │ └─systemd-udevd-kernel.socket
● │ ├─sysinit.target
....(omitted)

这样, 在内核完整载入完毕之后, 就是我们的systemd的主场了!

进入系统之后

接下来系统连接到/usr/lib/systemd/system这个目录去取按照用户的设定, 读取default.target所指向的目标target(注意呀,刚刚上面说的那个default是由系统生成的根文件系统, 和我们进入系统的那个不是一回事, 所以两个default.target不一样的), 读取完了之后, 就会去下面的两个地方,加载设定

  • /etc/systemd/system/XXX(你设置的).target.wants/:使用者设定载入的unit
  • /usr/lib/systemd/system/XXX.target.wants/:系统预设载入的unit

一直这样XXX不是个事, 一般都是multi-user吧, 所以就拿这个好了.

我们来看看multi-user.target:

1
2
3
4
5
6
7
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes

也就是说, 在必须在basic.target运行完毕之后才能载入…载入什么呢? 我们来看看对应的wants文件夹:

1
2
3
4
[root@WWW system]# ls /etc/systemd/system/multi-user.target.wants/
atd.service crond.service mdmonitor.service remote-fs.target sysstat.service vboxadd-service.service
auditd.service irqbalance.service NetworkManager.service rsyslog.service tuned.service vboxadd-x11.service
chronyd.service kdump.service postfix.service sshd.service vboxadd.service

因为我使用的是Virtual Box来着, 所以可以忽略一些

系统加载的unit有这些:

1
2
3
[root@WWW system]# ls /usr/lib/systemd/system/multi-user.target.wants/
brandbot.path getty.target plymouth-quit-wait.service systemd-logind.service systemd-user-sessions.service
dbus.service plymouth-quit.service systemd-ask-password-wall.path systemd-update-utmp-runlevel.service

简单分析一下systemctl list-dependencies multi-user.target所输出的依赖服务,基本上我们CentOS 7.x 的systemd 开机流程大约是这样:

  1. local-fs.target + swap.target:这两个target 主要在挂载本机/etc/fstab 里面所规范的文件系统与相关的内存置换空间
  2. sysinit.target:这个target 主要在扫描硬件,载入所需要的内核模块等
  3. basic.target:载入主要的周边硬件驱动程序与防火墙相关任务
  4. multi-user.target 底下的其它一般系统或网络服务的载入

第一个步骤的那两个target, 很好理解 只要根据fstab就可以了. 那后面呢? 我们可以查看一下他们所启动的服务就知道了.

sysinit.target

基本上,我们可以将这些服务归类成几个大项就是了:

  • 特殊文件系统装置的挂载:包括dev-hugepages.mount dev-mqueue.mount 等挂载服务,主要在挂载跟巨量内存分页使用与信息队列的功能。挂载成功后,会在/dev 底下建立/dev/hugepages/, /dev/mqueue/ 等目录;
  • 特殊文件系统的启用:包括磁盘阵列(RAID)、网路磁盘(iscsi)、LVM、文件系统对照服务(multipath) 等等,也会在这里被扫描和使用.
  • 开机过程的信息传递与动画执行:使用plymouthd 服务搭配plymouth 指令来传递动画与信息
  • 登录日志的使用:就是systemd-journald 这个服务的启用.
  • 载入额外的内核模块:透过/etc/modules-load.d/*.conf 文件的设定,让内核额外载入所需要的内核模块.
  • 载入额外的内核参数设定:包括/etc/sysctl.conf 以及/etc/sysctl.d/*.conf 内部设定
  • 启动系统的随机产生器:随机产生器可以帮助系统进行一些密码加密演算的功能
  • 设定终端(console)字体
  • 启动动态设备管理服务:就是udevd 啦 用在动态对应实际设备存取与设备档名对应的一个服务.

不论你即将使用哪种操作环境来使用系统,这个sysinit.target 几乎都是必要的工作 从上面你也可以看的出来,基本的内核功能、文件系统、文件系统硬件的驱动等等, 都在这个时刻处理完毕~所以,这个sysinit.target 的阶段是挺重要.

basic.target

执行完sysinit.target 之后,再来则是basic.target 这个项目了.

sysinit.target 在初始化系统,而basic.target 的阶段主要启动的服务大概有这些:

  • 载入firewalld 防火墙:CentOS 7.x 以后使用firewalld 取代iptables 的防火墙设定,虽然最终都是使用iptables 的架构, 不过在设定上面差很多.
  • 载入CPU 的微指令功能;
  • 启动与设定SELinux 的安全上下文:如果由disable 的状态改成enable 的状态,或者是管理员设定强制重新设定一次SELinux 的安全上下
  • 将目前的开机过程所产生的开机信息写入到/var/log/dmesg 当中
  • 由/etc/sysconfig/modules/*.modules 及/etc/rc.modules 载入管理员指定的模块
  • 载入systemd 支持的timer 功能;

系统启动完毕

这样就差不多了, 接下来就是关于模块的一些内容 关于这个 和之前的就没有什么不同了, 参考这个地方就可以了: 内核管理