Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd

  • 0. 概述
  • 1. BIOS/UEFI 启动阶段
  • 2. 内核引导阶段
    • 2.1 分区类型
      • 2.1.1 MS-DOS
      • 2.1.2 GPT
    • 2.2 引导方式
      • 2.2.1 legacy + mbr/ms-dos + grub2
      • 2.2.2 uefi + grub2
  • 3. 内核启动阶段
  • 4. systemd 启动阶段
    • 4.1 sysinit.target
    • 4.2 basic.target
    • 4.3 initrd.target
    • 4.4 switch-root
    • 4.5 multi-user.target
  • 参考文档

0. 概述

从加电到出现登录界面,整个流程大致如下(基于 CentOS Linux release 8.2.2004 (Core)):
BIOS/UEFI ==> bootloader ==> kernel ==> initramfs ==> systemd(initramfs) ==> switch-root ==> systemd(real rootfs)

可以将上述过程粗略的划分为四个阶段:

  • BIOS/UEFI 启动阶段
  • 内核引导阶段
  • 内核启动阶段
  • 服务启动阶段

1. BIOS/UEFI 启动阶段

该阶段有两种引导方式:传统的 BIOS 模式(功能有限)以及UEFI 模式(功能丰富),核心功能大致是一些硬件启动前的检测,提供交互式界面来配置启动参数,加载 bootloader 到内存,并且将控制权交给 bootloader。

  • 启动方式
    • BIOS
      什么是 BIOS?BIOS 是机器加电后运行的第一个程序,保存在只读存储器(ROM)中。启动信息保存在 CMOS 芯片中,CMOS 是一个随机存储器(RAM)。
    • UEFI
      什么是 UEFI?UEFI 可以认为是增强版的 BIOS,能够识别文件系统,直接读取 bootloader 文件,而不用像 BIOS 一样,只能从启动盘第一个扇区读取 bootloader。
  • 主要功能
    • POST (Power On Self Test)
    • 加载 bootloader,并把控制权转交给 bootloader

vmware 虚拟机可以设置启动方式,虚拟机设置 > 选项 > 高级 > 固件类型,virtual box 也有类似设置:
Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第1张图片

2. 内核引导阶段

该阶段核心功能是加载内核镜像 vmlinuz 以及 initramfs 到内存,并将控制权交给内核。

根据安装系统时启动方式的不同,系统启动盘分区格式会有所不同,默认是 BIOS/MS-DOS 和 UEFI/GPT 组合。如果采用 BIOS 启动方式安装 OS,启动盘将采用 MS-DOS 分区格式,可以通过在安装参数里添加 inst.gpt 来使用 GPT 分区,该种方式下,必须存在 biosboot 分区;如果采用 UEFI 方式安装,启动盘将采用 GPT 分区格式,UEFI 启动方式下,必须存在 efi 分区。

2.1 分区类型

2.1.1 MS-DOS

  • MS-DOS 分区

    MBR(Master Boot Record, 主引导记录)。早期的 Linux 系统为了相容于 Windows 的磁盘,以支持 Windows 的 MBR 的方式来处理 bootloader 与 partion table。bootloader 与 partion table 都放在磁盘的第一个扇区,具体分布如下:
    Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第2张图片

    • 第一部分记录部分 bootloader,共446B

    • 第二部分记录分区表,共 64B,最多记录 4 个分区信息

    • 第三部分记录 MBR 标记,内容是 55AA,共 2 B

# hexdump 可以查看磁盘上指定地址的内容,下面是 BIOS 方式下启动盘前 512 字节的内容
[root@localhost rootfs]# hexdump -C -n 512 /dev/sda
00000000  eb 63 90 10 8e d0 bc 00  b0 b8 00 00 8e d8 8e c0  |.c..............|
00000010  fb be 00 7c bf 00 06 b9  00 02 f3 a4 ea 21 06 00  |...|.........!..|
00000020  00 be be 07 38 04 75 0b  83 c6 10 81 fe fe 07 75  |....8.u........u|
00000030  f3 eb 16 b4 02 b0 01 bb  00 7c b2 80 8a 74 01 8b  |.........|...t..|
00000040  4c 02 cd 13 ea 00 7c 00  00 eb fe 00 00 00 00 00  |L.....|.........|
00000050  00 00 00 00 00 00 00 00  00 00 00 80 01 00 00 00  |................|
00000060  00 00 00 00 ff fa 90 90  f6 c2 80 74 05 f6 c2 70  |...........t...p|
00000070  74 02 b2 80 ea 79 7c 00  00 31 c0 8e d8 8e d0 bc  |t....y|..1......|
00000080  00 20 fb a0 64 7c 3c ff  74 02 88 c2 52 be 05 7c  |. ..d|<.t...R..||
00000090  b4 41 bb aa 55 cd 13 5a  52 72 3d 81 fb 55 aa 75  |.A..U..ZRr=..U.u|
000000a0  37 83 e1 01 74 32 31 c0  89 44 04 40 88 44 ff 89  |7...t21..[email protected]..|
000000b0  44 02 c7 04 10 00 66 8b  1e 5c 7c 66 89 5c 08 66  |D.....f..\|f.\.f|
000000c0  8b 1e 60 7c 66 89 5c 0c  c7 44 06 00 70 b4 42 cd  |..`|f.\..D..p.B.|
000000d0  13 72 05 bb 00 70 eb 76  b4 08 cd 13 73 0d 5a 84  |.r...p.v....s.Z.|
000000e0  d2 0f 83 de 00 be 85 7d  e9 82 00 66 0f b6 c6 88  |.......}...f....|
000000f0  64 ff 40 66 89 44 04 0f  b6 d1 c1 e2 02 88 e8 88  |[email protected]..........|
00000100  f4 40 89 44 08 0f b6 c2  c0 e8 02 66 89 04 66 a1  |[email protected].......f..f.|
00000110  60 7c 66 09 c0 75 4e 66  a1 5c 7c 66 31 d2 66 f7  |`|f..uNf.\|f1.f.|
00000120  34 88 d1 31 d2 66 f7 74  04 3b 44 08 7d 37 fe c1  |4..1.f.t.;D.}7..|
00000130  88 c5 30 c0 c1 e8 02 08  c1 88 d0 5a 88 c6 bb 00  |..0........Z....|
00000140  70 8e c3 31 db b8 01 02  cd 13 72 1e 8c c3 60 1e  |p..1......r...`.|
00000150  b9 00 01 8e db 31 f6 bf  00 80 8e c6 fc f3 a5 1f  |.....1..........|
00000160  61 ff 26 5a 7c be 80 7d  eb 03 be 8f 7d e8 34 00  |a.&Z|..}....}.4.|
00000170  be 94 7d e8 2e 00 cd 18  eb fe 47 52 55 42 20 00  |..}.......GRUB .|
00000180  47 65 6f 6d 00 48 61 72  64 20 44 69 73 6b 00 52  |Geom.Hard Disk.R|
00000190  65 61 64 00 20 45 72 72  6f 72 0d 0a 00 bb 01 00  |ead. Error......|
000001a0  b4 0e cd 10 ac 3c 00 75  f4 c3 00 00 00 00 00 00  |.....<.u........|
000001b0  00 00 00 00 00 00 00 00  f3 bb 74 a1 00 00 80 04  |..........t.....|
000001c0  01 04 83 fe c2 ff 00 08  00 00 00 a0 0f 00 00 fe  |................|
000001d0  c2 ff 83 fe c2 ff 00 a8  0f 00 00 58 30 06 00 00  |...........X0...|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
  • MS-DOS 分区表有主分区、扩展分区和逻辑分区的概念:

    • 主分区和扩展分区最多只能有四个
    • 扩展分区最多只能有一个,可以没有
    • 逻辑分区建立在扩展分区之上
    • 主分区和逻辑分区可以创建文件系统,扩展分区不可以
  • MS-DOS 分区的缺陷:

    • 由于分区表位数的限制,OS 无法获取 2.2T 以上的磁盘容量

    • MBR只有一个扇区,且只有一份,破坏后,很难恢复

    • bootloader 只有 446 字节,无法容纳较多的程序代码

2.1.2 GPT

  • GPT 分区

    GPT 为了处理大硬盘,使用 LBA 编址,每个 LBA 512B。原来 MBR 占用的第一个 sector 现在是 LBA0。GPT 使用了 34 个 LBA 区块来纪录分区信息!与 MBR 仅有一的区块, GPT 除了前面 34 个 LBA 之外,整个磁盘的最后 33 个 LBA 也拿来作为另一个备份。具体结构如下:
    Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第3张图片

    • LBA0 (MBR 兼容区块) 与 MBR 模式相近,前 446B 还是 bootloader(UEFI 模式下,bootloader 不放在此部分),后 66B 换成一个特殊标记,用于表明此磁盘是 GPT 分区
    • LBA1 (GPT 表头记录) 记录分区表本身的位置与大小,备份分区放置的位置, 以及校验码 (CRC32)
    • LBA(2~33) (实际记录分区信息) 从 LBA2 区块开始,每个 LBA 都可以纪录 4 个分区纪录,所以在默认的情况下,总共可以有 4*32 = 128 个分区记录
    • LBA(-1) GPT 表头记录的备份
    • LBA(-33 ~ -2) 分区信息备份

2.2 引导方式

2.2.1 legacy + mbr/ms-dos + grub2

引导过程如下:
boot.img(mbr 446B) ==> diskboot.img ==> kernel.img,通过 /boot/grub2/grub.cfg 找到 vmlinuz 和 initramfs

上述 boot.img、diskboot.img 和 kernel.img 都是 bootloader的一部分,他们被硬编码到启动盘上,不是以文件的形式存在于文件系统中。
Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第4张图片

  • boot.img

    在 BIOS 平台下,boot.img 是 grub 启动的第一个 img 文件,它被写入到MBR中或分区的boot sector中,因为 boot sector 的大小是512字节,所以该 img 文件的大小也是 512 字节。

    boot.img 唯一的作用是读取属于 core.img 的第一个扇区并跳转到它身上,将控制权交给该扇区的 img。由于体积大小的限制,boot.img 无法理解文件系统的结构,因此 grub2-install 将会把 core.img 的位置硬编码到 boot.img 中,这样就一定能找到 core.img 的位置。

  • core.img

    core.img 根据 diskboot.img、kernel.img 和一系列的模块被 grub2-mkimage 程序动态创建。core.img 中嵌入了足够多的功能模块以保证 grub 能访问 /boot/grub2,并且可以加载相关的模块实现相关的功能,例如加载启动菜单、加载目标操作系统的信息等,由于 grub2 大量使用了动态功能模块,使得 core.img 体积变得足够小。

    core.img 中包含了多个 img 文件的内容,包括 diskboot.img/kernel.img 等。

    • diskboot.img

      如果启动设备是硬盘,即从硬盘启动时,core.img 中的第一个扇区的内容就是 diskboot.img。diskboot.img 的作用是读取 core.img 中剩余的部分到内存中,并将控制权交给 kernel.img,由于此时还不识别文件系统,所以将 core.img 的全部位置以 block 列表的方式编码,使得 diskboot.img 能够找到剩余的内容。

      该img文件因为占用一个扇区,所以体积为512字节。

    • kernel.img

      kernel.img 文件包含了 grub 的基本运行时环境:设备框架、文件句柄、环境变量、救援模式下的命令行解析器等等。很少直接使用它,因为它们已经整个嵌入到了 core.img 中了。注意,kernel.img 是 grub 的 kernel,和操作系统的内核无关。

      如果细心的话,会发现 kernel.img 本身就占用 28KB 空间,但嵌入到了 core.img 中后,core.img 文件才只有 26KB 大小。这是因为 core.img 中的 kernel.img 是被压缩过的。

  • *.mod

    各种功能模块,部分模块已经嵌入到 core.img 中,或者会被 grub 自动加载,但有时也需要使用 insmod 命令手动加载。

2.2.2 uefi + grub2

引导方式如下:
x86_64: uefi ==> shimx64.efi ==> grubx64.efi ==> /boot/efi/EFI/centos/grub.cfg ==> vmlinuz & initramfs
aarch64: uefi ==> shimaa64.efi ==> grubaa64.efi ==> /boot/efi/EFI/centos/grub.cfg ==> vmlinuz & initramfs

x86 架构系统中,shimx64.efi 和 grubx64.efi 是 UEFI 启动方式下的 bootloader,他们以文件的形式存在于磁盘的 /boot/efi/EFI/centos 目录下,UEFI 可以直接识别文件系统,并获取 shimx64.efi 加载到内存运行,然后 shimx64.efi 获取 grubx64.efi,grubx64.efi 则通过 grub.cfg 寻找 vmlinuz 和 initfamfs,并将其加载到内存中。arm 架构系统过程类似。

3. 内核启动阶段

内核启动过程比较复杂,未深入研究,以后有时间研究再整理。为了启动用户态服务,内核需要做的一些工作如下:

  • 解压 vmlinuz,运行内核镜像
  • 创建 rootfs,并挂在到 /,解压 initramfs 到 / (内存文件系统)
  • 执行 init 进程,此时的 init 为 initramfs 中的 init,在内存文件系统根目录下,指向 systemd

上述过程内核代码流程如下(基于 4.19.112 内核):

start_kernel() // init/main.c
|── vfs_caches_init()
|	└── mnt_init()
|		|── init_rootfs()
|		|	|── register_filesystem(&rootfs_fs_type)
|		|	└── init_ramfs_fs()
|		|		└── register_filesystem(&ramfs_fs_type)
|	    └── init_mount_tree()
|			|── init_task.nsproxy->mnt_ns = ns
|			|── set_fs_pwd(current->fs, &root)
|			└── set_fs_root(current->fs, &root)
└── rest_init()
	└── kernel_thread(kernel_init, NULL, CLONE_FS)
		└── kernel_init()
			|── kernel_init_freeable()
			|	|── do_basic_setup()
			|	|	└── do_initcalls() 
			|	|		└── populate_rootfs()
			|	|			└── unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start) 
			|	└── ramdisk_execute_command = "/init"
			└── run_init_process(ramdisk_execute_command)

initramfs 中的内容如下:

# initramfs 可以通过如下命令解压
[root@localhost rootfs]# /usr/lib/dracut/skipcpio /boot/initramfs-4.18.0-193.el8.x86_64.img | zcat | cpio -divm
[root@localhost rootfs]# ll
total 44
lrwxrwxrwx.  1 root root    7 Jul  3 19:13 bin -> usr/bin
drwxr-xr-x.  2 root root 4096 Jul  3 19:13 dev
drwxr-xr-x. 11 root root 4096 Jul  3 19:13 etc
lrwxrwxrwx.  1 root root   23 Jul  3 19:13 init -> usr/lib/systemd/systemd
lrwxrwxrwx.  1 root root    7 Jul  3 19:13 lib -> usr/lib
lrwxrwxrwx.  1 root root    9 Jul  3 19:13 lib64 -> usr/lib64
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 proc
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 root
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 run
lrwxrwxrwx.  1 root root    8 Jul  3 19:13 sbin -> usr/sbin
-rwxr-xr-x.  1 root root 3126 Oct  8  2018 shutdown
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 sys
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 sysroot
drwxr-xr-x.  2 root root 4096 Apr 24 11:24 tmp
drwxr-xr-x.  7 root root 4096 Jul  3 19:13 usr

从 initramfs 解压的内容中可以看出,这就是一个小型的根文件系统,其中,init 是指向 usr/lib/systemd/systemd 的软连接。

4. systemd 启动阶段

该阶段主要启动一些用户态服务。

由于大部分用户态服务涉及的可执行文件以及配置文件都在根文件系统中,需要挂载根文件系统;若要识别根文件系统,又需要加载磁盘驱动,而磁盘驱动也位于根文件系统中,形成死锁。

对于上述问题,主流 OS 发行版的做法都是通过 initramfs 方式,就是将一些必要的驱动和用户态工具打包到 initramfs 中,然后通过 initramfs 启动,必要的初始化后,再切换到真实的根文件系统,过程如下:

  • grub2 程序根据 grub.cfg 文件找到并加载 vmlinuz 和 iniramfs 到内存,并把地址告诉内核;
  • 内核自解压,并开始执行;
  • 内核启动到一定阶段,会在内存中创建根文件 rootfs,并将其挂载到 / 下;
  • 内核将 initramfs 解压到 / 下;
  • 内核执行 /init 即 systemd ;
  • systemd 拉起各种初始化服务,完成真实根文件系统的识别与挂载;
  • 切换到真实根文件系统,调用真实根文件系统的 systemd,重新启动 systemd 进程 ;
  • systemd 继续拉起各种用户服务,包括登录服务和终端服务等。

systemd 是通过 target 的方式来完成各种服务的启动,具体流程如下:
sysinit.target ==> basic.target ==> initrd.target ==> switch-root ==> multi-user.target

上述流程中可以看到关键的步骤 switch-root,switch-root 之前运行的服务都来自 initramfs,switch-root 之后运行的服务都来自真实根文件系统。

sysinit.target、basic.target 和 initrd.target 中涉及的一些 dracut 服务是启动过程中一些关键的启动点,可以通过在启动参数里添加 rd.break 启动参数,在这些点停下来,部分可以进入shell,具体可以查看 10. dracut.cmdline(7) — Linux manual page 。

switch-root 之前的初始启动日志会打印 Welcome to CentOS Linux 8 (Core) dracut-049-70.git20200228.el8 (Initramfs)!,该内容来自 initramfs 中的 /etc/initrd-release,systemd 也正是通过这个文件判断是否运行在 initramfs 中。
Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第5张图片
switch-root 之后启动日志中会打印 Welcome to CentOS Linux 8 (Core)!(过程太快,这里没有截到图,想从串口查看日志,vmware 设置串口没有成功)。

4.1 sysinit.target

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第6张图片
sysinit.target 主要完成一些初始化工作。

  • dracut-cmdline.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-cmdline.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut cmdline hook
Documentation=man:dracut-cmdline.service(8)
DefaultDependencies=no
Before=dracut-pre-udev.service
After=systemd-journald.socket
Wants=systemd-journald.socket
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cmdline
ConditionKernelCommandLine=|rd.break=cmdline
ConditionKernelCommandLine=|resume
ConditionKernelCommandLine=|noresume
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-cmdline
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
  • dracut-pre-udev.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-pre-udev.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut pre-udev hook
Documentation=man:dracut-pre-udev.service(8)
DefaultDependencies=no
Before=systemd-udevd.service dracut-pre-trigger.service
After=dracut-cmdline.service
Wants=dracut-cmdline.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-udev
ConditionKernelCommandLine=|rd.break=pre-udev
ConditionKernelCommandLine=|rd.driver.blacklist
ConditionKernelCommandLine=|rd.driver.pre
ConditionKernelCommandLine=|rd.driver.post
ConditionPathExistsGlob=|/etc/cmdline.d/*.conf
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-udev
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
  • dracut-pre-trigger.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-pre-trigger.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut pre-trigger hook
Documentation=man:dracut-pre-trigger.service(8)
DefaultDependencies=no
Before=systemd-udev-trigger.service dracut-initqueue.service
After=dracut-pre-udev.service systemd-udevd.service systemd-tmpfiles-setup-dev.service
Wants=dracut-pre-udev.service systemd-udevd.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger
ConditionKernelCommandLine=|rd.break=pre-trigger
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-trigger
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
  • drauct-initqueue.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-initqueue.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut initqueue hook
Documentation=man:dracut-initqueue.service(8)
DefaultDependencies=no
Before=remote-fs-pre.target
Wants=remote-fs-pre.target
After=systemd-udev-trigger.service
Wants=systemd-udev-trigger.service
ConditionPathExists=/usr/lib/initrd-release
ConditionPathExists=|/lib/dracut/need-initqueue
ConditionKernelCommandLine=|rd.break=initqueue
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-initqueue
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP

4.2 basic.target

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第7张图片

4.3 initrd.target

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第8张图片

  • dracut-pre-mount.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-pre-mount.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut pre-mount hook
Documentation=man:dracut-pre-mount.service(8)
DefaultDependencies=no
Before=initrd-root-fs.target sysroot.mount systemd-fsck-root.service
After=dracut-initqueue.service cryptsetup.target
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-mount
ConditionKernelCommandLine=|rd.break=pre-mount
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
  • dracut-mount.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-mount.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut mount hook
Documentation=man:dracut-mount.service(8)
After=initrd-root-fs.target initrd-parse-etc.service
After=dracut-initqueue.service dracut-pre-mount.service
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/mount
ConditionKernelCommandLine=|rd.break=mount
DefaultDependencies=no
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP

4.4 switch-root

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第9张图片
该阶段主要完成 root 的切换,并将控制权交给新的 systemd 进程:

  • dracut-pre-pivot.service
# /usr/lib/systemd/system/../../dracut/modules.d/98dracut-systemd/dracut-pre-pivot.service
#  This file is part of dracut.
#
# See dracut.bootup(7) for details

[Unit]
Description=dracut pre-pivot and cleanup hook
Documentation=man:dracut-pre-pivot.service(8)
DefaultDependencies=no
After=initrd.target initrd-parse-etc.service sysroot.mount
After=dracut-initqueue.service dracut-pre-mount.service dracut-mount.service
Before=initrd-cleanup.service
Wants=remote-fs.target
After=remote-fs.target
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-pivot
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cleanup
ConditionKernelCommandLine=|rd.break=pre-pivot
ConditionKernelCommandLine=|rd.break=cleanup
ConditionKernelCommandLine=|rd.break
ConditionPathExists=|/dev/root
ConditionPathExists=|/dev/nfs
Conflicts=shutdown.target emergency.target

[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-pivot
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes

# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
  • initrd-cleanup.service
# /usr/lib/systemd/system/initrd-cleanup.service
#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Cleaning Up and Shutting Down Daemons
DefaultDependencies=no
ConditionPathExists=/etc/initrd-release
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
After=initrd-root-fs.target initrd-fs.target initrd.target

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl --no-block isolate initrd-switch-root.target
  • initrd-switch-root.service
# /usr/lib/systemd/system/initrd-switch-root.service
#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Switch Root
DefaultDependencies=no
ConditionPathExists=/etc/initrd-release
OnFailure=emergency.target
OnFailureJobMode=replace-irreversibly
AllowIsolate=yes

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl --no-block switch-root /sysroot

4.5 multi-user.target

Linux 启动流程 -- BIOS/UEFI & bootloader & kernel & initramfs & systemd_第10张图片

  • systemd-logind.service
# /usr/lib/systemd/system/systemd-logind.service
#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Login Service
Documentation=man:systemd-logind.service(8) man:logind.conf(5)
Documentation=https://www.freedesktop.org/wiki/Software/systemd/logind
Documentation=https://www.freedesktop.org/wiki/Software/systemd/multiseat
Wants=user.slice
After=nss-user-lookup.target user.slice

# Ask for the dbus socket.
Wants=dbus.socket
After=dbus.socket

[Service]
ExecStart=/usr/lib/systemd/systemd-logind
Restart=always
RestartSec=0
BusName=org.freedesktop.login1
WatchdogSec=3min
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_KILL CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP>
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK
RestrictSUIDSGID=yes
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
LockPersonality=yes
FileDescriptorStoreMax=512

# Increase the default a bit in order to allow many simultaneous
# logins since we keep one fd open per session.
LimitNOFILE=16384
  • getty.target
# /usr/lib/systemd/system/getty.target
#  SPDX-License-Identifier: LGPL-2.1+
#
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Login Prompts
Documentation=man:systemd.special(7) man:systemd-getty-generator(8)
Documentation=http://0pointer.de/blog/projects/serial-console.html

至此,从加电到登录界面 Linux 的整个启动流程梳理完毕,只是粗略的介绍了大致流程,整个启动过程非常复杂,很多细节还没有搞清楚,所以没有展开。

参考文档

  • 1. UEFI 启动:实际工作原理

  • 2. DOS分区表(Boot Sector引导扇区)

  • 3. Linux磁盘管理——MBR 与 GPT

  • 4. grub2 详解(翻译和整理官方手册)

  • 5. linux内核启动流程(文章最后流程图)

  • 6. Linux内核Ramdisk(initrd)机制

  • 7. dracut.bootup(7) — Linux manual page

  • 8. bootup(7) — Linux manual page

  • 9. boot(7) — Linux manual page

  • 10. dracut.cmdline(7) — Linux manual page

  • 11. 鳥哥的 Linux 私房菜 - 基礎學習篇

  • 12. 金步国 systemd 中文手册

你可能感兴趣的:(内核学习)