Linux 引导过程详解

虽然个人使用 Linux 已有 6 年多,但是近来发现我对基础部分的认识一直模糊不清。因此,在之后的几篇博客中,我打算把这些基础部分进行详细、深入的重新学习,争取把我之前模糊不清的部分和错误的概念全都理清,同时也希望对新手有所帮助。这些内容可能涉及系统的各个方面,那么就以最基础的 Linux 引导过程开始吧。

虽然本文是我结合多篇 wiki 所整理出来的,但是其中也加入了部分个人理解,难免有错漏之处。望看到本文的各位读者不吝赐教。

一些说明:

1、本文覆盖的引导过程从 BIOS 读取并执行 MBR 开始,到 Linux 执行 init 为止。我只会详细说明每一步的过程及其作用,至于其中的具体原理部分(例如执行的程序或文件具体装入哪个内存地址,调用哪些函数或中断),由于个人知识水平所限,因此无法深入剖析。

2、本文的引导过程侧重于 MBR 的传统引导方式,UEFI 部分可能不会涉及。但是执行 boot loader/boot manager 阶段完成之后,UEFI 和 MBR 并无区别(除非采用 EFI stub)。

3、本文中的 boot loader 以 grub-legacy 为例,grub2 取消了 grub-legacy 中 stage 1、stage 1.5 和 stage 2 的概念,但是基本执行过程还是这三个阶段。LiLo 和 syslinux 只在执行完 boot loader 阶段之前和 grub-legacy 可能有所区别,之后的引导过程和 grub-legacy 一样。

先来看看 grub-legacy 的三个 stage,这对于理解引导过程会有所帮助。grub-legacy 的执行分为三个 stage,分别是 stage 1、stage 1.5 和 stage 2。

stage 1:也就是 grub-legacy 写入 MBR 或超级块的那部分,具体对应的文件是 boot.img。作用是装入 stage 1.5(即 core.img)的第一个扇区,为后续的引导过程做准备。grub-legacy 或 grub2 出错以后显示的 grub rescue shell 也就是这一阶段的直观呈现。

stage 1.5:也就是写入保留扇区的那部分,具体对应的文件是 core.img。由于在标准的 BIOS/MBR 分区表上,第一个分区的起始位置为第 63 扇区,而 MBR 写入的是第 0 扇区,中间有 62 个扇区的空间既不属于任何分区,也不属于 MBR,这 62 个扇区就是保留扇区。由于 grub-legacy 的 MBR 部分并不能直接识别 /boot 的文件系统,因此需要借助 stage 1.5 来进行识别。执行此阶段后,grub-legacy 及 grub2 会加载自身的配置文件,及其他必要的文件系统模块,然后根据配置文件来执行 stage 2。正因为 grub-legacy 及 grub2 需要这一个中间过程才能顺利进行后续工作,因此,想要在不使用 UEFI 的情况下从 GPT 引导的话,必须要分一个最小为 31KiB 的 BIOS Boot 分区,其作用就是模拟 BIOS/MBR 表上的保留扇区。

stage 2:也就是 grub-legacy 及 grub2 的最终阶段。直接呈现给用户的就是一个引导菜单,其中提供操作系统名称、内核参数、引导分区等等。从这时候开始,才真正进行 Linux 的启动过程,之前的阶段都只是准备工作。

到这里为止,总结一下 grub 为 Linux 引导所做的准备工作:计算机加电开机,BIOS 进行硬件初始化和自举,然后把控制权交给 MBR 中的 grub。接过控制权之后,grub 装入 core.img 中的第一个扇区,识别 /boot 的文件系统并加载配置文件及必要的文件系统模块,然后才向用户展示引导菜单。之后,开始装入内核和虚拟内存盘,执行 init。

grub 的任务到此就完成了,之后开始真正的 Linux 启动过程。

根据自身的配置文件,grub 将对应的内核解压并装入内存,同时可能也需要装入对应的虚拟内存盘。在这之前,解释下虚拟内存盘的作用。

虚拟内存盘,一般称为 initrd 或者 initramfs,其中包含一个完整的 / 环境。通常,各个发行版的内核模块都存放在 /lib/modules 目录下,其中可能包括文件系统模块、加密算法模块等基础支持模块。而不少发行版的通用内核中可能都不包含针对部分文件系统的基础支持,因此可能会出现这种情况:为了顺利挂载 / 文件系统,那么就必须先加载对应的文件系统模块,而由于文件系统模块位于 `/lib/modules,就要先挂载 / 文件系统才能顺利加载这些模块。显然,这是一个死循环。

而虚拟内存盘的作用就是先将一个临时的 / 环境装入内存,加载这个虚拟内存盘中包含的文件系统模块和其他必要模块(如加密模块),接着把真正的 / 文件系统挂载到 /mnt,最后执行 switch_root,切换到位于 /mnt 的真正 / 文件系统,并执行 /sbin/init(或其他初始化进程,如 systemd)。

当然,在某些情况下,虚拟内存盘并不必要。最典型的例子就是,所有必要的基础支持都已经编入内核,而不是编为内核模块。确保内核能够识别并挂载所有必要文件系统,并且未采用 LVM、LUKS 等高级分区方案的情况下,就可以不需要虚拟内存盘。但是,对于已实施 usr merge 并且把 /usr 单独分区,或者采用 btrfs 文件系统的情况下,虚拟内存盘必不可少(个人使用经验是如此,不排除有例外)。

虚拟内存盘可以根据自己需要增减模块,只需确保加载所有必要模块即可。

执行 /sbin/init 之后,初始化进程才根据 /etc/fstab 中的设置内容对应挂载其他文件系统。可能有些人会有疑问:既然虚拟内存盘已经帮我们挂载了真正的 /,那么为什么我们还需要在 /etc/fstab 中定义 / 条目?因为在虚拟内存盘执行期间,真正的 / 是以只读挂载的。在执行 /sbin/init 之后,根据 /etc/fstab,/ 文件系统需要以读写进行重新挂载。当然,你可以尝试在加载内存虚拟盘的情况下去掉 /etc/fstab 中的 / 条目,看看有什么后果。

以上差不多就是 Linux 的整个引导过程了,其中的个人猜测部分可能存在错误,希望借此抛砖引玉吧。