Arch 的启动流程
为了启动 Arch Linux,必须配置一个与 Linux 兼容的引导加载程序。引导加载程序负责在初始化启动进程之前,加载好内核和 initramfs,具体过程因 BIOS 和 UEFI 系统而异。
固件类型
固件是开机时最先执行的程序。
- 本文时常以 BIOS 和 UEFI 代称固件。
- 勿与 Linux 固件混淆。
UEFI
统一可扩展固件接口(Unified Extensible Firmware Interface,UEFI)支持读取分区表和文件系统。UEFI 不从主引导记录(MBR)中启动任何引导代码(无论其是否存在),相反,UEFI 的启动过程依赖非易失性随机访问存储器(NVRAM)中的引导条目。
UEFI 规范要求支持 FAT12、FAT16 和 FAT32 文件系统(参见 UEFI 规范 2.11 版 13.3.1.1 小节),但每个符合规范的厂商可以选择添加对其它文件系统的支持;比如,苹果的固件支持 HFS+ 或 APFS 文件系统。UEFI 的一些实现方案还支持光盘的 ISO 9660 文件系统。
UEFI 会启动 EFI 应用程序,例如引导加载程序、引导管理器和 UEFI Shell 等等。这些应用程序通常以文件形式存储在 EFI 系统分区中。厂商可以将其特定文件存储在 EFI 系统分区中的 /EFI/vendor_name 文件夹下。应用程序可以通过在 NVRAM 中添加引导项或从 UEFI shell 中启动。
UEFI 规范通过兼容性支持模块(Compatibility Support Module,CSM)来支持传统 BIOS 引导。如果在 UEFI 中启用了 CSM,UEFI 会为所有驱动器生成 CSM 引导项。如果选择从某一个 CSM 引导项启动,UEFI 的 CSM 会尝试从这个磁盘的 MBR 引导代码启动。
BIOS
BIOS,又称基本输入输出系统(Basic Input-Output System),大多数情况下储存在主板自身的一块闪存内,独立于其它系统存储。其最早是为 IBM PC 开发,用于处理硬件初始化和启动过程。从 2010 年起已逐渐被技术上没有类似限制的 UEFI 替换。
系统初始化
系统上电时,会执行加电自检(Power-on self-test,POST)。详细信息可参考 Hugo Landau 的 Modern CPUs have a backstage cast 一文。
在 UEFI 下的情况
- 加电自检后,UEFI 初始化引导所需的硬件(硬盘、键盘控制器等等)。
- 固件读取 NVRAM 中的引导项,以决定要启动哪一个 EFI 应用程序,以及从哪启动(比如从哪一个硬盘和分区)。
- 一个引导项可能对应的只是一块硬盘。在这种情况下,固件会寻找硬盘上的 EFI 系统分区,并尝试在后备引导路径
\EFI\BOOT\BOOTx64.EFI处(在 IA32(32 位)UEFI 的系统上为BOOTIA32.EFI)查找 EFI 应用程序。这就是UEFI 可引导可移除介质的工作原理。
- 一个引导项可能对应的只是一块硬盘。在这种情况下,固件会寻找硬盘上的 EFI 系统分区,并尝试在后备引导路径
- 固件启动 EFI 应用程序。
- 这可以是一个#引导加载程序,或者是使用 EFISTUB 的 Arch 内核本体。
- 还可以是一些其他的 EFI 应用程序,比如 UEFI shell 或引导管理器(例如 systemd-boot 或 rEFInd)。
如果启用了安全启动,启动过程将会通过签名验证 EFI 二进制文件的真实性。
多重引导
由于每个操作系统或厂商都可以维护自己在 EFI 系统分区中的文件,同时不影响其他系统,所以 UEFI 的多重引导的原理就是启动不同的、与特定操作系统引导加载程序所对应的 EFI 应用程序。这避免了依赖一个#引导加载程序去加载另一个操作系统的链式加载机制。
另请参阅 Arch + Windows 双系统。
在 BIOS 下的情况
- 上电自检后,BIOS 初始化引导所需的硬件(硬盘、键盘控制器等等)。
- BIOS 启动在“BIOS 硬盘顺序”中第一块硬盘上的前 440 字节代码(即主引导记录引导代码区域)。
- 引导加载程序在 MBR 引导代码的第一阶段,之后会从下列任意一处启动第二阶段代码(如果有的话):
- 真正的#引导加载程序启动。
- 随后,引导加载程序通过链式加载或直接加载操作系统内核的方式加载操作系统。
引导加载程序
引导加载程序(boot loader,又称引导加载器、启动加载器或启动引导器)是由计算机固件(BIOS 或 UEFI)启动的软件,负责用指定内核参数加载内核和其它外置 initramfs 映像。
引导管理器(boot manager,又称启动管理器)让用户使用启动选项菜单或其他方式控制启动过程——也就是说,仅用于运行其他 EFI 应用程序。
在 UEFI 的情况下,内核本身可以由 UEFI 使用 EFI boot stub 直接启动。要在引导前编辑内核参数,可以使用引导管理器或是单独的引导加载程序。
使用 32 位 IA32 UEFI 固件的系统需要使用支持混合启动模式的引导加载程序。
/boot 目录下的内核和 initramfs 映像才能成功引导 Arch 系统。也就是说,引导加载程序必须解决从块设备、堆叠块设备(LVM、RAID、dm-crypt、LUKS 等)开始,到内核和 initramfs 映像所在文件系统为止的访问。
因为几乎没有引导加载程序支持堆叠块设备,并且文件系统引入的一些新特性可能尚未有任何引导加载程序支持(例如 archlinux/packaging/packages/grub#7、FS#79857、FS#59047、FS#58137、FS#51879、FS#46856、FS#38750、FS#21733 和 fscrypt 加密目录),所以用广泛支持的文件系统(例如 FAT32)单独创建 /boot 分区通常更可行。
功能比较
- 由于 GPT 是 UEFI 规范的一部分,因此所有的 UEFI 引导加载程序都支持 GPT 磁盘。在 BIOS 上使用 GPT 磁盘是可行的,可以使用 Hybrid MBR 的“混合引导(hybrid booting)”,或者使用新的纯 GPT 协议。但是这个协议可能在某些 BIOS 实现上会出问题,详情请参考 Rodsbooks。
- 作为 UEFI 规范的一部分,所有 UEFI 引导加载程序都支持安全启动,但有可能会存在一些限制。
| 名称 | 固件 | 分区表 | 多重引导 | 文件系统 | 注意 | ||
|---|---|---|---|---|---|---|---|
| BIOS | UEFI | MBR | GPT | ||||
| Clover | 是 | 是 | 否 | 是 | 是 | 可扩展2,5 | 可以在过时 BIOS 系统上模拟 UEFI。 |
| EFI boot stub | – | 是1 | 是 | 是 | – | 继承自固件2 | 内核是有效的 EFI 可执行文件,可直接从 UEFI 或其它 UEFI 引导加载器启动。 |
| GRUB | 是 | 是3 | 是 | 是 | 是 | 内置 | 支持 RAID,LUKS(Argon2 PBKDFs 除外)和 LVM(但是不支持精简配置卷)。平台相关限制请参考 GRUB。 |
| Limine | 是 | 是3 | 是 | 是 | 是 | 有限 | |
| rEFInd | 否 | 是 | 是 | 是 | 是4 | 可扩展2,5 | 支持自动检测内核和参数,无需明确配置,并支持快速启动[2]。 |
| Syslinux | 是 | 部分1 | 是 | 是 | 部分 | 有限 | 不支持某些文件系统功能。 只能访问自身所处的文件系统。 |
| systemd-boot | 否 | 是3 | 手动 | 是 | 是4 | 可扩展2,5 | 无法从 ESP 或扩展引导加载程序分区(XBOOTLDR)以外的分区启动二进制文件。 支持自动检测放入 esp/EFI/Linux/ 的统一内核映像。
|
| 统一内核映像 | – | 是3 | 是 | 是 | – | 继承自固件2 | systemd-stub(7)、内核、initramfs、内核命令行打包而成的 EFI 可执行文件,可直接从 UEFI 固件或另一个引导加载程序加载。 |
| GRUB Legacy | 是 | 否 | 是 | 否 | 是 | 有限 | 停止开发,转为 GRUB。 |
| LILO | 是 | 否 | 是 | 部分 | 是 | 有限 | 因为局限性(如与 Btrfs、GPT 和 RAID 搭配使用时)已停止开发。 |
- 虽然二进制文件可以被签名用于安全启动,但它不会进行后续验证,从而破坏了信任链。
- 文件系统支持是从固件继承的。UEFI 规范要求支持 FAT12,FAT16 和 FAT32 文件系统,但厂商可选择添加对其他文件系统的支持。比如说,苹果 Mac 中的固件支持 HFS+ 文件系统。如果固件提供在启动时加载 UEFI 驱动程序的接口,则可以通过加载文件系统驱动程序(需单独获取)的方式添加对其他文件系统的支持。
- 支持混合模式启动,即可以在 32 位 IA32 UEFI 固件上启动 64 位 x86_64 Linux 内核
- 一种启动管理器。它只能启动其他的 EFI 应用程序,例如,使用
CONFIG_EFI_STUB=y参数编译的 Linux 内核映像和 Windows Boot Managerbootmgfw.efi。 - 支持加载 UEFI 文件系统驱动。
另请参见维基百科:引导加载程序比较。
内核
引导加载器会启动包含内核的 vmlinux 映像。
内核是操作系统的核心。它运行于一个叫内核空间的底层上,负责机器硬件和应用程序之间的交流。在继续进入用户空间前,内核会首先执行硬件枚举和初始化。具体细节请参考zhwp:内核和zhwp:Linux内核。
initramfs
initramfs(初始内存文件系统,initial RAM file system)映像是一个 cpio 存档文件,为早期用户空间(见下文)启动晚期用户空间提供了必要的文件。这包括了所有用于定位,访问和挂载根文件系统的内核模块、用户空间工具、相关库文件、类似 udev 规则的支持文件等。得益于 initramfs 的概念,它可以处理更加复杂的配置场景,例如从外置硬盘启动,堆叠设备(例如逻辑卷,软 RAID,压缩和加密),或是在早期用户空间中运行一个微型 SSH 服务器,以供远程解锁或为根文件系统执行维护任务。
绝大部分内核模块都将在初始化流程的后期阶段,由 udev 在根切换到根文件系统后加载。
具体流程如下:
-
/下的根文件系统原本是一个空的 rootfs,它是一个特殊的 tmpfs 或 ramfs 实例。这里就是 initramfs 会解压到的临时根文件系统。 - 内核会将其内置 initramfs 解压到临时根文件系统下。Arch Linux 官方支持的内核使用空白存档作为内置 initramfs,即构建内核时的默认行为。
- 然后,内核会按照引导加载器传递的命令行参数指定的顺序解压外置 initramfs 映像,覆盖掉之前内置 initramfs 或其它解压出来的文件。注意,可以将多个 initramfs 映像合并为一个文件,内核会按照文件内的顺序加载映像。
- 如果首个 initramfs 映像未经压缩,那么内核会在解包该映像后在
/kernel/x86/microcode/目录查找 CPU 微码更新,在/kernel/firmware/acpi/目录查找 ACPI 表更新。 - 在适用的情况下,在处理完 CPU 微码和 ACPI 表更新后,内核会继续解压剩余的 initramfs 映像。
- 如果首个 initramfs 映像未经压缩,那么内核会在解包该映像后在
initramfs 映像是 Arch Linux 推荐的早期用户空间配置方法,并可通过 mkinitcpio,dracut 或 booster 来生成。
不使用 initramfs
从 6.13.8 版本开始,官方支持的内核已内置 Btrfs 和 Ext4 的驱动[3]。
因此内核可以直接使用这些文件系统格式的根分区,然后再加载其它需要的外部模块。但有几点需要注意:
- 不能使用 GPT 分区自动挂载,必须使用
root内核参数。 - 只能使用
PARTUUID和PARTLABEL作为根分区(root)的块设备持久化名称[4]。 -
rootflags的挂载选项比较有限,例如noatime就无法使用[5]。要绕过该问题,可以先以只读进行挂载(rootflags=ro),然后使用 fstab 进行重新挂载来应用所需的挂载选项。 - 由于 GPT 分区自动挂载不可用,无需启用 systemd-gpt-auto-generator(8),启用甚至还会导致问题出现[6],可以通过
systemd.gpt_auto=no将其禁用。
另外,早期微码加载也必须搭配 initramfs 使用,但没必要为其构建完整映像,Arch 可以将微码放置在单独的 initramfs 文件中,以供单独使用。
即使没有提供 initramfs 映像,内核仍会包含一个空映像以供启动[7],以防止根分区固定出现问题。
早期用户空间
早期用户空间阶段(亦称“initramfs 阶段”)在由 #initramfs 映像提供文件的 rootfs 中进行,始于内核以 PID 1 执行 /init。
早期用户空间的功能可以进行配置,但主要是引导系统到能够访问真正根文件系统的状态,这主要包括:
- systemd-modules-load(8) 加载内核模块(基于 systemd 的 initramfs),例如挂载真正根文件系统所需的任何块设备模块。
- 构建访问真正根文件系统所需的存储栈(例如通过 dm-crypt、dm-verity、mdadm、LVM、systemd-repart 等)。
- 解密真正根文件系统(若适用)。
- udev 将块设备持久化名称解析为实际设备。
- 加载 DRM 模块(因为默认启用的 KMS 早启动)。
需要注意的是,早期用户空间不仅仅用于设置真正根文件系统。有些任务只能在挂载真正根文件系统之前执行,例如 fsck和从休眠中恢复。
在早期用户空间的最后阶段,真正根文件系统会被挂载到 /sysroot/(基于 systemd 的 initramfs)或 /new_root/(基于 BusyBox 的 initramfs),然后通过 systemctl switch-root(基于 systemd 的 initramfs)或 switch_root(8)(基于 BusyBox 的 initramfs)切换到真正根文件系统。最后通过执行真正根文件系统中的 init 程序启动晚期用户空间。
晚期用户空间
晚期用户空间从 init 进程开始。Arch 官方支持的 systemd 基于单元和服务的概念,但这里描述的功能在很大程度上与其它 init 系统重叠。
getty
init 会为每个虚拟终端(通常有六个)调用一次 getty,它会初始化终端并保护其免受未授权访问。在提供用户名和密码后,getty 会对照 /etc/passwd 和 /etc/shadow 检查是否正确。如果正确,就接着调用 login(1)。
login
login 会根据 /etc/passwd 设置环境变量并启动用户 shell,从而为用户配置一个会话。在成功登录后,启动登录 shell 前,login 程序会显示 /etc/motd(message of the day)的内容,你可以用它来显示服务条款以提醒用户你的本地策略,也可以显示其它提示信息。
shell
用户的 shell 启动后,在显示命令行提示符前,通常会执行一个运行时配置文件(例如 bashrc)。如果用户账户配置为在登录时自动启动 X,那么运行时配置文件会调用 startx 或 xinit,具体内容请参考#图形会话(Xorg)。
显示管理器
另外,在特定虚拟终端下,init 可配置为启动显示管理器,而不是 getty。要达成该效果,需要手动启用其 systemd 服务文件,之后显示管理器就会启动一个图形会话。
图形会话(Xorg)
xinit 会调用用户的 xinitrc 运行时配置文件,后者一般会启动一个窗口管理器或桌面环境。如果用户退出了窗口管理器,xinit、startx、shell、login 就会依次中断,返回到 getty 或显示管理器。
参见
- Wikipedia:Booting process of Linux
- Inside the Linux boot process
- Rod Smith - Managing EFI Boot Loaders for Linux
- NeoSmart: The BIOS/MBR Boot Process
- Lennart Poettering - Linux Boot Partitions and How to Set Them Up
- Wikipedia:initramfs
- Early Userspace in Arch Linux
- Kernel Newbie Corner: initrd and initramfs
- bootup(7)(主要涉及 systemd 早期用户空间部分)