Linux工作原理5内核如何启动

5 内核如何启动

现在你已经知道了 Linux 系统的物理和逻辑结构、什么是内核以及如何使用进程。本章将向你介绍内核是如何启动的。换句话说,你将学习内核如何移动到内存中,以及在第一个用户进程启动之前内核所做的工作。

启动过程的简化视图如下:

  • 机器的BIOS或引导固件加载并运行引导加载器。
  • 引导加载器在磁盘上找到内核映像,将其加载到内存中并启动。
  • 内核初始化设备及其驱动程序。
  • 内核挂载根文件系统。
  • 内核启动init 的程序,其进程ID为1,此时为用户空间启动。
  • init启动其他系统进程。

在某些时候,init 会启动一个允许登录的进程,通常是在启动序列的末尾或接近末尾时。

本章主要介绍引导加载器和内核的前几个阶段。第 6 章通过详细介绍 systemd(Linux 系统中最常见的 init 版本),继续介绍用户空间启动。

5.1 启动信息

传统的 Unix 系统在启动时会产生许多诊断信息,告诉您启动过程。这些信息首先来自内核,然后来自 init 启动的进程和初始化程序。然而,这些信息并不漂亮,也不连贯,在某些情况下甚至没有什么参考价值。此外,由于硬件的改进,内核的启动速度比以前快得多;信息闪过的速度太快,很难看清发生了什么。因此,目前大多数 Linux 发行版都会尽力用闪屏和其他形式的填充物来隐藏启动诊断信息,以便在系统启动时分散你的注意力。

查看内核启动和运行诊断信息的最佳方法是使用 journalctl 命令获取内核日志。运行 journalctl -k 会显示当前启动时的信息,但也可以使用 -b 选项查看以前的启动信息。我们将在第 7 章详细介绍日志。

如果没有 systemd,可以检查 /var/log/kern.log 等日志文件,或运行 dmesg 命令查看内核环缓冲区中的信息。

下面是 journalctl -k 命令的示例:

# journalctl -k
Jun 20 15:42:09 sanrui kernel: Booting Linux on physical CPU 0x0000080000 [0x481fd010]
Jun 20 15:42:09 sanrui kernel: Linux version 5.15.0-106-generic (buildd@bos03-arm64-019) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #116-Ubuntu SMP Wed Apr 17 09:19:>
Jun 20 15:42:09 sanrui kernel: efi: EFI v2.70 by EDK II
Jun 20 15:42:09 sanrui kernel: efi: ACPI 2.0=0x2f690018 SMBIOS 3.0=0x2f530000 MEMATTR=0x3370c018 MOKvar=0x261b0000 RNG=0x2f69f698 MEMRESERVE=0x2f670498
Jun 20 15:42:09 sanrui kernel: random: crng init done
Jun 20 15:42:09 sanrui kernel: secureboot: Secure boot disabled
Jun 20 15:42:09 sanrui kernel: ACPI: Early table checksum verification disabled
Jun 20 15:42:09 sanrui kernel: ACPI: RSDP 0x000000002F690018 000024 (v02 HISI  )
...
``

内核启动后,用户空间启动过程经常会产生信息。这些信息可能更难查看和审查,因为在大多数系统中,你不会在一个日志文件中找到它们。启动脚本被设计为向控制台发送信息,并在启动过程结束后被清除。不过,这在 Linux 系统上不是问题,因为 systemd 会捕获启动和运行时的诊断信息,这些信息通常会发送到控制台。

##5.2 内核初始化和启动选项

启动时,Linux 内核按以下一般顺序初始化:

- CPU检查
- 内存检查
- 设备总线发现
- 设备发现
- 辅助内核子系统设置(网络等)
- 根文件系统挂载
- 启动用户空间

前两个步骤并不复杂,但当内核进入设备时,就会出现依赖性问题。例如,磁盘设备驱动程序可能依赖于总线支持和 SCSI 子系统支持,这在第3章中已经提到过。之后,在初始化过程中,内核必须在启动 init 之前挂载根文件系统。

能够识别启动过程中的每个阶段,对你解决启动问题和了解整个系统将是非常有价值的。不过,许多 Linux 发行版的默认行为通常会使你很难甚至不可能在启动过程中识别最初的几个启动阶段,所以你可能只能在这些阶段完成并登录后才能看清楚。

一般来说,除了某些必要组件可能是可加载的内核模块而不是主内核的一部分外,你不必担心这些依赖关系。有些机器可能需要在挂载真正的根文件系统前加载这些内核模块。我们将在第 6.7 节中介绍这个问题及其初始 RAM 文件系统(initrd)的解决方法。

内核会发出某些信息,表明它准备启动第一个用户进程:
```sh
Freeing unused kernel memory: 2408K
Write protecting the kernel read-only data: 20480k
Freeing unused kernel memory: 2008K
Freeing unused kernel memory: 1892K

在这里,内核不仅清理了一些未使用的内存,还保护了自己的数据。然后,如果你运行的是一个足够新的内核,你会看到内核以 init 的身份启动第一个用户空间进程:

Run /init as init process
   with arguments:
--snip--

随后,你应该能看到根文件系统被挂载,systemd 启动,并向内核日志发送一些信息:

EXT4-fs (sda1): mounted filesystem with ordered data mode. Opts: (null)
systemd[1]: systemd 237 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
systemd[1]: Detected architecture x86-64.
systemd[1]: Set hostname to <duplex>.

此时,你肯定知道用户空间已经启动。

5.3 内核参数

Linux 内核启动时,会收到一组基于文本的内核参数,其中包含一些额外的系统细节。这些参数指定了许多不同类型的行为,例如内核应产生的诊断输出量和特定于设备驱动程序的选项。

你可以通过查看 /proc/cmdline 文件,查看传递给系统当前运行内核的参数:

$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-5.10.0-218.0.0.117.oe2203sp4.aarch64 root=/dev/mapper/openeuler-root ro rd.lvm.lv=openeuler/root rd.lvm.lv=openeuler/swap video=VGA-1:640x480-32@60me cgroup_disable=files apparmor=0 crashkernel=1024M,high smmu.bypassdev=0x1000:0x17 smmu.bypassdev=0x1000:0x15 arm64.nopauth console=tty0 kpti=off

参数要么是简单的单字标志,如 ro 和 quiet,要么是键值对,如 vt.handoff=1。许多参数并不重要,例如用于显示闪屏的 splash 标志,但其中一个关键参数是 root 参数。这是根文件系统的位置;没有它,内核就无法正确执行用户空间启动。

根文件系统可以指定为设备文件,如下面的例子:

root=/dev/sda1

在大多数现代系统中,有两种更常见的选择。首先,你可能会看到这样一个逻辑卷:

root=/dev/mapper/my-system-root

你还可能看到一个 UUID(见第 4.2.4 节):

root=UUID=17f12d53-c3d7-4ab3-943e-a0a72366c9fa

这两个参数都比较好,因为它们不依赖于特定的内核设备映射。

ro参数指示内核在用户空间启动时以只读模式挂载根文件系统。这是正常现象;只读模式可确保 fsck 在执行任何重要操作之前安全地检查根文件系统。检查结束后,启动进程会以读写模式重新挂载根文件系统。

遇到不理解的参数时,Linux 内核会保存该参数。内核随后会在执行用户空间启动时将该参数传递给 init。例如,如果你在内核参数中添加了-s,内核就会将-s传递给init程序,以表明它应该以单用户模式启动。

如果您对基本启动参数感兴趣,请参阅 bootparam(7) 手册。如果你想找一些非常具体的参数,可以查看 Linux 内核自带的参考文件 kernel-params.txt。

有了这些基础知识,你就可以跳到第 6 章,了解用户空间启动、初始 RAM 磁盘和内核作为第一个进程运行的 init 程序的具体细节。本章其余部分将详细介绍内核如何加载到内存并启动,包括如何获取参数。

5.4 引导加载器(Boot Loaders)

在启动过程开始时,在内核和 init 启动之前,引导加载程序会启动内核。引导加载器的工作听起来很简单:从磁盘上的某个地方将内核加载到内存中,然后用一组内核参数启动内核。然而,这项工作比表面看起来要复杂得多。要理解原因,请考虑一下引导加载器必须回答的问题:

  • 内核在哪里?
  • 内核启动时应将哪些内核参数传递给内核?

答案是(通常)内核及其参数通常在根文件系统的某处。听起来内核参数应该很容易找到,但请记住,内核本身尚未运行,通常是内核遍历文件系统来查找必要的文件。更糟糕的是,通常用于访问磁盘的内核设备驱动程序也不可用。这就像一个 “先有鸡还是先有蛋 ”的问题。问题可能比这还要复杂,但现在,让我们来看看 Boot Loader 如何克服驱动程序和文件系统的障碍。

Boot Loader 确实需要驱动程序来访问磁盘,但与内核使用的驱动程序不同。在个人电脑上,引导加载器使用传统的基本输入/输出系统(BIOS)或较新的统一可扩展固件接口(UEFI)来访问磁盘。(可扩展固件接口或 EFI 和 UEFI 将在第 5.8.2 节中详细讨论)。现代磁盘硬件包含固件,允许 BIOS 或 UEFI 通过逻辑块寻址 (LBA) 访问连接的存储硬件。LBA 是访问任何磁盘数据的通用、简单方法,但性能较差。但这并不是问题,因为启动加载程序通常是唯一必须使用这种模式访问磁盘的程序;启动后,内核可以访问自己的高性能驱动程序。

要确定系统使用的是 BIOS 还是 UEFI,请运行 efibootmgr。 如果得到启动目标列表,说明系统使用的是 UEFI。如果显示不支持 EFI 变量,则说明系统使用的是 BIOS。另外,也可以检查是否存在 /sys/firmware/efi;如果存在,则说明系统使用了 UEFI。

一旦解决了访问磁盘原始数据的问题,引导加载程序就必须在文件系统中查找所需数据。大多数常见的 Boot Loader 都能读取分区表,并内置了对文件系统只读访问的支持。因此,它们可以找到并读取内核进入内存所需的文件。这种功能使得动态配置和增强 Boot Loader 变得容易得多。Linux 引导加载器并不总是具备这种能力;如果没有这种能力,配置引导加载器就会更加困难。

一般来说,内核增加新功能(尤其是存储技术)后, Boot Loader 会增加这些功能的单独简化版本来弥补。

5.4.1 引导加载器的任务

Linux 引导加载器的核心功能包括以下功能:

  • 从多个内核中选择。
  • 在内核参数集之间切换。
  • 允许用户手动覆盖和编辑内核映像名称和参数(例如,进入单用户模式)。
  • 支持启动其他操作系统。

自 Linux 内核诞生以来,引导加载器已经变得相当先进,具备了命令行历史记录和菜单系统等功能,但内核映像和参数选择的灵活性始终是一个基本需求。(一个令人惊讶的现象是,有些需求实际上已经减少了。例如,由于可以从 USB 存储设备执行紧急启动或恢复启动,因此很少需要担心手动输入内核参数或进入单用户模式)。当前的 Boot Loader 比以往提供了更强大的功能,如果你正在构建自定义内核或只是想调整参数,这一点会特别方便。

5.4.2 引导加载器概述

以下是您可能会遇到的主要 Boot Loader:

  • GRUB 几乎是 Linux 系统的通用标准,有 BIOS/MBR 和 UEFI 版本。
  • LILO 最早的 Linux 引导加载器之一。ELILO 是 UEFI 版本。
  • SYSLINUX 可以配置为从多种不同的文件系统运行。
  • LOADLIN 从 MS-DOS 引导内核。
  • systemd-boot 一个简单的 UEFI 启动管理器。
  • coreboot(前身为 LinuxBIOS) 一种可包含内核的高性能 PC BIOS 替代程序。
  • Linux Kernel EFISTUB 直接从 EFI/UEFI 系统分区(ESP)加载内核的内核插件。
  • efilinux 一个 UEFI 引导加载器,旨在作为其他 UEFI 引导加载器的模型和参考。

本书几乎只涉及 GRUB。使用其他 Boot Loader 的理由是它们比 GRUB 配置更简单、速度更快或提供其他特殊用途的功能。

在引导提示符下输入内核名称和参数,可以了解引导加载器的很多信息。要做到这一点,你需要知道如何进入引导提示符或菜单。不幸的是,由于 Linux 发行版对引导加载程序的行为和外观进行了大量定制,这一点有时很难弄明白。通常无法通过观察启动过程来判断发行版使用了哪个引导加载器。

接下来的章节将告诉你如何进入启动提示符,以输入内核名称和参数。一旦你熟悉了这些,你将看到如何配置和安装 Boot Loader。

5.5 GRUB 简介

GRUB 是 Grand Unified Boot Loader 的缩写。我们将介绍 GRUB 2,但还有一个旧版本称为 GRUB Legacy,已不再使用。

GRUB 最重要的功能之一是文件系统导航,可以轻松选择内核映像和配置。查看 GRUB 菜单是了解 GRUB 功能的最佳方式之一。该界面易于浏览,但你很可能从未见过它。

要访问 GRUB 菜单,请在 BIOS 启动屏幕首次出现时按住 SHIFT,如果系统是 UEFI,则按住 ESC。否则,引导加载器配置可能不会在加载内核前暂停。

尝试以下方法探索引导加载器:

  • 重新启动或打开 Linux 系统。
  • 在 BIOS 自检时按住 SHIFT,或在固件闪屏时按住 ESC,以获取 GRUB 菜单。(有时这些屏幕不可见,所以你必须猜测何时按下按钮)。
  • 按 e 查看默认启动选项的启动加载程序配置命令。您将看到类似图 5-2 的内容(可能需要向下滚动才能看到所有细节)。

该屏幕显示,在此配置中,root 被设置为 UUID,内核映像为 /boot/vmlinuz-4.15.0-45-generic,内核参数包括 ro、quiet 和 splash。初始 RAM 文件系统为 /boot/initrd.img-4.15.0-45-generic。但如果你以前从未见过这种配置,可能会觉得有些困惑。为什么会有多个 root 引用,它们为什么不同?为什么 insmod 会出现在这里?如果你以前见过它,可能会记得它是 Linux 内核的一项功能,通常由 udevd 运行。

你有理由多看两眼,因为 GRUB 并不使用 Linux 内核(记住,它的任务是启动内核)。你所看到的配置完全由 GRUB 内部的功能和命令组成,它存在于自己独立的世界中。

造成混淆的部分原因是 GRUB 借用了许多来源的术语。GRUB 有自己的 “内核 ”和 insmod 命令,用于动态加载 GRUB 模块,完全独立于 Linux 内核。许多 GRUB 命令与 Unix shell 命令相似;甚至还有一个 ls 命令来列出文件。

注意:启动内核位于逻辑卷上的系统需要一个用于 LVM 的 GRUB 模块。你可能会在系统中看到这个模块。

到目前为止,最容易引起混淆的是 GRUB 对 root 一词的使用。通常,root 指的是系统的根文件系统。在 GRUB 配置中,这是一个内核参数,位于 linux 命令的映像名称之后。

配置中其他所有提及 root 的地方都是指 GRUB root,它只存在于 GRUB 内部。GRUB 的 “根 ”是 GRUB 查找内核和 RAM 文件系统映像文件的文件系统。

在图 5-2 中,GRUB 根目录首先被设置为 GRUB 专用设备(hd0,msdos1),该配置的默认值为 1。在下一条命令中,GRUB 将搜索分区 2 上的特定 UUID。如果找到该 UUID,就会将 GRUB root 设为该分区。

最后,linux 命令的第一个参数(/boot/vmlinuz-. . )是 Linux 内核映像文件的位置 3。GRUB 会从 GRUB 根目录加载该文件。initrd 命令与此类似,它指定了第 6 章所述初始 RAM 文件系统的文件 4.

您可以在 GRUB 中编辑此配置;这通常是暂时修复错误启动的最简单方法。要永久解决启动问题,需要更改配置(参见第 5.5.2 节),但现在让我们更深入一步,通过命令行界面查看 GRUB 的一些内部信息。

5.5.1 使用GRUB命令行查看设备和分区

如图 5-2 所示,GRUB 有自己的设备寻址方案。例如,找到的第一个硬盘被命名为 hd0,然后是 hd1,以此类推。设备名称分配可能会改变,但幸运的是,GRUB 可以搜索所有分区的 UUID,找到内核所在的分区,如图 5-2 中的搜索命令所示。

列出设备
要了解 GRUB 如何指代系统中的设备,请在启动菜单或配置编辑器中按下 c 进入 GRUB 命令行。您将看到 GRUB 提示:

grub>

您可以在此输入配置中看到的任何命令,但要开始使用,请尝试使用诊断命令:ls。在没有参数的情况下,输出是 GRUB 所知道的设备列表:

grub> ls
(hd0) (hd0,msdos1)

在这种情况下,有一个主磁盘设备 (hd0) 和一个分区 (hd0,msdos1)。如果磁盘上有一个交换分区,也会显示出来,如 (hd0,msdos5)。分区上的 msdos 前缀说明磁盘包含 MBR 分区表;GPT 则以 gpt 开头,这在 UEFI 系统上可以找到。(还有更深层次的组合,即分区中包含 BSD 磁盘标签映射(第三个标识符),但除非在一台机器上运行多个操作系统,否则通常不必担心这个问题)。

要获取更详细的信息,请使用 ls -l。该命令特别有用,因为它会显示分区文件系统的 UUID。例如

grub> ls -l
Device hd0: No known filesystem detected – Sector size 512B - Total size 32009856KiB
        Partition hd0,msdos1: Filesystem type ext* – Last modification time
          2019-02-14 19:11:28 Thursday, UUID 8b92610e-1db7-4ba3-ac2f-30ee24b39ed0 - Partition start at 1024Kib - Total size 32008192KiB

该磁盘的第一个 MBR 分区上有一个 Linux ext2/3/4 文件系统。使用交换分区的系统会显示另一个分区,但从输出结果中无法判断其类型。

现在让我们看看 GRUB 的文件系统导航功能。使用 echo 命令确定 GRUB 根目录(请记住,这是 GRUB 希望找到内核的位置):

grub> echo $root
hd0,msdos1

要使用 GRUB 的 ls 命令列出根目录下的文件和目录,可以在分区的末尾添加一个斜线:

grub> ls (hd0,msdos1)/

由于不方便键入实际的 root 分区,可以用 root 变量来代替,以节省时间:

grub> ls ($root)/

输出结果是该分区文件系统中文件和目录名的简短列表,如 etc/、bin/ 和 dev/。现在,GRUB ls 命令的功能完全不同了。以前,你列出的是设备、分区表,或许还有一些文件系统头信息。现在,你实际上是在查看文件系统的内容。

你可以用类似的方式深入查看分区上的文件和目录。例如,要检查 /boot 目录,请从以下步骤开始:

grub> ls ($root)/boot

使用上下箭头键翻阅 GRUB 命令历史记录,使用左右箭头编辑当前命令行。标准的读行键(CTRL-N、CTRL-P 等)也可以使用。

你还可以使用 set 命令查看当前设置的所有 GRUB 变量:

grub> set
?=0
color_highlight=black/white
color_normal=white/black
--snip--
prefix=(hd0,msdos1)/boot/grub
root=hd0,msdos1

这些变量中最重要的一个是 $prefix,即 GRUB 希望在其中找到配置和辅助支持的文件系统和目录。接下来我们将讨论 GRUB 配置。

使用完 GRUB 命令行界面后,可以按 ESC 键返回 GRUB 菜单。或者,如果你已经设置了启动所需的所有配置(包括 linux 变量和可能的 initrd 变量),可以输入启动命令来启动这些配置。无论如何,启动你的系统吧。我们将探讨 GRUB 配置,这最好在系统完整可用时进行。

5.5.2 GRUB 配置

GRUB 配置目录通常为 /boot/grub 或 /boot/grub2。它包含中心配置文件 grub.cfg、特定架构目录(如 i386-pc)(包含后缀为 .mod 的可加载模块)以及其他一些项目(如字体和本地化信息)。我们不会直接修改 grub.cfg,而是使用 grub-mkconfig 命令(或 Fedora 上的 grub2-mkconfig)。

首先,快速查看 grub.cfg,了解 GRUB 如何初始化菜单和内核选项。你会看到该文件由 GRUB 命令组成,通常以一系列初始化步骤开始,然后是一系列菜单条目,用于不同的内核和启动配置。初始化过程并不复杂,但开头的许多条件可能会让你误以为并非如此。前半部分包括大量函数定义、默认值和视频设置命令,如下所示:

if loadfont $font ; then
  set gfxmode=auto
  load_video
  insmod gfxterm
  --snip--

注意:许多变量如 $font 都来自 grub.cfg 开头附近的 load_env 调用。

在配置文件的后面,你会发现可用的启动配置,每个配置都以 menuentry 命令开头。根据前一节所学,你应该能够阅读并理解这个示例:

menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-8b92610e-1db7-4ba3-ac2f-30ee24b39ed0' {
        recordfail
        load_video
        gfxmode $linux_gfx_mode
        insmod gzio
        if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
        insmod part_msdos
        insmod ext2
        set root='hd0,msdos1'
        search --no-floppy --fs-uuid --set=root 8b92610e-1db7-4ba3-ac2f-30ee24b39ed0
        linux   /boot/vmlinuz-4.15.0-45-generic root=UUID=8b92610e-1db7-4ba3-ac2f-30ee24b39ed0 ro quiet splash $vt_handoff
        initrd  /boot/initrd.img-4.15.0-45-generic
}

检查 grub.cfg 文件中是否有包含多个 menuentry 命令的子菜单命令。许多发行版在旧版本的内核中使用子菜单命令,这样就不会挤占 GRUB 菜单。

如果你想更改 GRUB 配置,不要直接编辑 grub.cfg 文件,因为它会自动生成,系统偶尔会覆盖它。你可以在其他地方设置新配置,然后运行 grub-mkconfig 生成新配置。

要了解配置生成的工作原理,请查看 grub.cfg 的开头部分。应该有这样的注释行:

### BEGIN /etc/grub.d/00_header ###

进一步检查后,你会发现 /etc/grub.d 中的几乎每个文件都是一个 shell 脚本,用于生成 grub.cfg 文件的一部分。grub-mkconfig 命令本身就是一个运行 /etc/grub.d 中所有内容的 shell 脚本。请记住,GRUB 本身不会在启动时运行这些脚本;我们在用户空间运行这些脚本,生成 GRUB 运行的 grub.cfg 文件。

请以 root 用户身份亲自尝试。不用担心会覆盖当前配置。该命令本身只是将配置打印到标准输出。

# grub-mkconfig

如果想在 GRUB 配置中添加菜单项和其他命令怎么办?简而言之,你应该在 GRUB 配置目录(通常为 /boot/grub/custom.cfg)中新建一个 custom.cfg 文件,将你的自定义设置放入其中。

长答案要复杂一些。/etc/grub.d 配置目录提供了两个选项: 40_custom 和 41_custom。第一个选项 40_custom 是一个脚本,你可以自己编辑,但它最不稳定;软件包升级可能会破坏你所做的任何更改。41_custom 脚本更简单;它只是一系列在 GRUB 启动时加载 custom.cfg 的命令。如果选择第二个选项,在生成配置文件时将不会显示您所做的更改,因为 GRUB 会在启动时完成所有工作。

注意:文件名前面的数字会影响处理顺序;较低的数字在配置文件中排在前面。

自定义配置文件的两个选项并不是特别广泛,没有什么能阻止你添加自己的脚本来生成配置数据。你可能会在/etc/grub.d目录中看到一些针对你的特定发行版而添加的内容。例如,Ubuntu 在配置中添加了内存测试启动选项 (memtest86+)。

要写入并安装新生成的 GRUB 配置文件,可以使用 grub-mkconfig 的 -o 选项将配置写入 GRUB 目录,如下所示:

# grub-mkconfig -o /boot/grub/grub.cfg

像往常一样,备份旧配置并确保安装到正确的目录。

现在我们将进入 GRUB 和 Boot Loader 的一些技术细节。如果你听腻了引导加载器和内核,请跳至第 6 章。

参考资料

5.5.3 安装 GRUB

安装 GRUB 比配置 GRUB 更复杂。幸运的是,您通常不必担心安装问题,因为您的发行版会为您处理。不过,如果您要复制或还原可启动磁盘,或准备自己的启动顺序,可能需要自行安装。

在继续之前,请阅读第 5.4 节,了解 PC 如何启动,并确定是使用 MBR 还是 UEFI 启动。接下来,构建 GRUB 软件集,并确定 GRUB 目录的位置;默认为 /boot/grub。如果您的发行版会为您编译 GRUB,您可能不需要编译 GRUB,但如果需要,请参阅第 16 章了解如何从源代码编译软件。请确保构建了正确的目标:MBR 或 UEFI 启动的目标不同(32 位和 64 位 EFI 启动的目标甚至也不同)。

安装引导加载器需要您或安装程序确定以下内容:

当前运行系统看到的 GRUB 目标目录。如前所述,这通常是 /boot/grub,但如果您要将 GRUB 安装到另一个磁盘上供另一个系统使用,则可能有所不同。
GRUB 目标磁盘的当前设备。
对于 UEFI 启动,EFI 系统分区(通常为 /boot/efi)的当前挂载点。
请记住,GRUB 是一个模块化系统,但为了加载模块,它必须读取包含 GRUB 目录的文件系统。你的任务是构建一个能读取该文件系统的 GRUB 版本,以便加载其余配置(grub.cfg)和任何所需的模块。在 Linux 上,这通常意味着构建一个预加载了 ext2.mod 模块(可能还有 lvm.mod)的 GRUB 版本。有了这个版本后,你需要做的就是把它放在磁盘的可启动部分,然后把其他所需的文件放到 /boot/grub。

幸运的是,GRUB 自带了一个名为 grub-install 的实用程序(不要与 install-grub 混淆,你可能会在一些旧系统上找到它),它可以为你完成安装 GRUB 文件和配置的大部分工作。例如,如果你的当前磁盘位于 /dev/sda,而你想在该磁盘的 MBR 上安装 GRUB,并使用当前的 /boot/grub 目录,请使用此命令:

# grub-install /dev/sda

错误安装 GRUB 可能会破坏系统的启动顺序,因此不要轻易执行此命令。如果担心,请研究如何使用 dd 备份 MBR,备份当前安装的任何其他 GRUB 目录,并确保有一个紧急启动计划。

要在当前系统之外的存储设备上安装 GRUB,必须手动指定该设备上的 GRUB 目录,就像当前系统现在看到的那样。例如,目标设备是 /dev/sdc,而该设备包含 /boot 的根文件系统(例如 /dev/sdc1)已挂载到当前系统的 /mnt。这意味着安装 GRUB 时,当前系统将看到 /mnt/boot/grub 中的 GRUB 文件。运行 grub-install 时,请告诉它这些文件的位置,如下所示:

# grub-install --boot-directory=/mnt/boot /dev/sdc

在大多数 MBR 系统中,/boot 是根文件系统的一部分,但有些安装程序会将/boot 放入自己的独立文件系统中。请确保您知道目标 /boot 位于何处。

UEFI 安装应该更简单,因为只需将引导加载器复制到适当的位置即可。但你还需要用 efibootmgr 命令将引导加载器 “宣布 ”到固件中,也就是将加载器配置保存到 NVRAM 中。如果有的话,grub-install 命令会运行它,所以通常你可以像这样在 UEFI 系统上安装 GRUB:

# grub-install --efi-directory=efi_dir –-bootloader-id=name

这里,efi_dir 是当前系统中 UEFI 目录的位置(通常是 /boot/efi/EFI,因为 UEFI 分区通常挂载在 /boot/efi),name 是引导装载程序的标识符。

不幸的是,安装 UEFI 启动加载器时可能会出现很多问题。例如,如果您安装到的磁盘最终会被安装到另一个系统中,您就必须想办法将引导加载器公布到新系统的固件中。可移动媒体的安装程序也有不同。

但最大的问题之一是 UEFI 安全启动。

5.6 UEFI 安全启动问题

影响 Linux 安装的一个新问题是如何处理近期 PC 上的安全启动功能。激活 UEFI 机制后,任何引导加载程序都必须经过可信机构的数字签名才能运行。微软要求硬件供应商在出货 Windows 8 及更高版本的系统时使用安全启动。其结果是,如果你试图在这些系统上安装未签名的启动加载程序,固件将拒绝加载程序,操作系统将无法加载。

主要的 Linux 发行版在安全启动方面没有问题,因为它们包含签名的启动加载器,通常基于 UEFI 版本的 GRUB。通常情况下,UEFI 和 GRUB 之间会有一个小的签名临时文件;UEFI 运行临时文件,临时文件反过来执行 GRUB。如果你的机器不在可信环境中或需要满足某些安全要求,防止启动未经授权的软件是一项重要功能,因此有些发行版更进一步,要求整个启动序列(包括内核)都要签名。

安全启动系统也有一些缺点,尤其是对于尝试构建自己的启动加载器的人来说。你可以在 UEFI 设置中禁用安全启动要求。不过,这对双启动系统来说并不奏效,因为如果不启用安全启动,Windows 将无法运行。

5.7 链载其他操作系统

UEFI 支持加载其他操作系统相对容易,因为您可以在 EFI 分区中安装多个引导加载器。然而,旧的 MBR 风格不支持此功能,即使您有 UEFI,您可能仍有一个带有 MBR 风格引导加载器的单独分区。GRUB 可以在磁盘的特定分区上加载并运行不同的引导加载器,而不是配置并运行 Linux 内核;这就是所谓的链式加载。

要进行连锁加载,请在 GRUB 配置中创建一个新的菜单项(使用 “生成新配置文件 ”一节中描述的方法之一)。下面是在磁盘第三个分区安装 Windows 的示例:

menuentry "Windows" {
	insmod chain
	insmod ntfs
	set root=(hd0,3)
	chainloader +1
}

+1选项告诉chainloader加载分区第一个扇区的任何内容。你也可以让它直接加载文件,方法是使用这样的一行来加载 io.sys MS-DOS 加载器:

menuentry "DOS" {
	insmod chain
	insmod fat
	set root=(hd0,3)
	chainloader /io.sys
}

5.8 引导加载器细节

现在我们来快速了解一下 Boot Loader 的内部结构。要了解 GRUB 这样的引导加载器是如何工作的,首先我们要了解一下电脑是如何启动的。由于必须解决传统 PC 启动机制的许多不足,启动加载方案有多种变化,但主要有两种: MBR 和 UEFI。

5.8.1 MBR 启动

除了第 4.1 节所述的分区信息外,MBR 还包括一个 441 字节的小区域,PC BIOS 在开机自检(POST)后加载并执行该区域。不幸的是,这个空间不足以容纳几乎所有的引导加载程序,因此需要额外的空间,这就是所谓的多级引导加载程序。在这种情况下,MBR 中的初始代码除了加载引导加载程序的其余代码外,什么也不做。引导加载程序的其余部分通常被塞入 MBR 和磁盘上第一个分区之间的空间。这并不十分安全,因为任何东西都可以覆盖那里的代码,但大多数引导加载器都这样做,包括大多数 GRUB 安装。

这种将引导加载程序代码放在 MBR 之后的方案对于使用 BIOS 引导的 GPT 分区磁盘无效,因为 GPT 信息位于 MBR 之后的区域。(GPT 的解决方法是创建一个称为 BIOS 启动分区的小分区,并使用特殊的 UUID(21686148-6449-6E6F-744E-656564454649),为完整的启动加载程序代码提供一个位置。不过,这种配置并不常见,因为 GPT 通常用于 UEFI,而非传统 BIOS。通常只有在拥有超大磁盘(超过 2TB)的旧系统中才会使用,因为这些磁盘对于 MBR 来说太大了。

5.8.2 UEFI 启动

PC 制造商和软件公司意识到传统的 PC BIOS 有很大的局限性,因此决定开发一种名为 “可扩展固件接口 (EFI) ”的替代软件。EFI 花了一段时间才在大多数 PC 上流行起来,但如今它已成为最常见的固件接口,尤其是在微软要求 Windows 安全启动的今天。目前的标准是统一 EFI (UEFI),它包括内置 shell、读取分区表和浏览文件系统等功能。GPT 分区方案是 UEFI 标准的一部分。

与 MBR 相比,UEFI 系统的启动方式截然不同。在大多数情况下,它更容易理解。在 UEFI 系统中,可执行的启动代码并不驻留在文件系统之外,而是始终存在一个特殊的 VFAT 文件系统,称为 EFI 系统分区(ESP),其中包含一个名为 EFI 的目录。在 Linux 系统中,ESP 通常挂载在 /boot/efi,因此你可能会发现大部分 EFI 目录结构都是从 /boot/efi/EFI 开始的。每个 Boot Loader 都有自己的标识符和相应的子目录,如 efi/microsoft、efi/apple、efi/ubuntu 或 efi/grub。引导加载程序文件的扩展名为 .efi,与其他支持文件一起存放在这些子目录中。如果你继续探索,可能会发现 grubx64.efi (GRUB 的 EFI 版本)和 shimx64.efi 等文件。

注意:ESP 与第 5.8.1 节所述的 BIOS 启动分区不同,具有不同的 UUID。你应该不会遇到同时使用这两种启动分区的系统。

但有一个问题:你不能将旧的引导加载程序代码放入 ESP,因为旧的代码是为 BIOS 界面编写的。相反,你必须提供一个为 UEFI 编写的引导加载器。例如,使用 GRUB 时,必须安装 UEFI 版本的 GRUB 而不是 BIOS 版本。而且,正如前面在 “使用 UEFI 安装 GRUB ”中解释的,必须向固件发布新的引导加载器。

最后,如第 5.6 节所述,我们还必须解决 “安全启动 ”问题。

5.8.3 GRUB如何工作

最后,我们来看看 GRUB 是如何工作的:

  • 电脑 BIOS 或固件初始化硬件,并搜索启动顺序存储设备以查找启动代码。
  • 找到启动代码后,BIOS/固件会加载并执行它。这就是 GRUB 开始的地方。
  • GRUB 核心加载。
  • 核心初始化。此时,GRUB 可以访问磁盘和文件系统。
  • GRUB 识别其引导分区并在此加载配置。
  • GRUB 允许用户更改配置。
  • 超时或用户操作后,GRUB 执行配置(如第 5.5.2 节所述,grub.cfg 文件中的命令序列)。
  • 在执行配置的过程中,GRUB 可能会在引导分区中加载额外的代码(模块)。其中一些模块可能是预加载的。
  • GRUB 执行引导命令,加载并执行配置的 linux 命令指定的内核。

由于传统 PC 启动机制的不足,GRUB 内核加载序列的第 3 步和第 4 步可能会比较复杂。最大的问题是 "GRUB 内核在哪里?有三种基本可能性

  • 部分塞在 MBR 和第一个分区的开头之间
  • 常规分区中
  • 特殊引导分区中:GPT 引导分区、ESP 或其他地方

除了 UEFI/ESP 之外的所有情况下,电脑 BIOS 都会从 MBR 中加载 512 字节,这就是 GRUB 的起始位置。这一小段(源自 GRUB 目录中的 boot.img)还不是核心,但它包含核心的起始位置,并从此处加载核心。

不过,如果你有一个 ESP,GRUB 内核就会以文件形式存在。固件可以浏览 ESP 并直接执行 GRUB 或其他操作系统加载器中的所有内容。(你可能会在 ESP 中的 GRUB 之前加一个垫片来处理安全启动,但想法是一样的)。

不过,在大多数系统中,这还不是全部。在加载和执行内核之前,引导加载器可能还需要将初始 RAM 文件系统映像加载到内存中。这就是 initrd 配置参数指定的内容,我们将在第 6.7 节中介绍。不过,在了解初始 RAM 文件系统之前,你应该先了解一下用户空间的启动--下一章就从这里开始。

posted @ 2024-07-21 08:08  磁石空杯  阅读(11)  评论(0编辑  收藏  举报