Linux 启动实用指南(二)

原文:Hands-on Booting

协议:CC BY-NC-SA 4.0

三、GRUB 开机管理程序

Linux 系统现在使用的引导装载程序是 GRUB 版本 2。GRUB 2 的第一个稳定版本是在 2012 年,但它是在 2014 年随着 Centos 7 和 RHEL 7 开始出现在企业级 Linux 中的。2015 年后,它在几乎所有流行的 Linux 发行版中都被广泛采用。通常当用户提出错误或要求新功能时,开发人员会听取反馈,区分工作的优先级,并最终推出新版本的代码。然而,在 GRUB 的情况下,它以另一种方式工作。当用户对 GRUB Legacy(版本 1)感到满意时,开发人员决定改变 GRUB 2 的整个结构。

  • “由于混乱的代码和设计失败,GRUB Legacy 已经变得不可维护。我们收到了许多功能请求,并且在没有重新设计框架的情况下,将 GRUB 扩展到了原来的范围之外。这导致了一种状态,即如果不从根本上重新思考一切,就不可能进一步扩展 GRUB。”

  • —gnu grub 常见问题解答( https://www.gnu.org/software/grub/grub-faq.html

以下是 GRUB 2 提供或正在开发的一些特性:

  • 完全支持 USB。

  • Linux 统一设置密钥(LUKS)支持。LUKS 是 Linux 硬盘加密的标准。

  • 一个别致的菜单实现,将有动画,彩色效果,样式表等。

  • 一个“分开的”工具将被添加到引导装载程序中。添加后,用户将能够在引导时编辑磁盘配置。

本章将涵盖以下内容:

  • 如何为 BIOS 和 UEFI 固件实现 GRUB 2

  • GRUB 2 中固件特定的结构变化

  • GRUB 2 的引导程序规范特性

  • UEFI 的安全引导特性及其在 GRUB 2 中的实现

  • 几个与引导程序相关的问题以及我们如何修复它们

GRUB 2 实现

正如我们到目前为止所看到的,GRUB 控制了固件。这意味着它必须处理 UEFI 以及 BIOS。我们先来看看 GRUB 2 是如何在基于 BIOS 的系统上实现的。

基于 BIOS 的系统上的 GRUB 2

基于 BIOS 的系统上的 GRUB 2 将其所有文件保存在三个不同的位置。

  • /boot/grub2/

  • /etc/default/grub

  • /etc/grub.d/

以 Ubuntu 为例,GRUB 的名字中没有使用版本 2,所以会用/boot/grub/代替/boot/grub2/,用grub-install代替grub2-install,或者用grub-mkconfig代替grub2-mkconfig

让我们讨论一下位置和它们的内容。

/boot/grub2

这是 GRUB 2 的安装位置。正如你在图 3-1 中看到的,这个目录保存了引导装载程序的核心文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-1

/boot/grub2 中的文件

设备.地图

GRUB 不理解 sda 或 vda 等磁盘名称,因为这些磁盘命名约定是由操作系统的 SCSI 驱动程序创建的。很明显,GRUB 在操作系统不存在时运行,因此它有自己的磁盘命名约定。以下是 GRUB 的磁盘命名约定:

|

GRUB 版本

|

磁盘命名约定

|

意义

|
| — | — | — |
| Two | hd0msdos1 | 硬盘号 0 和分区号 1,其中有一个 MS-DOS 分区表 |
| Two | hd1msdos3 | 2 号硬盘和 3 号分区,其中有一个 MS-DOS 分区表 |
| Two | hd2gpt1 | 3 号硬盘和 1 号分区,后者有一个 GPT 分区表 |
| one | hd00 | 硬盘号 0 和分区号 1 |

在 GRUB 中,硬盘从 0 开始,分区号从 1 开始,而磁盘和分区的操作系统命名约定从 1 开始。由于 OS 和 GRUB 磁盘命名约定不同,所以必须有一个用户映射,这就是为什么创建了device.map文件。

# cat /boot/grub2/device.map
      # this device map was generated by anaconda
      (hd0)      /dev/sda

grub2-install like 命令将使用device.map文件来了解 GRUB 的核心文件安装在哪个磁盘上。以下是该文件的一个示例:

# strace -o delete_it.txt  grub2-install  /dev/sda
      Installing for i386-pc platform.
      Installation finished. No error reported.

# cat delete_it.txt | grep -i 'device.map'
      openat(AT_FDCWD, "/boot/grub2/device.map", O_RDONLY) = 3
      read(3, "# this device map was generated "..., 4096) = 64
      openat(AT_FDCWD, "/boot/grub2/device.map", O_RDONLY) = 3
      read(3, "# this device map was generated "..., 4096) = 64

因为用户不知道 GRUB 磁盘命名约定,所以grub2-install命令将以 OS 磁盘命名约定的形式接受输入。在执行过程中,grub2-install会通过读取device.map文件将 SCSI 磁盘命名约定转换为 GRUB 磁盘命名约定。

grub.cfg

这是 GRUB 的主要配置文件。正如你在图 3-2 中看到的,这是一个巨大的脚本文件,它是通过引用其他一些脚本文件生成的,我们将很快讨论这些文件。强烈建议不要更改grub.cfg的内容,因为这样做可能会使您的 Linux 版本无法启动。GRUB part-3 从这个文件中获取如下指令:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-2

grub.cfg 文件

  • 内核和 initramfs 的位置

    • /boot/vmlinuz-<version>

    • /boot/initramfs-<version>

  • 内核命令行参数

    • 根文件系统名称及其位置等。

GRUB 有自己的命令集,正如您在这里看到的:

|

GRUB 命令

|

目的

|
| — | — |
| menuentry | 这将在屏幕上打印标题。 |
| set root | 这将提供存储内核和 initramfs 的磁盘和分区名称。 |
| linux | Linux 内核文件的绝对路径 |
| initrd | Linux 的 initramfs 文件的绝对路径 |

因此,GRUB 2 在基于 BIOS 的 Fedora 系统上的引导顺序如下:

  1. 开机:首先是 BIOS,然后是 POST,然后是 BIOS,最后是第一个扇区。

  2. 首先是引导程序(GRUB 的第一部分),然后是 GRUB 的第二部分,最后是 GRUB 的第三部分。

  3. Part-3 of GRUB will read the previously shown grub.cfg from /boot/grub2/ (in the case of Ubuntu, it will be /boot/grub/) and will print the welcome screen, as shown in Figure 3-3.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-3

    欢迎屏幕

  4. 当用户选择 Ubuntu menuentry时,它将运行set rootlinuxinitrd命令,并开始在内存中加载内核和 initramfs。

  5. 在类似 Fedora 的 Linux 发行版中,您会发现一种不同的方法。会有一个grub.cfg文件,但是menuentryset rootlinuxinitrd命令在grub.cfg中不可用。一个叫做 BLS 的 GRUB 上游项目有了新的进展。我们将在本章的后面讨论这个问题。

i386 电脑

该目录包含所有 GRUB 支持的文件系统模块(驱动程序)(请参见图 3-4 )。所有的*.mod文件都是模块。通过使用这些模块,GRUB 可以在内存中加载内核和 initramfs 文件。例如,这个系统的/boot有一个 ext4 文件系统,所以显然当从/boot中探索和加载vmlinuz和 initramfs 文件时,GRUB 需要 ext4 模块,它从ext4.mod文件中获得这个模块。这类似于 XFS 或 UFS 文件系统上的/boot;因此,xfs.modufs.mod文件存在于/boot/grub2/i386-pc中。同时你会发现像http.modpxe.mod这样的模块。这意味着 GRUB 2 的 part-3 可以从httppxe设备加载内核和 initramfs 文件。一般来说,*.mod文件增加了功能,而不仅仅是设备。这些功能可能包括设备支持、文件系统支持或协议支持。

早先,/boot在 LVM 治下是不可能的,原因很简单。GRUB 必须了解 LVM 的设备。为了理解和组装 LVM 设备,GRUB 需要 LVM 模块和 LVM 二进制文件,如vgscan, vgchange, pvs, lvscan,等。它会增加 GRUB 作为一个包的大小;因此,企业 Linux 系统厂商一直避免在 LVM 设备下使用/boot。但是自从 UEFI 引入以来,GRUB 已经开始在 LVM 设备上支持/boot

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-4

那个。/boot/grub2/i386-pc 中的 mod*文件

如图 3-5 所示,除了这些*.mod文件,你还会在/boot/grub2/i386-pc/位置找到几个其他文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-5

这些文件除了*。现代的

core.img文件是 GRUB 2 的第三部分。因此,Linux 引导顺序如下:

-> Power on -> BIOS -> POST -> BIOS ->
-> part-1 of GRUB2 -> Part-2 of GRUB2 -> core3.img -> grub.cfg ->
-> if /boot is on an xfs filesystem -> /boot/grub2/i386-pc/xfs.mod ->
-> load vmlinuz & initramfs in main memory.

一旦内核在内存中,GRUB 2 的工作就完成了。引导序列的其余部分将由内核执行,我们将在第四章中讨论。

/etc/默认值/grub

另一个重要的文件当然是/etc/default/grub。请参见图 3-6 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-6

/etc/default 目录的内容

GRUB 使用该文件接受用户对外观和内核命令行的更改。

$ cat /etc/default/grub
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/root_vg-swap rd.lvm.lv=root_vg/root rd.lvm.lv=root_vg/swap console=ttyS0,115200 console=tty0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

如您所见,在这个文件中,我们可以更改 GRUB 欢迎屏幕的默认超时、字体、子菜单和默认内核命令行参数,如根设备名、交换设备名等。

/etc/grub.d/

这就是 GRUB 2 真正有趣的地方。

GRUB 2 有一个名为grub2-mkconfig .的命令,该命令的名字表明它将创建 GRUB 配置文件grub.cfg,,GRUB 的第三部分将引用该文件来显示欢迎屏幕。grub2-mkconfig文件将首先从/etc/default/grub获取外观和内核命令行参数输入,并从/etc/grub.d/目录运行图 3-7 中列出的脚本文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-7

/etc/grub.d/目录的内容

如您所见,这些文件都有编号。这意味着它们将按顺序运行。

00_header01_users08_fallback_counting10_reset_boot_success12_menu_auto_hide脚本文件做内务工作。例如,00_header脚本文件负责向grub.cfg文件添加一个头。例如,在 Fedora Linux 上,运行grub2-mkconfig文件后,会在grub.cfg中添加以下头:

### BEGIN /etc/grub.d/00_header ###
set pager=1

if [ -f ${config_directory}/grubenv ]; then
  load_env -f ${config_directory}/grubenv
elif [ -s $prefix/grubenv ]; then
  load_env
fi
if [ "${next_entry}" ] ; then
   set default="${next_entry}"
   set next_entry=
   save_env next_entry
   set boot_once=true
else
   set default="${saved_entry}"
fi

if [ x"${feature_menuentry_id}" = xy ]; then
  menuentry_id_option="--id"
else
  menuentry_id_option=""
fi

export menuentry_id_option

if [ "${prev_saved_entry}" ]; then
  set saved_entry="${prev_saved_entry}"
  save_env saved_entry
  set prev_saved_entry=
  save_env prev_saved_entry
  set boot_once=true
fi
function savedefault {
  if [ -z "${boot_once}" ]; then
    saved_entry="${chosen}"
    save_env saved_entry
  fi
}

function load_video {
  if [ x$feature_all_video_module = xy ]; then

    insmod all_video
  else
    insmod efi_gop
    insmod efi_uga
    insmod ieee1275_fb
    insmod vbe
    insmod vga
    insmod video_bochs
    insmod video_cirrus
  fi
}

terminal_output console
if [ x$feature_timeout_style = xy ] ; then
  set timeout_style=menu
  set timeout=5
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
  set timeout=5
fi
### END /etc/grub.d/00_header ###

08_fallback_counting脚本文件将在grub.cfg中添加以下内容:

### BEGIN /etc/grub.d/08_fallback_counting ###
insmod increment
# Check if boot_counter exists and boot_success=0 to activate this behaviour.
if [ -n "${boot_counter}" -a "${boot_success}" = "0" ]; then
  # if countdown has ended, choose to boot rollback deployment,
  # i.e. default=1 on OSTree-based systems.
  if  [ "${boot_counter}" = "0" -o "${boot_counter}" = "-1" ]; then
    set default=1
    set boot_counter=-1
  # otherwise decrement boot_counter
  else
    decrement boot_counter
  fi
  save_env boot_counter
fi
### END /etc/grub.d/08_fallback_counting ###

如您所见,该文件添加了监控 GRUB 欢迎屏幕的默认超时值的代码,与其余文件(10_reset_boot_successmenu_auto_hide)为 GRUB 做家务的方式相同。让我们看看让 GRUB 2 成为多引导最佳引导加载程序之一的脚本文件。

10_linux

这个文件包含将近 500 行 bash 脚本文件。每当用户执行grub2-mkconfig命令时,它就会运行这个脚本。10_linux文件将找出您的系统上还安装了哪些其他的 Linux 发行版。它会一个分区一个分区地查找已经安装在您系统上的所有其他 Linux 版本。如果还有其他的,那么它会在grub.cfg里做一个menuentry。与menuentry一起,它将添加各自的内核和 initramfs 条目。是不是很神奇?

考虑你先装了 Ubuntu 再装了 Fedora 现在你不用手动把 Ubuntu 的条目加入 Fedora 的grub.cfg。你只要跑grub2-mkconfig就行了。该命令将为我们运行10_linux,它最终会发现 Ubuntu 已经安装,并将为它添加适当的条目。

S7-1200 可编程控制器

grub2-mkconfig之后,这个脚本文件会发现你的系统是否安装了 XEN 内核。如果是,那么它将在grub.cfg中为其添加适当的条目。大多数 Linux 发行商将 XEN 作为一个独立的内核包发布。XEN 主要由管理程序使用。

20_ppc_terminfo

如果您的系统有 IBM 的 PPC 或 PowerPC 架构,那么这个脚本文件将为它找到相应的内核,并将适当的条目添加到grub.cfg中。

30 秒后醒来

如果您的硬盘上安装了任何非基于 Linux 的操作系统,那么这个脚本文件将找到该操作系统,并为其创建适当的条目。换句话说,如果您的系统上安装了 Windows,它会自动找出并在grub.cfg中为其创建一个适当的条目。这就是为什么在 UEFI 系统上安装了我们的第三个操作系统(Fedora 31)后,我们什么也没做就获得了操作系统列表。在图 3-8 中可以看到 Fedora 31 呈现的欢迎画面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-8

欢迎屏幕

Fedora 安装完成后,Anaconda 在后台运行grub2-mkconfig,最终运行30_os_prober,它找到 Windows 安装并在grub.cfg中为其创建适当的条目。

30_uefi 韧体

只有当您拥有 UEFI 系统时,此脚本才能成功运行。该脚本文件的任务是在grub.cfg中添加 UEFI 固件的适当条目。如图 3-8 所示,System setup条目已经被30_uefi-firmware脚本文件添加。

### BEGIN /etc/grub.d/30_uefi-firmware ###
menuentry 'System setup' $menuentry_id_option 'uefi-firmware' {
        fwsetup
}
### END /etc/grub.d/30_uefi-firmware ###

如果用户选择“系统设置”选项,那么它将引导回 UEFI 固件。在图 3-9 中可以看到 UEFI 固件界面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-9

UEFI 固件

40 _ 自定义和 41 _ 自定义

这些是给用户的,以防用户想要添加一些自定义条目到grub.cfg。例如,如果grub2-mkconfig未能添加任何已安装的 OS 作为条目,那么用户可以向这两个自定义文件添加一个自定义条目。您可以创建自己的自定义文件,但需要确保每个文件都有一个编号,并且有可执行的权限。

基于 UEFI 的系统上的 GRUB 2

同样,GRUB 2 有三个存储文件的位置。图 3-10 显示了目录及其文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-10

基于 UEFI 的系统上的 GRUB 2 位置

先前在/boot/grub2/中显示的grub.cfg文件已经被转移到 ESP ( /boot/efi/EFI/fedora/)中。另外,正如您所见,没有i386-pc目录。这是因为 EFI 提供了丰富的设备和文件系统支持。在 ESP 中,你会发现几个*.efi文件,包括我们的shim.efigrubx64.efi二进制文件。负责 GRUB 外观变化和内核命令行参数的etc/default/grub文件仍然在同一个位置。由于grub2-install命令在 UEFI 系统上没有意义,因此device.map文件不可用。我们将在本章的后面讨论这个命令。

引导加载程序规范(BLS)

BLS 是 GRUB 上游项目的新发展,还没有被许多主流发行版采用。具体来说,这种方案已经被 RHEL、Fedora、Centos、Oracle Linux 等基于 Fedora 的操作系统所采用。,但不是基于 Debian 的发行版,如 Ubuntu、Mint 等。

在基于 BIOS 的系统上,无论哪个操作系统控制了前 512 个字节,它就控制了所有操作系统的引导序列,这就是为什么每个操作系统都试图获得前 512 个字节。出现这种情况是因为 BIOS 总是进入硬盘的前 512 个字节,并调用引导程序的第一部分(引导程序)。part-1 到 part-2 和 part-2 到 part-3 的转换稍后发生,然后在最后,part-3 读取特定于引导加载程序的配置文件(对于 Windows,bcdedit对于 Linux,grub.cfg)。如果该配置文件包含其他已安装操作系统的条目,那么它们将有机会启动。所以,长话短说:谁控制了前 512 个字节,谁就控制了整个引导序列。但是有了 ESP,每个操作系统都有平等的启动机会,因为 UEFI 会检查 ESP 目录并列出所有可用的操作系统条目。开发人员开始考虑是否能在基于 BIOS 的系统中得到这样的东西,于是他们想到了 BLS。

在 BLS,引入了一个新的位置(第五个)来存储与引导程序相关的文件,那就是/boot/loader/.所以,我们现在有了五个 GRUB 存储文件的位置。

  • /boot/grub2/

  • /etc/default/grub

  • /etc/grub.d

  • /boot/efi/EFI/<OS_vendor>/(仅适用于 UEFI)

  • /boot/loader/ (BLS 的文件将存储在这里)

其思想是,在新内核安装之后,内核本身及其后脚本(类似于 Fedora 中的kernel-core包)将在/boot/loader/目录中为新内核创建一个条目。例如,我们安装了这个内核包:

# rpm -q kernel
Kernel-5.3.7-301.fc31.x86_64

这是将提供/boot/vmlinuz/boot/initramfs文件的同一个包。一旦安装了这个内核,它将准备以下文件:

# cat /boot/loader/entries/36543031048348f9965e3e12e48bd2b1-5.3.7-301.fc31.x86_64.conf

title Fedora (5.3.7-301.fc31.x86_64) 31 (Thirty One)
version 5.3.7-301.fc31.x86_64
linux /vmlinuz-5.3.7-301.fc31.x86_64
initrd /initramfs-5.3.7-301.fc31.x86_64.img
options $kernelopts
grub_users $grub_users
grub_arg --unrestricted
grub_class kernel

如您所见,该文件有四个条目。

  • GRUB 的第三部分将打印的标题

  • 内核文件的位置和名称

  • initramfs 文件的位置和名称

  • 已经在/boot/grub2/grubenv文件中声明的$kernelopts变量

# cat /boot/grub2/grubenv

# GRUB Environment Block
saved_entry=2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64
menu_auto_hide=1
boot_success=0
kernelopts=root=/dev/mapper/fedora_localhost--live-root ro resume=/dev/mapper/fedora_localhost--live-swap rd.lvm.lv=fedora_localhost-live/root rd.lvm.lv=fedora_localhost-live/swap rhgb quiet
boot_indeterminate=0

基本上,kernelopts提供了内核命令行参数,比如根文件系统的名称(/dev/mapper/fedora_localhost--live-root)以及它必须以何种模式挂载(ro - read only)。

因此,引导顺序变成这样:

  1. BIOS > post > BIOS

  2. GRUB 的第一部分->第二部分->第三部分

  3. GRUB 的第三部分->阅读grub.cfg

  4. GRUB -> reads 的第三部分/boot/loader/entries/*

  5. 打印出现在/boot/loader/entries中的所有文件标题

例如,假设安装了新的操作系统或新的内核。它必须生成自己的条目文件,并将其放在第一个主分区的/boot/loader/entries/目录中。这样,每次第一个主操作系统的 GRUB part-3 读取条目时,其他操作系统都有机会引导。可以使用 Fedora 的kernel-install命令创建条目文件。

#kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz

该命令将在/boot/loader/entries/中为kernel-5.3.7-301.fc31.x86_64创建适当的条目,如下所示:

# ls /boot/loader/entries/ -l
total 8
-rw-r--r--. 1 root root 329 Dec  9 10:18 2058a9f13f9e489dba29c477a8ae2493-0-rescue.conf
-rw-r--r--. 1 root root 249 Oct 22 01:04 2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64.conf

*.conf文件相关的编号是唯一的。BLS 有自己的优势和劣势。

以下是优点:

  • 每个操作系统都有平等的启动机会。

  • 它的工作与 BIOS 和 UEFI 固件无关。

  • 在 BIOS 的情况下,最新的 Linux 安装删除了早期安装的操作系统的第一部分和第二部分,这已经变得过时,因为最新的 Linux 安装将通过早期操作系统上的kernel-install命令创建自己的条目。

以下是缺点:

  • BLS 尚未完全实现。如果第二个操作系统想要在第一个操作系统中创建它的条目,那么第一个操作系统的/boot必须被共享。目前情况并非如此。所以,我认为这是一个半实现。

  • BLS 不必要地使引导序列变得复杂,因为我们有两个配置文件需要参考:来自/boot/loader/entries/grub.conf<uniq_no><kernel_version>.conf。在解决“无法启动”问题的情况下,BLS 尤其让生活变得困难。

  • 除了基于 Fedora 的发行版,还没有人采用 BLS,这似乎是一个明智的决定。看起来 Fedora 是最致力于上游项目的;因此,BLS 已经在 Fedora 中实现。

常见的引导程序问题

基于这些知识,让我们尝试解决一些最常见的与引导程序相关的“无法引导”问题。

“无法引导”问题 1(引导加载程序)

**问题:**系统加电后,在 GRUB 提示符下退出,如图 3-11 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-11

GRUB 2 提示符

这是你在屏幕上看到的。你一生中肯定至少遇到过一次这种错误。让我们试着解决它。

  1. 只有当你知道问题是什么的时候,你才能解决问题。但是现在,我们不知道问题出在哪里,因为我们刚刚启动这个系统,这就是我们得到的结果。

  2. 该屏幕称为 GRUB 提示符。当这被称为提示符时,意味着您可以在它上面执行命令。请记住,这是一个 GRUB 命令提示符,这意味着它只能接受 GRUB 命令。

  3. 通过查看图 3-11 ,在 GRUB 的三个部分中,GRUB 的哪个部分为我们提供了 GRUB 提示符?

  4. 当然,它必须是 part-3,因为 part-1 和 part-2 的空间很小,所以它们无法容纳这样的功能。所以,我们已经成功地完成了 GRUB 的第三部分,最重要的是,这个系统是有 UEFI 还是 BIOS 并不重要。既然我们已经到了第三部分,这意味着我们已经离开了固件环境。这是至关重要的输入。现在我们不能只关注第三部分。

  5. GRUB 第三部分的目的是什么?很简单。它读取grub.cfg,并从那里获得内核和 initramfs 的位置。如果它是一个支持 BLS 的系统,那么它从/boot/loader/entries/目录中获取内核和 initramfs 名称。对于这个例子,我们将假设这个系统不知道 BLS。然后,第三部分在内存中加载vmlinuz和 initramfs。

  6. 由于第三部分已经向我们提供了 GRUB 提示符,但未能加载 OS,这意味着要么内核和 initramfs 文件不存在,要么grub.cfg文件没有指出这些文件的正确位置。

  7. 因此,在这种情况下,我们可以尝试手动引导 Fedora。手动意味着我们将使用 GRUB 提示符为内核和 initramfs 文件提供绝对路径。这是可以做到的。

  8. linux is a GRUB command through which we need to give the absolute path of the kernel (vmlinuz) file. As we know, the vmlinuz file is at /boot, and GRUB follows its own disk naming convention. So, the path of /boot will be hard disk number 0 and partition number 1. Of course, you might not be aware on which HDD or partition /boot has been stored. In that case, you can get the help of the autocomplete feature of GRUB. You can press Tab twice, and GRUB will prompt you for the available options. Let’s find out the HDD and partition number of /boot. Please refer to Figure 3-12.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-12

    0 号硬盘上的可用分区

    The first tab after hd0 showed us that there are two partitions available under the hard disk number 0. The second partition is not readable to GRUB, so of course the second partition cannot be /boot. Hence, we will choose the msdos1 partition. Then, as shown in Figure 3-13, we will start looking for the vmlinuz file in it with the help of autocomplete.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-13

    vmlinuz 文件

    As you can see inside HDD number 0 and partition number 1, we found two vmlinuz files; one is of a rescue kernel, and another one is the normal kernel file of Fedora 31. As shown in Figure 3-14, we will choose the normal kernel and will provide the root filesystem name to it. If you are unaware of the root filesystem name of your system, then you can boot the system with the rescue or live image and check the /etc/fstab entries. We will talk about the rescue mode in Chapter 10.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-14

    根文件系统名称和 ro 标志

    The absolute path of the vmlinuz file is (hd0,msdos1)/vmlinuz-5.3.7-301.fc31.x86_64. Next to it is the ro kernel command-line parameter, which stands for “read-only.” After ro, we have a root kernel command-line parameter to which we have passed our system’s root filesystem name, which is - /dev/mapper/fedora_localhost--live-root. It’s an lvm device.

    grub> linux (hd0,msdos1)/vmlinuz-5.3.7-301.fc31.x86_64 ro
         root=/dev/mapper/fedora_localhost--live-root
    
    

    After successfully executing the linux command, we need to pass on the initramfs name. We have two commands available that we can use: initrd and initrd16. Please refer to Figure 3-15.

    grub> initrd (hd0,msdos1)/initramfs-5.3.7-301.fc31.x86_64.img
    
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-15

    运行中的 linux、initrd 和 boot 命令

  9. The moment you execute the boot command, as shown in Figure 3-16 and in Figure 3-17, GRUB’s part-3 will take these inputs and load /boot/vmlinuz-5.3.7-301.fc31.x86_64 from sda1 (hd0,msdos1). Then it will load /boot/initramfs-5.3.7-301.fc31.x86_64.img and give control to the kernel. The kernel will eventually mount the root (/) filesystem from /dev/mapper/fedora_locahost--live-root on the / directory and will show the login screen.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-17

    登录屏幕

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 3-16

    引导时的控制台消息

  10. 在 Ubuntu 18 的情况下,命令略有不同。在 Fedora 31 上,我们将/boot分区的地址直接给了linux命令,而在 Ubuntu 中,我们有一个单独的 GRUB 命令,名为set root

如图 3-18 所示,Ubuntu 18 系统的根文件系统名称是/dev/sda1。这是一个标准分区,不像 Fedora 31 的lvm设备。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-18

Ubuntu 有一个稍微不同的方法

一旦我们向 GRUB 2 提供了正确的输入,它就会把我们带到登录屏幕。图 3-19 可以看到 Ubuntu 的登录界面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-19

Ubuntu 显示的登录屏幕

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-20

grub2-mkconfig 命令

  1. 回到我们的 Fedora 系统,由于现在已经启动,我们可以使用grub2-mkconfig命令重新生成grub.cfg文件,如图 3-20 所示。

我们可以在 Ubuntu 的情况下执行grub-mkconfig。请参见图 3-21 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-21

Ubuntu 的 grub-mkconfig 命令

但是如果是 UEFI 系统,你想再生grub.cfg,那么如图 3-22 所示,grub.cfg的位置就是 ESP。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-22

基于 UEFI 的系统上的 grub2-mkconfig

  1. 一旦生成了grub.cfg,我们需要为 Fedora 重新生成 BLS 条目。
#kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz

该命令将在/boot/loader/entries/中为kernel-5.3.7-301.fc31.x86_64创建适当的条目。

  1. 如果 Fedora 在 UEFI 系统上,那么 BLS 步骤保持不变。

  2. 重启后,Fedora 能够顺利启动,“无法启动”的问题已经修复。

# ls /boot/loader/entries/ -l
total 8
-rw-r--r--. 1 root root 329 Dec  9 10:18 2058a9f13f9e489dba29c477a8ae2493-0-rescue.conf
-rw-r--r--. 1 root root 249 Oct 22 01:04 2058a9f13f9e489dba29c477a8ae2493-5.3.7-301.fc31.x86_64.conf

“无法引导”问题 2(引导加载程序)

**问题:**系统上电后,通过固件阶段,但之后如图 3-23 所示,屏幕上什么都没有。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-23

空白屏幕

基于 BIOS 的系统的分辨率

以下是解决这个问题的步骤:

  1. 由于 BIOS 固件阶段已经过去,这意味着在引导装载程序级别有问题。

  2. 因为我们在屏幕上看不到任何东西,这意味着 GRUB 的第一部分或第二部分丢失了,或者至少它们被破坏了(512 字节+ 31 KB)。如果它到达了第三部分,那么我们至少会得到 GRUB 提示符。因此,这个问题已经被隔离,行动计划是替换 GRUB 的第一部分和第二部分。

  3. 这可以通过grub2-install命令来完成。首先用相同 Linux 发行版的 live 介质引导,或者,如果可以的话,用救援模式引导。现场图像和救援模式将在第十章中解释。

如图 3-24 所示,grub2-install将设备名称作为输入。请注意,设备名称不应是分区号;相反,它应该是一个磁盘名称。这是因为 GRUB 的第一部分和第二部分必须安装在磁盘的前 512 字节+ 31 KB 上,而不是安装在分区内。您需要用您的磁盘名称替换 sda。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-24

grub2-install 命令

随着引导程序文件的第一部分和第二部分,grub2-install修复或重新安装i386-pc目录,其中包含 GRUB 2 引导程序的所有模块。我们可以通过在自定义目录中安装模块来交叉验证这一点。请参见图 3-25 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-25

在临时目录中安装 grub2

您可以看到所有的 GRUB 2 文件和 GRUB 的模块文件都已经恢复。

# ls temp/grub2/
      fonts  grubenv  i386-pc
# ls -l temp/grub2/i386-pc/ | wc -l
      279

重启后,Fedora 应该可以正常启动,“无法启动”的问题应该已经修复。如果 GRUB 让您进入命令提示符,那么您需要遵循问题 1 中提到的步骤,因为grub2-install会修复二进制文件,但它不会重新生成grub.cfg文件。

但是,如果您在基于 UEFI 的系统上面临类似的问题,该怎么办呢?

基于 UEFI 的系统的解决方案

以下是步骤:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-26

基于 UEFI 的系统上的 grub-install 命令

  1. 正如你可能已经猜到的,我们只需改变grub2-install命令传递的设备名称,如图 3-26 所示。设备名称应该是 ESP。

“无法引导”问题 3(引导程序+内核)

**问题:**完整的/boot不见了。

基于 BIOS 的系统的分辨率

以下是步骤:

  1. 找回丢失的/boot是不可能的(或者至少超出了本书的范围)。

  2. 在救援模式下启动,或者用一个动态镜像启动,并挂载我们“不能启动”的系统的根文件系统。救援模式及其工作原理将在第十章中讨论。

  3. 首先创建一个新的/boot目录,并在其上设置适当的权限。

    • #mkdir /boot

    • #chmod 555 /boot

    • #chown root:root /boot

    • 如果/boot应该是一个单独的分区,那么用正确的分区挂载它。

  4. 正如我们所知,/boot是我们存储引导程序、内核和 initramfs 文件的地方。因为/boot丢失了,我们需要为它创建每个文件。

    • #dnf reinstall kernel
      • 这是一个基于 Fedora 的系统。如果是基于 Debian 的系统,那么可以使用apt-get命令,并且可以重装内核。

      • 这将安装vmlinuz文件并为其重新生成 initramfs 文件。

  5. 现在我们需要安装 GRUB。

    • # grub 2-安装/开发/ <磁盘名称>

      • 在我们的例子中,命令是#grub2-install /dev/sda.
    • 这将从/boot/grub2修复 GRUB 的 part-1、part-2 和i386-pc目录。

    • 为了修复 GRUB 的第三部分并使用 GRUB 提供的工具,我们需要在基于 Fedora 的系统上安装两个包。

      • #dnf reinstall grub2 grub2-tools

      • 顾名思义,grub2包将提供 GRUB 的第三部分,grub2-tools将提供一些类似grub2-install的工具。

    • 现在是重新生成 GRUB 配置文件的时候了。

      • #grub2-mkconfig -o /boot/grub2/grub.cfg
    • 最后,修复 BLS。

      • #kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz
基于 UEFI 的系统的解决方案

以下是步骤:

  • /boot and /boot/efi/是单独的挂载点。

    • # mkdir /boot

    • # chmod 555 /boot

    • # chown root:root /boot

    • # yum reinstall kernel

  • 现在我们需要创建一个 ESP 分区,正如我们所知,它必须是一个 VFAT 分区。然后给它分配一个 ESP 分区类型。

    • #mkdir /boot/efi

    • #mount /dev/sda2 /boot/efi

      • 在我们的例子中,我为 ESP 创建的分区是 sda2。
    • #grub2-install --efi-directory=/boot/efi

      • 这将在 ESP 中安装grubx64.efi文件。
    • 其余的必需文件由grub2-efishimgrub2-tools包提供。

      • #yum reinstall grub2-efi shim grub2-tools
    • 重新生成配置文件。

      • #grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg

      • #kernel-install add 5.3.7-301.fc31.x86_64 /lib/modules/5.3.7-301.fc31.x86_64/vmlinuz

重新启动系统后,它能够顺利启动。

现在是时候对 UEFI 的安全引导环境进行更多的阐述了。

UEFI 的安全引导功能

安全引导是 UEFI 的一个令人惊叹的功能。它确保启动时不会运行不可信的二进制文件。到目前为止,我们已经看到了以下内容:

  • 数字签名是唯一的字符串。

    • 任何文件的数字签名都将由私钥生成。

    • 可以从公钥中重新生成相同的数字签名。

    • 如果文件没有被修改,那么数字签名应该匹配。

  • 微软制作了它的密钥对(公钥和私钥)。

  • Microsoft 用其私钥对其引导加载程序相关文件(BCD)进行了数字签名。

  • 微软的公钥存在于 UEFI 内部。

  • 引导时,UEFI 将使用可用的公钥重新生成引导程序的数字签名。如果数字签名不匹配,那么 UEFI 将放弃执行.efi文件。

  • 为了在 Linux 环境中使用这个特性,已经创建了一个名为shim的新引导加载程序,它已经用微软的私钥进行了签名,因此 UEFI 将允许执行shim.efi

  • Shim.efi的工作是调用实际的 GRUB 文件,也就是grubx64.efi

但是安全引导并没有就此停止。因为有可能grubx64.efi本身已经被破坏,或者事实上在引导装载程序之后运行的任何代码都可能已经被破坏,所以仅仅保证引导环境达到引导装载程序级别是不够的;因此,现在安全引导特性保护了 Linux 的整个引导过程。它是这样工作的:

  1. Fedora 将准备自己的密钥对,并用 Fedora 的私钥签署 GRUB 文件。

  2. Fedora 的公钥将保存在shim.efi文件中。

  3. 随着引导序列的继续,GRUB 的数字签名将通过使用shim.efi中的公钥重新生成。

  4. 如果签名匹配,那么grubx64.efi和其他引导程序文件将被 UEFI 允许运行。

  5. GRUB 的最终工作是加载内核(/boot/vmlinuz)。

  6. 这个vmlinuz文件也可能被破坏,因此为了避免这种情况,内核将由用于签名 GRUB 的同一个私钥进行签名。

  7. 将使用shim.efi中的公钥重新生成Vmlinuz'的数字签名。

  8. 一旦数字签名匹配,内核就会控制引导序列。

  9. 但是内核使用了大量的模块/驱动程序,这些模块/驱动程序最终被插入内核内部。所以,这些二进制的模块可能会受到损害,而且由于它们将成为 kernel/ vmlinuz的一部分,最终内核本身也会受到损害。

  10. 因此,内核作为一个包将准备自己的密钥对。所有模块都将由这个内核的私钥签名,公钥将随内核包一起提供。内核包的私有密钥将在以后被销毁。

  11. 在引导时,在内核中插入模块时,模块的数字签名将通过使用内核中的公钥重新生成。

  12. 按照上面提到的步骤,安全引导特性确保只执行来自可信方的二进制文件。

图 3-27 所示的框图将进一步简化启动程序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-27

安全引导过程

100 操作系统多重引导项目

我的一个学生问了我一个问题:我们可以在一个系统上安装多少个操作系统,并用一个引导程序多重引导它们?我不知道答案,但我决定试着找出答案。我决定使用 GRUB 2 引导程序来引导我安装的每个操作系统。我已经安装和引导操作系统将近两年了。到目前为止,我已经安装了 106 个操作系统。这是我们的第三个系统,我命名为贾维斯。以下是 Jarvis 的硬件和软件细节:

  • UEFI 韧体。

  • 连接了两个磁盘(sda 和 sdb)。

  • 引导方法是 UEFI。

  • sda 是用 MS-DOS 分区表格式化的。

  • sdb 用 GPT 分区表格式化。

  • 所有的操作系统都由 GRUB 2 引导程序识别和引导。

sda 磁盘上安装的操作系统是通过将引导方法设置为 UEFI 来安装的,它拥有所有新的操作系统。sdb 上的操作系统是通过将固件的引导方法设置为 legacy 来安装的。sdb 托管大多数老一代操作系统,或者至少是那些不支持 UEFI 的操作系统。以下是详细情况:

|

划分

|

操作系统

|

文件系统

|

大小

|
| — | — | — | — |
| sda-1 | 电子燃油喷射系统分区 | FAT32 | 20 GB |
| sda-2 | MSR(微软恢复) | 发动机阻力矩控制系统 | 16 兆字节 |
| sda-3 | Windows 10 | Windows NT 文件系统(NT File System) | 9.7 GB |
| sda-4 | 交换 | 交换 | 2.01 GB |
| sda-5 | openSUSE Linux 13.2 | EXT4 | 10 GB |
| sda-6 | 就像 Linux 17.2 | EXT4 | 10 GB |
| sda-7 | Oracle OpenSolaris 11.2 | ZFS | 10 GB |
| sda-8 | Sabayon Linux 15.06 版 | EXT4 | 10 GB |
| sda-9 | 一些随机的自由空间 | 不适用的 | 8.4 兆字节 |
| sda-10 | Kali Linux 2.0 版 | EXT4 | 10 GB |
| sda-11 | Arch Linux 2015-8.1 | EXT4 | 10 GB |
| sda-12 | Debian Linux 8.1 | EXT4 | 10 GB |
| sda-13 | 简单 Linux 7.0.1 | EXT4 | 10 GB |
| sda-14 | Slackware 14.1 Linux | EXT4 | 10 GB |
| sda-15 | Openmandriva 2014.2 | EXT4 | 10 GB |
| sda-16 | 杀死 Ubuntu Linux15.04 | EXT4 | 10 GB |
| sda-17 | 蒸汽 OS beta 版 | EXT4 | 10 GB |
| sda-18 | Manjaro Linux 0.8.13.1 版 | EXT4 | 10 GB |
| sda-19 | Netrunner Linux 16 | EXT4 | 10 GB |
| sda-20 | Windows 8 | Windows NT 文件系统(NT File System) | 10 GB |
| sda-21 | Korora Linux 22 | EXT4 | 10 GB |
| sda-22 | 卡奥斯 Linux 2015.08 | EXT4 | 10 GB |
| sda-23 | ubuntu linux 15.04 | EXT4 | 10 GB |
| sda-24 | 声纳 Linux 2015.2 | EXT4 | 10 GB |
| sda-25 | Linux 2015 年 8 月 18 日 | EXT4 | 10 GB |
| sda-26 | linux 月 14 日 | EXT4 | 10 GB |
| sda-27 | 罗莎 Linux 新鲜版 R5 | EXT4 | 10 GB |
| sda-28 | SparkyLinux 4.0 | EXT4 | 10 GB |
| sda-29 | Linux 4.0 葡萄酒 | EXT4 | 10 GB |
| sda-30 | xubuntu linux 14 . 04 . 3 版 | EXT4 | 10 GB |
| sda-31 | Ubuntu Studio 14.04.3 版 | EXT4 | 10 GB |
| sda-32 | Suse 企业 12 | EXT4 | 10 GB |
| sda-33 | Ubuntu Linux 14.04 | EXT4 | 10 GB |
| sda-34 | Ubuntu Linux 15.04 | EXT4 | 10 GB |
| sda-35 | 科学 Linux 7 | EXT4 | 10 GB |
| sda-36 | 数百个 Linux 7 | EXT4 | 10 GB |
| sda-37 | Solus Linux 日报 | EXT4 | 10 GB |
| sda-38 | Ubuntu Server 14 Linux | EXT4 | 10 GB |
| sda-39 | Fedora 21 Linux | EXT4 | 10 GB |
| sda-40 | Fedora 22 Linux | EXT4 | 10 GB |
| sda-41 | 黑拱门 | EXT4 | 10 GB |
| sda-42 | gentoo linux multilib 20140826 | EXT4 | 10 GB |
| sda-43 | 计算 Linux 14.16.2 | EXT4 | 10 GB |
| sda-44 | Fedora 20 Linux | EXT4 | 10 GB |
| sda-45 | Fedora 23 Linux | EXT4 | 10 GB |
| sda-46 | Manjaro Linux 15-0.9 版 | EXT4 | 10 GB |
| sda-47 | Ubuntu Linux 16.04 | EXT4 | 10 GB |
| sda-48 | Linux 23 帽子 | EXT4 | 10 GB |
| sda-49 | Linux 22 归档类型 | EXT4 | 10 GB |
| sda-50 | Fx64 Linux 22 | EXT4 | 10 GB |
| sda-51 | 毒蛇 Linux 7 | EXT4 | 10 GB |
| sda-52 | Hanthana Linux 21 | EXT4 | 10 GB |
| sda-53 | Qubes R3.1 Linux | EXT4 | 10 GB |
| sda-54 | Fedora 24 | EXT4 | 10 GB |
| sda-55 | 科罗拉-23 | EXT4 | 10 GB |
| sda-56 | 萨巴永-16 | EXT4 | 10 GB |
| sda-57 | 科罗拉-24 | EXT4 | 10 GB |
| sda-58 | 声纳 16 Linux | EXT4 | 10 GB |
| sda-59 | 蝰蛇 9 Linux | EXT4 | 10 GB |
| sda-60 | Linux 23 归档类型 | EXT4 | 10 GB |
| sda-61 | Manjaro Linux 16 | EXT4 | 10 GB |
| sda-62 | Manjaro Linux 游戏 16 | EXT4 | 10 GB |
| sda-63 | 计算 Linux 15 | EXT4 | 10 GB |

因此,sda 磁盘上 UEFI OS 安装的总数是 59,因为 4 个分区是为类似 ESP 和 MSR 的东西保留的。以下是 sdb 磁盘安装的详细信息:

|

划分

|

操作系统

|

文件系统

|

大小

|
| — | — | — | — |
| sdb-1 | PCBSD 10.1.2 | ZFS | 10 GB |
| sdb-2 | Linux 魔法 2 | EXT4 | 10 GB |
| sdb-3 | Linux 魔法 3 | EXt4 | 10 GB |
| sdb-4 | 扩展/二级 | 不适用的 | 大约 970 GB |
| sdb-5 | Q4 Linux 1 . 2 . 8 作业系统 | EXT4 | 10 GB |
| sdb-6 | R2 Linux | EXT4 | 10 GB |
| sdb-7 | Pardus Linux 2013 版 | EXT4 | 10 GB |
| sdb-8 | 奥林帕斯 015 | EXT4 | 10 GB |
| sdb-9 | Crux Linux 3.1 | EXT4 | 10 GB |
| sdb-10 | 点 Linux 3.0 | EXT4 | 10 GB |
| sdb-11 | Extix Linux 15.3 | EXT4 | 10 GB |
| sdb-12 | Bodhi Linux 3.0 版 | EXT4 | 10 GB |
| sdb-13 | Debian Linux 7.0 | EXT4 | 10 GB |
| sdb-14 | Debian Linux 6.0 | EXT4 | 10 GB |
| sdb-15 | BOSS Linux 6.1 | EXT4 | 10 GB |
| sdb-16 | CrunchBang rc1 Linux 游戏 | EXT4 | 10 GB |
| sdb-17 | Linux 2.1 手机 | EXT4 | 10 GB |
| sdb-18 | Linux 2.4 版 | EXT4 | 10 GB |
| sdb-19 | Linux R9 瓦数 | EXT4 | 10 GB |
| sdb-20 | 平谷 14.04.3 Linux | EXT4 | 10 GB |
| sdb-21 | SuperX 3.0 Linux | EXT4 | 10 GB |
| sdb-22 | JuLinux 10X 版本 3.1 Linux | EXT4 | 10 GB |
| sdb-23 | 黑实验室 Linux 2015.7 | EXT4 | 10 GB |
| sdb-24 | Hamara Linux 1.0.3 | EXT4 | 10 GB |
| sdb-25 | 薄荷 LInux 20150518 | EXT4 | 10 GB |
| sdb-26 | Ubuntu 13.10 Linux | EXT4 | 10 GB |
| sdb-27 | linuxmint 13 杀手 | EXT4 | 10 GB |
| sdb-28 | Linux 薄荷 14.1 肉桂 | EXT4 | 10 GB |
| sdb-29 | linuxmint 15 xfce | EXT4 | 10 GB |
| sdb-30 | linuxmint 系统 16 KDE | EXT4 | 10 GB |
| sdb-31 | 薄荷 4 20131113 | EXT4 | 10 GB |
| sdb-32 | 薄荷 5 20140623 | EXT4 | 10 GB |
| sdb-33 | Fedora 12 | EXT4 | 10 GB |
| sdb-34 | 哪 7 个 Linux | EXT4 | 10 GB |
| sdb-35 | Oracle Linux 7.1 | EXT4 | 10 GB |
| sdb-36 | Fedora 14 Linux | EXT4 | 10 GB |
| sdb-37 | Fedora 15 Linux | EXT4 | 10 GB |
| sdb-38 | Fedora 17 Linux | EXT4 | 10 GB |
| sdb-39 | Fedora 19 Linux | EXT4 | 10 GB |
| sdb-40 | RHEL 6.5 Linux | EXT4 | 10 GB |
| sdb-41 | SolydX 201506 | EXT4 | 10 GB |
| sdb-42 | Oracle Linux 6.7 | EXT4 | 10 GB |
| sdb-43 | OpenSuse 11.3 | EXT4 | 10 GB |
| sdb-44 | LMDE (Linux Mint 2 Debian 版) | EXT4 | 10 GB |
| sdb-45 | Linux 12.04 中心 | EXT4 | 10 GB |
| sdb-46 | 基础操作系统 2013 | EXT4 | 10 GB |
| sdb-47 | 基础操作系统 2015 | EXT4 | 10 GB |
| sdb-48 | Sabayon 13.08 Linux | EXT4 | 10 GB |
| sdb-49 | Deepin 2013 Linux | EXT4 | 10 GB |
| sdb-50 | Deepin 15.1 Linux | EXT4 | 10 GB |

在 sdb 磁盘上以 BIOS 方式引导的操作系统总数为 50–2 = 48。

两个分区保留给交换分区和扩展分区。

因此,Jarvis 系统上的安装总数是 106,正如您在图 3-28 中看到的,所有这些操作系统都是通过使用 GRUB 2 引导程序进行多重引导的。有了这个项目,我意识到这是没有止境的。GRUB 2 和 UEFI 的组合可以处理 n 个操作系统。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-28

GRUB 2 列出的 106 种操作系统

我是如何安装这么多操作系统的?很简单。每次安装新的操作系统后,我都会启动grub-mkconfig命令,从所有连接的磁盘中找到所有的操作系统。

# time grub-mkconfig -o multiboot_grub.cfg

前面的命令是在安装了列表中第 106 个操作系统 Ubuntu 18 之后使用的。

如图 3-29 所示,当我安装第 106 个操作系统时,grub-mkconfig花了将近一个小时才完成,结果 GRUB 配置文件有 5500 行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-29

grb-mkconfig 命令花费的时间

一个虚拟的小型引导程序

我们知道 BIOS 跳转到第一个 512 字节并调用 GRUB 2 引导程序。为了理解 BIOS 是如何调用引导装载程序的,我们将制作自己的引导装载程序。与 GRUB 2 相比,我们的引导程序非常小。我们的引导程序会在屏幕上显示!。但是通过这个例子,您将能够理解 BIOS 如何像 GRUB 2 一样跳转到引导加载程序,如下所示:

#cat boot.nasm
    ;
    ; Note: this example is written in Intel Assembly syntax
    ;
     [BITS 16]
     [ORG 0x7c00]

    boot:
        mov al, '!'       <<-- Character for interrupt
        mov ah, 0x0e      <<-- Display character
        mov bh, 0x00      <<-- Set video mode
        mov bl, 0x07      <<-- Clear/Scroll screen down
        int 0x10          <<--- BIOS interrupt 10 which is taking inputs from al, ah, bh, bl
        jmp $
        times 510-($-$$) db 0      <<--- Out of 512 bytes first 510 bytes are filled  with 0's.
                                   In the real world it will be filled with grub's boot strap.
        db 0x55           <<-- &
        db 0xaa           <<-- | tells BIOS that this is the device which is active/fdisk sign/boot flag.

     #nasm -f bin boot.nasm && qemu-system-x86_64 boot

这将从boot.nasm文件中创建一个boot磁盘(磁盘映像),它将成为qemu的输入,后者将执行它。如图 3-30 所示,你会看到!印在屏幕上。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3-30

我们的小型引导程序

基本上,qemu机器把boot当作一个磁盘,每当qemu机器完成它的 BIOS 阶段,BIOS 就在引导磁盘的前 512 个字节处落下。在这里你会发现前 510 个字节被写成 0,最后 2 个字节我们有!(引导程序),它会被打印在我们的屏幕上。

到目前为止,我们已经很好地了解了 GRUB 2;在下一节中,我们将进一步讨论 GRUB 2 内部到底发生了什么。

低级别的 GRUB 2

在写这本书的时候,GRUB 最新可用的源代码是 GRUB 2.04,我一直在这里使用。从 512 字节的第一个 440 字节开始的引导二进制文件(如果系统是基于 BIOS 的)被称为boot.img,它在/usr/lib/grub/i386-pc/boot.img可用。

# ls -lh /usr/lib/grub/i386-pc/boot.img
-rw-r--r--. 1 root root 512 Mar 28  2019 /usr/lib/grub/i386-pc/boot.img

# file  /usr/lib/grub/i386-pc/boot.img
/usr/lib/grub/i386-pc/boot.img: DOS/MBR boot sector

boot.img文件是从写在文件/GRUB 2.04/grub-core/boot/i386/pc/boot.S中的源代码创建的。

以下是其中的一个片段:

<snip>
1 /* -*-Asm-*- */
  2 /*
  3  *  GRUB  --  GRand Unified Bootloader
  4  *  Copyright (C) 1999,2000,2001,2002,2005,2006,2007,2008,2009  Free Software Foundation, Inc.
  5  *
  6  *  GRUB is free software: you can redistribute it and/or modify
  7  *  it under the terms of the GNU General Public License as published by
  8  *  the Free Software Foundation, either version 3 of the License, or
  9  *  (at your option) any later version.
 10  *
 11  *  GRUB is distributed in the hope that it will be useful,
 12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  *  GNU General Public License for more details.
 15  *
 16  *  You should have received a copy of the GNU General Public License
 17  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 18  */
 19
 20 #include <grub/symbol.h>
 21 #include <grub/machine/boot.h>
 22
 23 /*
 24  *  defines for the code go here
 25  */
 26
 27         /* Print message string */
 28 #define MSG(x)  movw $x, %si; call LOCAL(message)
 29 #define ERR(x)  movw $x, %si; jmp LOCAL(error_message)
 30
 31         .macro floppy
 32 part_start:
 33
 34 LOCAL(probe_values):
 35         .byte   36, 18, 15, 9, 0
 36
 37 LOCAL(floppy_probe):
 38         pushw   %dx
 39 /*
 40  *  Perform floppy probe.
 41  */
 42 #ifdef __APPLE__
 43         LOCAL(probe_values_minus_one) = LOCAL(probe_values) - 1
 44         movw    MACRO_DOLLAR(LOCAL(probe_values_minus_one)), %si
 45 #else
 46         movw    MACRO_DOLLAR(LOCAL(probe_values)) - 1, %si
 47 #endif
 48
 49 LOCAL(probe_loop):
 50         /* reset floppy controller INT 13h AH=0 */
 51         xorw    %ax, %ax
 52         int     MACRO_DOLLAR(0x13)
 </snip>

您可以将boot.img视为引导加载程序的第一阶段或 GRUB 的第一部分。这个boot.img文件将控制权转移给diskboot.img,这是 GRUB 的第二部分。

# ls -lh /usr/lib/grub/i386-pc/diskboot.img
-rw-r--r--. 1 root root 512 Mar 28  2019 /usr/lib/grub/i386-pc/diskboot.img

# file /usr/lib/grub/i386-pc/diskboot.img
/usr/lib/grub/i386-pc/diskboot.img: data

diskboot.img文件由grub-2.04/grub-core/boot/i386/pc/diskboot.S.的源代码组成,以下是它的一个片段:

<snip>
1 /*
  2  *  GRUB  --  GRand Unified Bootloader
  3  * Copyright (C) 1999,2000,2001,2002,2006,2007,2009,2010 Free Software Foundation, Inc.
  4  *
  5  *  GRUB is free software: you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation, either version 3 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  GRUB is distributed in the hope that it will be useful,
 11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13  *  GNU General Public License for more details.
 14  *
 15  *  You should have received a copy of the GNU General Public License
 16  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 17  */
 18
 19 #include <grub/symbol.h>
 20 #include <grub/machine/boot.h>
 21
 22 /*
 23  *  defines for the code go here
 24  */
 25
 26 #define MSG(x)  movw $x, %si; call LOCAL(message)
 27
 28         .file   "diskboot.S"
 29
 30         .text
 31
 32         /* Tell GAS to generate 16-bit instructions so that this code works
 33            in real mode. */
 34         .code16
 35
 36         .globl  start, _start
 37 start:
 38 _start:
 39         /*
 40          * _start is loaded at 0x8000 and is jumped to with
 41          * CS:IP 0:0x8000 in kernel.
 42          */
 </snip>

然后,diskboot.img文件加载 GRUB 2 的实际核心部分,这是 GRUB 的第三部分。你也可以认为 GRUB 的第三部分是引导装载程序的一个内核。在这个阶段,GRUB 2 将能够读取文件系统。

# ls /boot/grub2/i386-pc/core.img -lh
-rw-r--r--. 1 root root 30K Dec  9 10:18 /boot/grub2/i386-pc/core.img

/GRUB 2.00/grub-core/kern/main.c开始,GRUB 2 设置根设备名,读取grub.cfg,最后显示操作系统列表供选择。

希望你现在明白 GRUB 2 是怎么工作的了。以下是我们到目前为止所讨论内容的简要总结:

  1. 引导装载程序是固件之后运行的第一个代码。

  2. bootloader/GRUB 将内核复制到内存中。

  3. 引导加载程序将 initramfs 映像加载到内存中,并给内核一个指向它的指针。

  4. 引导装载程序将控制权移交给内核。

四、内核

本章将介绍内核。

在内存中加载内核

这是一个有趣的章节。到目前为止,我们已经看到 GRUB 2 已经完全控制了引导过程。现在它必须将控制权交给内核。在这一章中,我们将会看到引导装载程序是如何以及在哪里装载内核的。换句话说,内核是怎么提取的?然后我们将看到 Linux 内核完成的与引导相关的任务,最后我们将看到内核如何启动 systemd。

Note

本章使用的内核源代码是版本kernel-5.4.4。当我写这本书的时候,那是最新的稳定代码;参见 https://www.kernel.org/ .关于这个主题的一个很好的参考资料是由 0xAX 写的《Linux 内部的*】一书。我从中学到了很多,我相信你也会的。你可以在 https://0xax.gitbooks.io/linux-insides/ 找到这本书。*

为了将控制权交给内核,引导装载程序必须完成两件主要的事情。

  • 将内核装入内存

  • 按照引导协议设置内核的一些字段

完整的启动协议可从 https://www.kernel.org/doc/Documentation/x86/boot.txt 获得。最初的引导协议是由 Linus Torvalds 定义的。

         ~                               ~
         |  Protected-mode kernel        |
 100000  +-------------------------------+
         |  I/O memory hole              |
 0A0000  +-------------------------------+
         |  Reserved for BIOS            | Leave as much as possible unused
         ~                               ~
         |  Command line                 | (Can also be below the X+10000 mark)
X+10000  +-------------------------------+
         |  Stack/heap                   | For use by the kernel real-mode code.
X+08000  +-------------------------------+
         |  Kernel setup                 | The kernel real-mode code.
         |  Kernel boot sector           | The kernel legacy boot sector.
      X  +-------------------------------+
         |  Boot loader                  | <- Boot sector entry point 0000:7C00\. You will see the same
         |                               | address location at our boot.asm file which we created above.
  001000 +-------------------------------+
         |  Reserved for MBR/BIOS        |
 000800  +-------------------------------+
         |  Typically used by MBR        |
 000600  +-------------------------------+
         |  BIOS use only                |
 000000  +-------------------------------+

按照引导协议,引导装载程序有责任传递或设置内核头的一些字段。这些字段是根设备名称、挂载选项(如rorw)、initramfs 名称、initramfs 大小等。这些相同的字段被称为内核命令行参数,我们已经知道内核命令行参数是由 GRUB/boot loader 传递给内核的。

GRUB 不会在任何随机位置加载内核(/boot/vmlinuz);它将总是被装载在一个特殊的位置。这个特殊的位置会根据您使用的 Linux 发行版和版本以及系统的 CPU 架构而有所不同。vmlinuz是一个归档文件,归档文件由三部分组成。

Vmlinuz (bZimage) =  Header   + kernel setup code + vmlinux (actual compressed kernel)
                     (part-1)   (part-2)            (part-3)

将内核加载到内存中后

这里我们需要想象 GRUB 2 已经在内存中的特殊位置加载了内核。下面是内核归档文件vmlinuz加载到内存后执行的初始步骤:

  1. 一旦引导加载程序将内核加载到内存中的特定位置,由文件arch/x86/boot/header.S生成的二进制文件就会运行。

  2. 如果vmlinuz是一个归档文件,而引导装载程序还没有提取它,就会出现混乱。引导加载程序刚刚在一个特定的位置加载了内核。那么为什么vmlinuz归档文件中的代码能够运行呢?

  3. 我们先看简答,长答在“什么提取了 vmlinuz?”本章第节。所以,简单的答案是由arch/x86/boot/header.S文件生成的二进制文件不在存档中;相反,它是执行kernel_setup任务的报头的一部分。标头在归档文件之外。

    Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel)
             --->Outside of archive<--- + -------->Inside archive<------->header.s file is here<---
    
    
  4. 现在让我们考虑vmlinuz已经被提取,让我们继续我们的引导序列。到目前为止,我们已经看到 GRUB 将内核加载到内存中的一个特殊位置,并运行由arch/x86/boot/header.S.生成的二进制文件。该二进制文件负责Kernel_setup部分.kernel_setup文件执行以下任务:

    1. 对齐段寄存器

    2. 设置堆栈和 BSS

在每一章中,一个流程图会给我们一个清晰的概念,让我们知道我们已经学了什么,以及在引导方面,我们已经达到了什么程度。图 4-1 显示了我们将在本章中构建的流程图的开始。显示由header.skernel_setup代码执行的动作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-1

kernel_setup 采取的步骤

  1. 然后在arch/x86/boot/main.c跳转到main()功能。main.c文件也是内核头文件的一部分,这个头文件在实际的归档文件之外。
Vmlinuz (bZimage) = Header + kernel setup code + vmlinux (actual compressed kernel)
         --->Outside of archive<--- + -------->Inside archive<---------
         --->main.c file is here<---

#vim arch/x86/boot/main.c
<snip>
134 void main(void)
135 {
136         /* First, copy the boot header into the "zeropage" */
137         copy_boot_params();
138
139         /* Initialize the early-boot console */
140         console_init();
141         if (cmdline_find_option_bool("debug"))
142                 puts("early console in setup code\n");
143
144         /* End of heap check */
145         init_heap();
146
147         /* Make sure we have all the proper CPU support */
148         if (validate_cpu()) {
149                 puts("Unable to boot - please use a kernel appropriate "
150                      "for your CPU.\n");
151                 die();
152         }
153
154         /* Tell the BIOS what CPU mode we intend to run in. */
155         set_bios_mode();
156
157         /* Detect memory layout */

158         detect_memory();
159
160         /* Set keyboard repeat rate (why?) and query the lock flags */
161         keyboard_init();
162
163         /* Query Intel SpeedStep (IST) information */
164         query_ist();
165
166         /* Query APM information */
167 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
168         query_apm_bios();
169 #endif
170
171         /* Query EDD information */
172 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
173         query_edd();
174 #endif
175
176         /* Set the video mode */
177         set_video();
178
179         /* Do the last things and invoke protected mode */
180         go_to_protected_mode();
181 }
</snip>

如您所见,main.c源代码负责以下内容:

  1. 它从引导装载程序复制引导参数(内核命令行参数)。copy_boot_params函数将用于复制引导加载程序传递的以下引导参数:

  2. 它初始化控制台,并检查用户是否传递了类似于debug的内核命令行参数。如果有,内核将在屏幕上显示详细级别的消息。

  3. 它初始化堆。

  4. 如果 CPU 不能被验证,那么它通过validate_cpu()函数抛出一个错误消息。像 Fedora 和 Ubuntu 这样的发行版定制了错误消息,从'unable to boot - please use the kernel appropriate for your cpu'到类似'The CPU is not supported'的东西。定制还会使内核恐慌,引导将被中止。

  5. 然后,它会检测内存布局,并在引导的早期阶段将其打印在屏幕上。使用'dmesg'命令可以在引导后看到相同的内存布局消息,如下所示:

debug, earlyprintk, ro, root, ramdisk_image, ramdisk_size etc.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-2

流程图

  1. 初始化键盘及其布局。

  2. 设置基本视频模式。

  3. 通过go_to_protected_mode()功能跳转到保护模式。请参考图 4-2 以便更好地理解。

[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000059000-0x000000000009cfff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009d000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007e5f7fff] usable
[    0.000000] BIOS-e820: [mem 0x000000007e5f8000-0x000000007e5f8fff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x000000007e5f9000-0x000000007e5f9fff] reserved
[    0.000000] BIOS-e820: [mem 0x000000007e5fa000-0x0000000087f62fff] usable
[    0.000000] BIOS-e820: [mem 0x0000000087f63000-0x000000008952bfff] reserved
[    0.000000] BIOS-e820: [mem 0x000000008952c000-0x0000000089599fff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x000000008959a000-0x00000000895fefff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000895ff000-0x00000000895fffff] usable
[    0.000000] BIOS-e820: [mem 0x0000000089600000-0x000000008f7fffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000f0000000-0x00000000f7ffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fe010000-0x00000000fe010fff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000086e7fffff] usable

保护模式

到目前为止,我们一直在实模式下工作,该模式有 20 位地址限制,因为我们可以访问高达 1 MB 的内存。通过go_to_protected_mode()函数,内核将 CPU 从实模式切换到保护模式。保护模式有 32 位地址限制,因此 CPU 可以访问高达 4 GB 的内存。简单地说,在实模式下,只有那些具有 16 位指令集的程序才会运行,例如 BIOS。在保护模式下,只有 32 位程序会运行。内核在保护模式下执行一些硬件相关的任务,然后在长模式下启动 CPU。

请注意,本书遵循 Intel 的 X86 架构,实模式、保护模式、长模式的讨论都是基于 Intel 的 64 位架构。

长模式

长模式对 CPU 没有任何内存限制。它可以使用所有已安装的内存。将 CPU 置于长模式将通过来自arch/x86/boot/compressed/head_64.Shead_64.S文件实现。它负责以下工作:

  1. 准备长模式意味着它将检查它是否支持长模式。

  2. 进入长模式。

  3. 解压内核。

以下是从head_64.S汇编文件中调用的函数:

|

功能

|

工作

|
| — | — |
| verify_cpu | 这将确保 CPU 具有长模式。 |
| make_boot_params | 这将负责引导加载程序传递的引导时参数。 |
| efi_main | UEFI 固件相关的东西。 |
| extract_kernel | 该功能在arch/x86/boot/compressed_misc.c中定义。这是将从vmlinuz解压vmlinux的函数。 |

$ cat arch/x86/boot/compressed/head_64.S | grep -i call
    call    1f
    call    verify_cpu
    call    get_sev_encryption_bit
    call    1f
    call    1f
    call    .Ladjust_got
     * this function call.
    call    paging_prepare
     * this function call.
    call    cleanup_trampoline
    call    1f
    call    .Ladjust_got
    call    1f
     * Relocate efi_config->call().
    call    make_boot_params
    call    1f
     * Relocate efi_config->call().
    call    efi_main
    call    extract_kernel    /* returns kernel location in %rax */
    .quad    efi_call

为了更好地理解,请参考图 4-3 所示的流程图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-3

更新后的流程图

等一下:如果内核还没有被解压缩,那么我们现在怎么继续呢?下面是长答案。

什么提取 vmlinuz?

到目前为止,我们知道是 GRUB 在内存中加载内核,但同时,我们注意到vmlinuz映像是一个归档文件。那么,是什么提取了这个图像呢?是幼虫吗?

不,这不是食物。更确切地说,是内核自己提取。对,我说的是提取内核的内核。vmlinuz可能是操作系统世界中唯一能自我解压的文件。但是怎么可能提取自己呢?为了理解这一点,让我们先对vmlinuz有更多的了解。

vmlinuz的“vm”代表“虚拟内存”。在 Linux 开发的早期阶段,虚拟内存的概念还没有发展起来,所以在添加虚拟内存时,会将“vm”字符添加到 Linux 内核的名称中。“z”代表压缩文件。

$ file vmlinuz-5.0.9-301.fc30.x86_64

vmlinuz-5.0.9-301.fc30.x86_64: Linux kernel x86 boot executable bzImage, version 5.0.9-301.fc30.x86_64 (mockbuild@bkernel04.phx2.fedoraproject.org) #1 SMP Tue Apr 23 23:57:35 U, RO-rootFS, swap_dev 0x8, Normal VGA

如你所见,vmlinuz就是bzImage ( bzImage代表“大紫马哥”)。vmlinuz是实际内核的二进制文件vmlinux的压缩文件。你不能用gunzip/bunzip甚至tar解压这个文件。提取vmlinuz并获得vmlinux文件的最简单方法是使用kernel-devel包提供的extract-vmlinux脚本文件(在 Fedora 的情况下)。文件将出现在/usr/src/kernels/<kernel_version>/scripts/extract-vmlinux

# ./extract-vmlinux /boot/vmlinuz-5.3.7-301.fc31.x86_64 >> /boot/temp/vmlinux
# file /boot/temp/*
/boot/temp/vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
statically linked, BuildID[sha1]=ec96b29d8e4079950644230c0b7868942bb70366, stripped

打开vmlinuxvmlinuz内核文件有多种方式。

     $ xxd vmlinux | less
     $ objdump vmlinux | less
     $ objdump vmlinux -D | less
     $ hexdump vmlinux | less
     $ od vmlinux | less

我们将使用带有一些开关的od命令来打开vmlinuz文件。

     $ od -A d -t x1 vmlinuz-5.0.9-301.fc30.x86_64 | less
<snip>
0000000 4d 5a ea 07 00 c0 07 8c c8 8e d8 8e c0 8e d0 31
0000016 e4 fb fc be 40 00 ac 20 c0 74 09 b4 0e bb 07 00
0000032 cd 10 eb f2 31 c0 cd 16 cd 19 ea f0 ff 00 f0 00
0000048 00 00 00 00 00 00 00 00 00 00 00 00 82 00 00 00
0000064 55 73 65 20 61 20 62 6f 6f 74 20 6c 6f 61 64 65
0000080 72 2e 0d 0a 0a 52 65 6d 6f 76 65 20 64 69 73 6b
0000096 20 61 6e 64 20 70 72 65 73 73 20 61 6e 79 20 6b
0000112 65 79 20 74 6f 20 72 65 62 6f 6f 74 2e 2e 2e 0d
0000128 0a 00 50 45 00 00 64 86 04 00 00 00 00 00 00 00
0000144 00 00 01 00 00 00 a0 00 06 02 0b 02 02 14 80 37
0000160 8e 00 00 00 00 00 80 86 26 02 f0 48 00 00 00 02
0000176 00 00 00 00 00 00 00 00 00 00 20 00 00 00 20 00
0000192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000208 00 00 00 c0 b4 02 00 02 00 00 00 00 00 00 0a 00
0000224 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000256 00 00 00 00 00 00 06 00 00 00 00 00 00 00 00 00
0000272 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000288 00 00 00 00 00 00 00 00 00 00 80 39 8e 00 48 09
0000304 00 00 00 00 00 00 00 00 00 00 2e 73 65 74 75 70
0000320 00 00 e0 43 00 00 00 02 00 00 e0 43 00 00 00 02
0000336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000352 50 60 2e 72 65 6c 6f 63 00 00 20 00 00 00 e0 45
0000368 00 00 20 00 00 00 e0 45 00 00 00 00 00 00 00 00
0000384 00 00 00 00 00 00 40 00 10 42 2e 74 65 78 74 00

0000400 00 00 80 f3 8d 00 00 46 00 00 80 f3 8d 00 00 46
0000416 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00
0000432 50 60 2e 62 73 73 00 00 00 00 80 86 26 02 80 39
0000448 8e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000464 00 00 00 00 00 00 80 00 00 c8 00 00 00 00 00 00
0000480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff
0000496 ff 22 01 00 38 df 08 00 00 00 ff ff 00 00 55 aa
0000512 eb 66 48 64 72 53 0d 02 00 00 00 00 00 10 c0 37
0000528 00 01 00 80 00 00 10 00 00 00 00 00 00 00 00 00
0000544 00 00 00 00 50 5a 00 00 00 00 00 00 ff ff ff 7f
0000560 00 00 00 01 01 15 3f 00 ff 07 00 00 00 00 00 00
0000576 00 00 00 00 00 00 00 00 b1 03 00 00 11 f3 89 00
0000592 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00
0000608 00 c0 b4 02 90 01 00 00 8c d8 8e c0 fc 8c d2 39
0000624 c2 89 e2 74 16 ba 50 58 f6 06 11 02 80 74 04 8b
</snip>

# od -A d -t x1 /boot/vmlinuz-5.3.7-301.fc31.x86_64 | grep -i '1f 8b 08 00'
0018864 8f 1f 8b 08 00 00 00 00 00 02 03 ec fd 79 7c 54

所以,在0018864,实际的内核(vmlinux)开始,而vmlinuz文件在0000000.开始,这意味着从00000000018864,我们拥有的是文件的头,比如header.Smisc.c等。这将从vmlinuz.中提取实际的内核(vmlinux)你可以把一个头文件看作是vmlinux二进制文件上的一个帽,当这个帽可用时,它就变成了vmlinuz。在接下来的部分中,我们将看到内核例程如何提取vmlinuz

提取 _ 内核

让我们从arch/x86/boot/compressed/misc.c回到extract_kernel函数。

asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
                                          unsigned char *input_data,
                                          unsigned long input_len,
                                          unsigned char *output,
                                          unsigned long output_len)

如您所见,该函数将接受七个参数。

|

争吵

|

目的

|
| — | — |
| rmode | 由引导装载程序填充的指向boot_params结构的指针 |
| heap | 指向代表早期引导堆起始地址的文件的指针 |
| input_data | 指向压缩内核起点的指针,或者换句话说,指向arch/x86/boot/compressed/vmlinux.bin.bz2的指针 |
| input_len | 压缩内核的大小 |
| output | 未来解压缩内核的起始地址 |
| output_len | 解压缩内核的大小 |
| run_size | 运行内核所需的空间量,包括.bss.brk部分 |

除了内核,引导装载程序还将在内存中装载 initramfs。我们将在第五章中讨论 initramfs。因此,在提取内核映像之前,头文件或内核例程必须注意vmlinuz提取不会覆盖或重叠已经加载的 initramfs 映像。因此,extract_kernel函数也将负责计算 initramfs 地址空间,并相应地调整内核映像解压缩。一旦我们得到了头文件可以解压vmlinuz的正确地址,它将在那里提取内核。

340 asmlinkage __visible void *extract_kernel(void *rmode, memptr heap,
341                                   unsigned char *input_data,
342                                   unsigned long input_len,
343                                   unsigned char *output,
344                                   unsigned long output_len)
345 {
346         const unsigned long kernel_total_size = VO__end - VO__text;
347         unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
348         unsigned long needed_size;
349
350         /* Retain x86 boot parameters pointer passed from startup_32/64\. */
351         boot_params = rmode;
352
353         /* Clear flags intended for solely in-kernel use. */
354         boot_params->hdr.loadflags &= ~KASLR_FLAG;
355
356         sanitize_boot_params(boot_params);
357
358         if (boot_params->screen_info.orig_video_mode == 7) {
359                 vidmem = (char *) 0xb0000;
360                 vidport = 0x3b4;
361         } else {
362                 vidmem = (char *) 0xb8000;
363                 vidport = 0x3d4;
364         }
365
366         lines = boot_params->screen_info.orig_video_lines;
367         cols = boot_params->screen_info.orig_video_cols; 

368
369         console_init();
370
371         /*
372          * Save RSDP address for later use. Have this after console_init()
373          * so that early debugging output from the RSDP parsing code can be
374          * collected.
375          */
376         boot_params->acpi_rsdp_addr = get_rsdp_addr();
377
378         debug_putstr("early console in extract_kernel\n");
379
380         free_mem_ptr     = heap;        /* Heap */
381         free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
382
383         /*
384          * The memory hole needed for the kernel is the larger of either
385          * the entire decompressed kernel plus relocation table, or the
386          * entire decompressed kernel plus .bss and .brk sections.
387          *
388          * On X86_64, the memory is mapped with PMD pages. Round the
389          * size up so that the full extent of PMD pages mapped is
390          * included in the check against the valid memory table
391          * entries. This ensures the full mapped area is usable RAM
392          * and doesnt include any reserved areas.
393          */
394         needed_size = max(output_len, kernel_total_size);
395 #ifdef CONFIG_X86_64
396         needed_size = ALIGN(needed_size, MIN_KERNEL_ALIGN);
397 #endif
398
399         /* Report initial kernel position details. */
400         debug_putaddr(input_data);
401         debug_putaddr(input_len);
402         debug_putaddr(output);
403         debug_putaddr(output_len);
404         debug_putaddr(kernel_total_size);
405         debug_putaddr(needed_size);
406
407 #ifdef CONFIG_X86_64

408         /* Report address of 32-bit trampoline */
409         debug_putaddr(trampoline_32bit);
410 #endif
411
412         choose_random_location((unsigned long)input_data, input_len,
413                                 (unsigned long *)&output,
414                                 needed_size,
415                                 &virt_addr);
416
417         /* Validate memory location choices. */
418         if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
419                 error("Destination physical address inappropriately aligned");
420         if (virt_addr & (MIN_KERNEL_ALIGN - 1))
421                 error("Destination virtual address inappropriately aligned");
422 #ifdef CONFIG_X86_64
423         if (heap > 0x3fffffffffffUL)
424                 error("Destination address too large");
425         if (virt_addr + max(output_len, kernel_total_size) > KERNEL_IMAGE_SIZE)
426                 error("Destination virtual address is beyond the kernel mapping area");
427 #else
428         if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))
429                 error("Destination address too large");
430 #endif
431 #ifndef CONFIG_RELOCATABLE
432         if ((unsigned long)output != LOAD_PHYSICAL_ADDR)
433                 error("Destination address does not match LOAD_PHYSICAL_ADDR");
434         if (virt_addr != LOAD_PHYSICAL_ADDR)
435                 error("Destination virtual address changed when not relocatable");
436 #endif
437
438         debug_putstr("\nDecompressing Linux... ");
439         __decompress(input_data, input_len, NULL, NULL, output, output_len,
440                         NULL, error);
441         parse_elf(output);
442         handle_relocations(output, output_len, virt_addr);
443         debug_putstr("done.\nBooting the kernel.\n");
444         return output;
445 }

解压缩方法将根据内核编译时使用的压缩算法来选择。解压缩方法可以在同一个misc.c文件中看到。

           <snip from misc.c>
 57 #ifdef CONFIG_KERNEL_GZIP
 58 #include "../../../../lib/decompress_inflate.c"
 59 #endif
 60
 61 #ifdef CONFIG_KERNEL_BZIP2
 62 #include "../../../../lib/decompress_bunzip2.c"
 63 #endif
 64
 65 #ifdef CONFIG_KERNEL_LZMA
 66 #include "../../../../lib/decompress_unlzma.c"
 67 #endif
 68
 69 #ifdef CONFIG_KERNEL_XZ
 70 #include "../../../../lib/decompress_unxz.c"
 71 #endif
 72
 73 #ifdef CONFIG_KERNEL_LZO
 74 #include "../../../../lib/decompress_unlzo.c"
 75 #endif
     </snip>

一旦内核在内存中解压缩,提取的内核的入口点将从extract_kernel函数中获得,CPU 将跳转到一个内核内部。

内核内部

内核做了很多事情,但是我将列出你作为一个学习引导的人最感兴趣的事情。

  • 如果架构是 64 位的,内核会将内核堆栈大小设置为 16 KB。这意味着每一个新的进程都将拥有自己的内核堆栈,大小为 16 KB。

  • page_size将被设置为 4 KB,这是英特尔 64 位架构的默认页面大小。

  • 内核将准备中断和异常处理机制,也称为中断描述符表 (IDT)。

  • 内核会设置页面错误处理机制。

  • 内核将收集 initramfs 文件的详细信息,如文件名、大小、地址、重定位地址、新根设备的主要和次要编号等。,从/arch/x86/kernel/setup.c开始。

  • 然后它从源代码文件init/initramfs.c中提取 initramfs。

  • 最后,它通过使用init/main.cstart_kernel函数启动 systemd。

您会注意到这是我们第一次来到arch目录之外。这意味着我们可以认为这段代码是独立于架构的。内核一旦启动,它会做无数的事情,在本书中几乎不可能涵盖所有的内容。就引导而言,内核的座右铭是从 initramfs .启动 systemd 由于 initramfs 已经被引导装载程序加载到内存中,提取 initramfs 内核需要 initramfs 文件细节,内核将从/arch/x86/kernel/setup.c获得这些细节。

      Initramfs file name,
      Initramfs file size,
      Initramfs files address,
      Initramfs files relocation address,
      Major and minor numbers on which initramfs will be mounted.

一旦内核收到 initramfs 文件的细节,它将从init/initramfs.c文件中提取 initramfs 档案。我们将在第五章中讨论内核如何提取内存中的 initramfs。要将 initramfs 挂载为根设备,需要虚拟文件系统,如procsysdev等。,所以内核相应地准备它们。

    err = register_filesystem(&proc_fs_type);
        if (err)
        return;

内核稍后将在init/do_mounts.c.do_mount_root函数的帮助下将提取的 initramfs 挂载为根。一旦 initramfs 被挂载到内存中,内核将从其中启动 systemd。systemd 将通过一个init/main.c文件的相同的start_kernel函数启动。

     asmlinkage void __init start_kernel(void)

基本上,一旦根文件系统准备好了,它将进入根文件系统并创建两个线程:PID 1 是一个 systemd 进程,PID 2 是一个 kthread。为了更好地理解,请参考图 4-4 所示的流程图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-4

流程图,再次更新

图 4-5 显示了我们到目前为止讨论过的完整启动序列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-5

框图中的启动顺序

在我们继续讨论内核如何提取 initramfs 并从中运行 systemd 之前,我们需要了解 initramfs 的基础知识,比如我们为什么需要它,它的结构是什么,等等。一旦我们理解了 initramfs 的重要性和基础知识,我们将继续我们的引导顺序,讨论 systemd 在引导顺序中的作用。

五、initramfs

在本章中,我们将讨论为什么我们真的需要 initramfs,以及为什么它在引导过程中很重要。我们知道 initramfs 是由 bootloader 加载到内存中的,但是我们还没有讨论 initramfs 是如何提取的。本章将解决这个问题。我们还将看到提取、重建和定制 initramfs 的步骤。稍后,我们将看到 initramfs 的结构以及 initramfs 中系统的引导顺序。

为什么是 initramfs?

引导过程的目的是向用户展示驻留在根文件系统中的他们自己的文件。换句话说,找到、安装并向用户展示用户的根文件系统是内核的职责。为了实现这个目标,内核必须运行 systemd 二进制文件,它也驻留在用户的根文件系统中。现在这已经成为一个鸡和蛋的问题。要运行 systemd 进程,首先我们必须挂载根文件系统,要挂载根文件系统,我们必须从根文件系统运行 systemd。此外,除了实际的根文件系统之外,用户还可能拥有其他文件系统上的文件,如 NFS、CIFS 等。其他文件系统的列表也在根文件系统中(/etc/fstab)。

因此,为了解决这个先有鸡还是先有蛋的问题,开发人员提出了一个名为 initramfs(意思是“初始 RAM 文件系统”)的解决方案。initramfs 是一个临时的根文件系统(在主内存中),它将用于挂载实际的根文件系统(从硬盘或网络)。因此,initramfs 的全部目的是从 HDD/网络挂载用户的根文件系统。理想情况下,内核有足够的能力在没有 initramfs 的情况下从磁盘挂载根文件系统,但是现在用户的根文件系统可能在任何地方。它可能在 RAID、LVM 或多路径设备上。它可以是 XFS、BTRFS、ext4、ext3、NFS 等多种文件系统类型。它甚至可以在像 LUKS 这样的加密文件系统上。因此,对于一个内核来说,在自己的vmlinux二进制文件中包含所有这些场景几乎是不可能的。让我在这一部分提供一些真实的场景。

假设根文件系统在 NFS 上,并且没有 initramfs 的概念。这意味着内核必须自己从 NFS 挂载用户的根文件系统。在这种情况下,内核必须完成以下任务:

  1. 调出主网络接口。

  2. 调用 DHCP 客户端并从 DHCP 服务器获取 IP 地址。

  3. 找到 NFS 共享和相关的 NFS 服务器。

  4. 挂载 NFS 共享(根文件系统)。

要实现这些步骤,内核需要有以下二进制:NetworkManagerdhclientmount等。

现在让我们假设根文件系统在一个软件 RAID 设备上。然后内核必须完成以下任务:

  1. 先用mdadm --examine --scan找到 RAID 磁盘。

  2. 一旦软件 RAID 跨越的底层磁盘被识别,它必须用mdadm --assemble --scan组装 RAID。

  3. 为了实现这一点,内核需要有mountmdadm二进制文件以及软件 RAID 设备的一些配置文件。

现在让我们假设根文件系统在一个逻辑卷上。然后内核必须自己完成以下任务:

  1. pvs找到物理卷。

  2. vgscan找到卷组,然后用vgchange.激活它

  3. lvscan扫描 LVS。

  4. 最后,一旦填充了root lv,就将其作为根文件系统挂载。

  5. 为了实现这一点,内核需要有类似于pvscanpvslvscanvgscanlvsvgchange的二进制文件。

假设根文件系统位于一个加密的块设备上。然后内核必须完成以下任务:

  1. 从用户处收集密码和/或插入硬件令牌(如智能卡或 USB 安全加密狗)。

  2. 使用设备映射器创建解密目标。

为了实现所有这些,内核需要与 LUKS 相关的二进制文件。

对于内核来说,不可能包含所有这些根文件系统的可能性;因此,开发人员提出了 initramfs 概念,其唯一目的是挂载根文件系统。

内核仍然可以执行我们已经讨论过的所有步骤。例如,如果您从 LFS ( www.linuxfromscratch.org/ )构建一个简单的命令行 Linux 系统,您不需要 initramfs 来挂载根文件系统,因为内核本身就有足够的能力来挂载根文件系统。但是当你试图通过 BLFS 给它添加一个 GUI 时,你需要 initramfs。

因此,结论是内核可以自己挂载根文件系统,但是为此,内核必须保留所有讨论过的二进制文件、支持库、配置文件等。,在vmlinuz文件中。这会产生很多问题。

  • 它会破坏内核二进制的主要动机。

  • 内核二进制文件将会非常大。较大的二进制文件将很难维护。

  • 庞大的二进制文件很难在服务器上管理、升级、共享和处理(就 RPM 包而言)。

  • 方法不会按照接吻规则(保持简单,笨蛋)。

基础设施

为了理解 initramfs 结构,我们需要首先理解三种不同的文件系统。

拉弗

为了便于理解,我们将比较 ramfs 和内核的缓存机制。Linux 有一个独特的特性叫做页面缓存。每当您执行任何 I/O 事务时,它都会在页面中缓存这些事务。在内存中缓存页面总是好的。这将节省我们未来的 I/O 事务。每当系统遇到内存不足的情况时,内核就会从内存中丢弃这些缓存的页面。ramfs 就像我们的缓存。但是 ramfs 的问题是它没有后备存储;因此,它不能换出页面(swap 也是一个存储设备)。因此,很明显,内核将无法释放这些内存,因为没有地方保存这些页面。因此,ramfs 将继续增长,你不能真正限制它的大小。我们能做的是只允许根用户写入 ramfs 来缓解这种情况。

文件系统

tmpfs 就像 ramfs 一样,只是增加了一些东西。我们可以限制 tmpfs 的大小,这在 ramfs 中是做不到的。此外,tmpfs 页面可以使用交换空间。

根目录

rootfs 是一个 tmpfs,它是 ramfs 的一个实例。rootfs 的优点是您不能卸载它。这和你不能杀死systemd进程是一个道理。

initramfs 使用 ramfs 作为文件系统,一旦挂载了用户的根文件系统,initramfs 在内存中占用的空间就会被释放。

# dmesg | grep Free

[    0.813330] Freeing SMP alternatives memory: 36K
[    3.675187] Freeing initrd memory: 32548K    <<<=======<<<<<<===== NOTE
[    5.762702] Freeing unused decrypted memory: 2040K
[    5.767001] Freeing unused kernel image memory: 2272K
[    5.776841] Freeing unused kernel image memory: 2016K
[    5.783116] Freeing unused kernel image memory: 1580K

以前,Linux 使用initrd(初始 RAM 磁盘)而不是 initramfs,但是initrd现在已经过时了,因此我们将只列出几个要点来与 initramfs 进行比较。

init rd

  • 被格式化/视为块设备意味着initrd无法扩展。这意味着一旦您将initrd放入内存并将其视为块设备,您就不能增加或减少它的大小。

  • 我们将在缓存中浪费一些内存,因为initrd被认为是块设备,因为 Linux 内核被设计为将块设备内容保存在缓存中,以减少 I/O 事务。简而言之,内核不必要地缓存已经在内存中的initrd内容。

Initramfs 的缩写形式

  • initrd中,总会有文件系统驱动程序及其二进制文件的开销,比如mke2fsmke2fs命令用于创建 ext2/3/4 文件系统。这意味着一些 RAM 区域将首先被格式化,由mke2fs使用 ext2/3/4 文件系统,然后initrd将在其上提取,而 initramfs 就像 tmpfs 一样,您可以随时动态地增加或减少。

  • 数据块设备和缓存之间没有数据重复。

  • 要使用 initramfs 作为根文件系统,内核不需要任何驱动程序或类似于mke2fs的二进制文件,因为 initramfs 归档文件将按原样提取到主内存中。

# ls -lh /boot/initramfs-5.3.7-301.fc31.x86_64.img
-rw-------. 1 root root 32M Dec  9 10:19 /boot/initramfs-5.3.7-301.fc31.x86_64.img

我们可以使用lsinitrd工具查看 initramfs 的内容,或者我们可以在skipcpio工具的帮助下提取 initramfs。

#lsinitrd
<snip>
Image: /boot/initramfs-5.3.7-301.fc31.x86_64.img: 32M
========================================================================
Early CPIO image
========================================================================
drwxr-xr-x   3 root     root            0 Jul 25  2019 .
-rw-r--r--   1 root     root            2 Jul 25  2019 early_cpio
drwxr-xr-x   3 root     root            0 Jul 25  2019 kernel
drwxr-xr-x   3 root     root            0 Jul 25  2019 kernel/x86
drwxr-xr-x   2 root     root            0 Jul 25  2019 kernel/x86/microcode
-rw-r--r--   1 root     root       100352 Jul 25  2019 kernel/x86/microcode/GenuineIntel.bin
========================================================================
Version: dracut-049-27.git20181204.fc31.1

Arguments: -f

dracut modules:
bash
systemd
systemd-initrd
nss-softokn
i18n
network-manager
network
ifcfg
drm
plymouth
dm
kernel-modules
kernel-modules-extra
kernel-network-modules
lvm
qemu
qemu-net
resume
rootfs-block
terminfo
udev-rules
dracut-systemd
usrmount
base
fs-lib
shutdown
========================================================================
drwxr-xr-x  12 root     root            0 Jul 25  2019 .
crw-r--r--   1 root     root       5,   1 Jul 25  2019 dev/console
crw-r--r--   1 root     root       1,  11 Jul 25  2019 dev/kmsg
crw-r--r--   1 root     root       1,   3 Jul 25  2019 dev/null
crw-r--r--   1 root     root       1,   8 Jul 25  2019 dev/random
crw-r--r--   1 root     root       1,   9 Jul 25  2019 dev/urandom
lrwxrwxrwx   1 root     root            7 Jul 25  2019 bin -> usr/bin
drwxr-xr-x   2 root     root            0 Jul 25  2019 dev
drwxr-xr-x  11 root     root            0 Jul 25  2019 etc
drwxr-xr-x   2 root     root            0 Jul 25  2019 etc/cmdline.d
drwxr-xr-x   2 root     root            0 Jul 25  2019 etc/conf.d
-rw-r--r--   1 root     root          124 Jul 25  2019 etc/conf.d/systemd.conf
-rw-r--r--   1 root     root            0 Jul 25  2019 etc/fstab.empty
-rw-r--r--   1 root     root          240 Jul 25  2019 etc/group
-rw-r--r--   1 root     root           22 Jul 25  2019 etc/hostname
lrwxrwxrwx   1 root     root           25 Jul 25  2019 etc/initrd-release -> ../usr/lib/initrd-release
-rw-r--r--   1 root     root         8581 Jul 25  2019 etc/ld.so.cache
-rw-r--r--   1 root     root           28 Jul 25  2019 etc/ld.so.conf
drwxr-xr-x   2 root     root            0 Jul 25  2019 etc/ld.so.conf.d
-rw-r--r--   1 root     root           17 Jul 25  2019 etc/ld.so.conf.d/libiscsi-x86_64.conf
-rw-rw-r--   1 root     root           19 Jul 25  2019 etc/locale.conf
drwxr-xr-x   2 root     root            0 Jul 25  2019 etc/lvm
-rw-r--r--   1 root     root       102256 Jul 25  2019 etc/lvm/lvm.conf
-rw-r--r--   1 root     root         2301 Jul 25  2019 etc/lvm/lvmlocal.conf
-r--r--r--   1 root     root           33 Jul 25  2019 etc/machine-id
drwxr-xr-x   2 root     root            0 Jul 25  2019 etc/modprobe.d
</snip>

要提取 initramfs ,的内容,请使用来自/usr/lib/dracut/skipcpio/.skipcpio二进制文件,skipcpio由 dracut 工具提供。我们将在第六章中讲述 dracut。

#/usr/lib/dracut/skipcpio initramfs-5.3.7-301.fc31.x86_64.img | gunzip -c | cpio -idv

如果您查看提取的 initramfs 内容,您会惊讶地发现它看起来就像用户的根文件系统。请注意,我们已经将 initramfs 提取到了/root/boot目录中。

# ls -lh /root/boot/
total 44K
lrwxrwxrwx.  1 root root    7 Mar 26 18:03 bin -> usr/bin
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 dev
drwxr-xr-x. 11 root root 4.0K Mar 26 18:03 etc
lrwxrwxrwx.  1 root root   23 Mar 26 18:03 init -> usr/lib/systemd/systemd
lrwxrwxrwx.  1 root root    7 Mar 26 18:03 lib -> usr/lib
lrwxrwxrwx.  1 root root    9 Mar 26 18:03 lib64 -> usr/lib64
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 proc
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 root
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 run
lrwxrwxrwx.  1 root root    8 Mar 26 18:03 sbin -> usr/sbin
-rwxr-xr-x.  1 root root 3.1K Mar 26 18:03 shutdown
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 sys
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 sysroot
drwxr-xr-x.  2 root root 4.0K Mar 26 18:03 tmp
drwxr-xr-x.  8 root root 4.0K Mar 26 18:03 usr
drwxr-xr-x.  3 root root 4.0K Mar 26 18:03 var

您会发现binsbinusretcvarliblib64——类似于我们过去在用户的根文件系统中看到的目录。与此同时,您会注意到虚拟文件系统目录,如devrunprocsys等。因此,initramfs 就像用户的根文件系统。为了更好地理解 initramfs 的实现,让我们研究一下每个目录。

initramfs 实现

现在我们来看看 initramfs 的内容,以及 initramfs 是如何组织的。通过本节,您将认识到 initramfs 只不过是一个小的根文件系统。

容器

正常双星

我们可以在已经完成引导过程的系统上使用以下所有二进制文件。因为所有这些二进制文件都可以在 initramfs 中获得,所以当系统还在引导时,我们将能够在引导时使用所有这些命令。

cat, chown, cp, dmesg, echo, grep, gzip, less, ln, mkdir, mv, ps, rm, sed, sleep, umount, uname, vi, loadkeys, kbd_mode, flock, tr, true, stty, mount, sort etc.

[root@fedorab boot]# ls -la bin/

total 7208
drwxr-xr-x. 2 root root    4096 Jan 10 12:01 .
drwxr-xr-x. 8 root root    4096 Dec 19 14:30 ..
-rwxr-xr-x. 1 root root 1237376 Dec 19 14:30 bash
-rwxr-xr-x. 1 root root   50160 Dec 19 14:30 cat
-rwxr-xr-x. 1 root root   82688 Dec 19 14:30 chown
-rwxr-xr-x. 1 root root  177144 Dec 19 14:30 cp
-rwxr-xr-x. 1 root root   89344 Dec 19 14:30 dmesg
-rwxr-xr-x. 1 root root    2666 Dec 19 14:30 dracut-cmdline
-rwxr-xr-x. 1 root root     422 Dec 19 14:30 dracut-cmdline-ask
-rwxr-xr-x. 1 root root    1386 Dec 19 14:30 dracut-emergency
-rwxr-xr-x. 1 root root    2151 Dec 19 14:30 dracut-initqueue
-rwxr-xr-x. 1 root root    1056 Jan 10 12:01 dracut-mount
-rwxr-xr-x. 1 root root     517 Dec 19 14:30 dracut-pre-mount
-rwxr-xr-x. 1 root root     928 Dec 19 14:30 dracut-pre-pivot
-rwxr-xr-x. 1 root root     482 Dec 19 14:30 dracut-pre-trigger
-rwxr-xr-x. 1 root root    1417 Dec 19 14:30 dracut-pre-udev
-rwxr-xr-x. 1 root root   45112 Dec 19 14:30 echo
-rwxr-xr-x. 1 root root   76768 Dec 19 14:30 findmnt
-rwxr-xr-x. 1 root root   38472 Dec 19 14:30 flock
-rwxr-xr-x. 1 root root  173656 Dec 19 14:30 grep
-rwxr-xr-x. 1 root root  107768 Dec 19 14:30 gzip
-rwxr-xr-x. 1 root root   78112 Dec 19 14:30 journalctl
-rwxr-xr-x. 1 root root   17248 Dec 19 14:30 kbd_mode
-rwxr-xr-x. 1 root root  387504 Dec 19 14:30 kmod
-rwxr-xr-x. 1 root root  192512 Dec 19 14:30 less
-rwxr-xr-x. 1 root root   85992 Dec 19 14:30 ln
-rwxr-xr-x. 1 root root  222616 Dec 19 14:30 loadkeys
lrwxrwxrwx. 1 root root       4 Dec 19 14:30 loginctl -> true
-rwxr-xr-x. 1 root root  158056 Dec 19 14:30 ls
-rwxr-xr-x. 1 root root   99080 Dec 19 14:30 mkdir
-rwxr-xr-x. 1 root root   80264 Dec 19 14:30 mkfifo
-rwxr-xr-x. 1 root root   84560 Dec 19 14:30 mknod
-rwsr-xr-x. 1 root root   58984 Dec 19 14:30 mount
-rwxr-xr-x. 1 root root  169400 Dec 19 14:30 mv
-rwxr-xr-x. 1 root root   50416 Dec 19 14:30 plymouth
-rwxr-xr-x. 1 root root  143408 Dec 19 14:30 ps
-rwxr-xr-x. 1 root root   60376 Dec 19 14:30 readlink
-rwxr-xr-x. 1 root root   83856 Dec 19 14:30 rm
-rwxr-xr-x. 1 root root  127192 Dec 19 14:30 sed
-rwxr-xr-x. 1 root root   52272 Dec 19 14:30 setfont
-rwxr-xr-x. 1 root root   16568 Dec 19 14:30 setsid
lrwxrwxrwx. 1 root root       4 Dec 19 14:30 sh -> bash
-rwxr-xr-x. 1 root root   46608 Dec 19 14:30 sleep
-rwxr-xr-x. 1 root root  140672 Dec 19 14:30 sort
-rwxr-xr-x. 1 root root   96312 Dec 19 14:30 stat
-rwxr-xr-x. 1 root root   92576 Dec 19 14:30 stty
-rwxr-xr-x. 1 root root  240384 Dec 19 14:30 systemctl
-rwxr-xr-x. 1 root root   20792 Dec 19 14:30 systemd-cgls
-rwxr-xr-x. 1 root root   19704 Dec 19 14:30 systemd-escape
-rwxr-xr-x. 1 root root   62008 Dec 19 14:30 systemd-run
-rwxr-xr-x. 1 root root   95168 Dec 19 14:30 systemd-tmpfiles

-rwxr-xr-x. 1 root root  173752 Dec 19 14:30 teamd
-rwxr-xr-x. 1 root root   58400 Dec 19 14:30 tr
-rwxr-xr-x. 1 root root   45112 Dec 19 14:30 true
-rwxr-xr-x. 1 root root  442552 Dec 19 14:30 udevadm
-rwsr-xr-x. 1 root root   41912 Dec 19 14:30 umount
-rwxr-xr-x. 1 root root   45120 Dec 19 14:30 uname
-rwxr-xr-x. 1 root root 1353704 Dec 19 14:30 vi

特殊二进制文件

|

特殊二元

|

目的

|
| — | — |
| bash | initramfs 会在引导时给我们提供一个 shell。 |
| mknod | 我们将能够创造设备。 |
| udevadm | 我们将能够管理设备。dracut 使用的是udev,一个事件驱动的工具,它会启动某些程序,比如lvmmdadm等。,当符合某些udev规则时。例如,只要符合某些udev规则,存储卷和网卡设备文件就会出现在/dev下。 |
| kmod | 一个管理内核模块的工具。 |

网络二进制文件

在 bin 下只有一个与网络相关的二进制文件可用,那就是 teamd (initramfs 可以处理分组网络设备)。

钩住

我们将在第七章和第九章中讨论钩子。

dracut-cmdline               dracut-cmdline-ask
dracut-emergency             dracut -initqueue
dracut-mount                 dracut -pre-pivot
dracut - pre-trigger         dracut -pre-udev

二进制系统

|

二进制的

|

目的

|
| — | — |
| systemd | 这是替换init的每个进程的父进程。这是第一个进程,它在我们进入 initramfs 时运行。 |
| systemctl | Systemd 的服务经理。 |
| systemd-cgls | 这将列出现有的控制组(cgroups)。 |
| systemd-escape | 这将把字符串转换成 systemd 单位格式,也称为转义。 |
| systemd-run | 这可以将程序作为服务运行,但是在临时范围内。 |
| systemd-tmpfiles | 这将创建、删除和清理易变的和临时的文件和目录。 |
| journalctl | 一个处理 systemd 日志的工具。 |

命令

文件系统和存储相关的二进制文件

|

二进制的

|

目的

|
| — | — |
| blkid | 要读取设备属性 |
| chroot | 要更改根文件系统设备 |
| e2fsck | 检查 ext2/3/4 文件系统 |
| fsck, fsck.ext4 | 检查并修复文件系统 |
| swapoff | 如果您想停止交换设备 |
| dmsetup | 用于 LVM 管理的设备映射工具 |
| dmeventd | 设备映射器事件守护程序 |
| lvm | 一个 LVM 管理工具,将提供lvscanvgscanvgchangepvs等。,命令 |
| lvm_scan | 找到 LVM 设备的脚本 |

网络二进制文件

|

双星

|

目的

|
| — | — |
| dhclient | 要从 DHCP 服务器获取 IP 地址 |
| losetup | 设置loop装置 |
| Netroot | 网络上对根的支持 |
| NetworkManager | 管理网络设备的工具 |

特殊二进制文件

|

双星

|

目的

|
| — | — |
| depmod | 生成modules.dep(kmod的符号链接) |
| lsmod | 列出加载的模块(kmod的符号链接) |
| modinfo | 打印模块信息(符号链接kmod) |
| modprobe | 加载或插入模块(符号链接kmod) |
| rmmod | 移除加载的模块(kmod的符号链接) |
| init / systemd | 第一个过程 |
| kexec | Kdump 使用的 kexec 内核 |
| udevadm | Udev 经理 |

基本二进制文件

最后,下面是基本的二进制文件:

Halt, poweroff, reboot

 [root@fedorab boot]# ls -lah sbin/
total 13M
drwxr-xr-x. 2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 8 root root 4.0K Dec 19 14:30 ..
-rwxr-xr-x. 1 root root 126K Dec 19 14:30 blkid
-rwxr-xr-x. 1 root root  50K Dec 19 14:30 chroot
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 depmod -> ../bin/kmod
-rwxr-xr-x. 1 root root 2.9M Dec 19 14:30 dhclient
-r-xr-xr-x. 1 root root  45K Dec 19 14:30 dmeventd
-r-xr-xr-x. 1 root root 159K Dec 19 14:30 dmsetup
-rwxr-xr-x. 2 root root 340K Dec 19 14:30 e2fsck
-rwxr-xr-x. 1 root root  58K Dec 19 14:30 fsck
-rwxr-xr-x. 2 root root 340K Dec 19 14:30 fsck.ext4
lrwxrwxrwx. 1 root root   16 Dec 19 14:30 halt -> ../bin/systemctl
lrwxrwxrwx. 1 root root   22 Dec 19 14:30 init -> ../lib/systemd/systemd
-rwxr-xr-x. 1 root root 1.2K Dec 19 14:30 initqueue
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 insmod -> ../bin/kmod
-rwxr-xr-x. 1 root root  197 Dec 19 14:30 insmodpost.sh
-rwxr-xr-x. 1 root root 203K Dec 19 14:30 kexec
-rwxr-xr-x. 1 root root  496 Dec 19 14:30 loginit
-rwxr-xr-x. 1 root root 117K Dec 19 14:30 losetup
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 lsmod -> ../bin/kmod

-r-xr-xr-x. 1 root root 2.4M Dec 19 14:30 lvm
-rwxr-xr-x. 1 root root 3.5K Dec 19 14:30 lvm_scan
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 modinfo -> ../bin/kmod
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 modprobe -> ../bin/kmod
-rwxr-xr-x. 1 root root 2.7K Dec 19 14:30 netroot
-rwxr-xr-x. 1 root root 5.3M Dec 19 14:30 NetworkManager
-rwxr-xr-x. 1 root root  16K Dec 19 14:30 nologin
-rwxr-xr-x. 1 root root 150K Dec 19 14:30 plymouthd
lrwxrwxrwx. 1 root root   16 Dec 19 14:30 poweroff -> ../bin/systemctl
-rwxr-xr-x. 1 root root 1.4K Dec 19 14:30 rdsosreport
lrwxrwxrwx. 1 root root   16 Dec 19 14:30 reboot -> ../bin/systemctl
lrwxrwxrwx. 1 root root   11 Dec 19 14:30 rmmod -> ../bin/kmod
-rwxr-xr-x. 1 root root  25K Dec 19 14:30 swapoff
-rwxr-xr-x. 1 root root 6.0K Dec 19 14:30 tracekomem
lrwxrwxrwx. 1 root root   14 Dec 19 14:30 udevadm -> ../bin/udevadm

看到没有实际用户的根文件系统,我们也能使用和管理 Shell、网络、模块、设备等等,这难道不令人惊奇吗??换句话说,您并不真正需要用户的根文件系统,除非用户想要访问他们的私有文件。开玩笑的。

现在一个问题浮现在脑海中:我们可以在哪里以及如何使用所有这些命令?initramfs 将自动使用这些二进制文件或命令。或者,正确地说,这些二进制文件或命令将被 initramfs 的 systemd 用来挂载用户的实际根文件系统,但如果 systemd 无法这样做,它将为我们提供一个 shell,我们将能够使用这些命令并进一步排除故障。我们将在第七章第七章、第八章和第九章对此进行讨论。

等等

来自binsbin目录的二进制文件将有自己的配置文件,它们将存储在 initramfs 的etc目录中。

 [root@fedorab boot]# tree etc/
etc/
├── cmdline.d
├── conf.d
│   └── systemd.conf
├── fstab.empty
├── group
├── hostname
├── initrd-release -> ../usr/lib/initrd-release
├── ld.so.cache
├── ld.so.conf
├── ld.so.conf.d
│   └── libiscsi-x86_64.conf
├── locale.conf
├── lvm
│   ├── lvm.conf
│   └── lvmlocal.conf
├── machine-id
├── modprobe.d
│   ├── firewalld-sysctls.conf
│   ├── kvm.conf
│   ├── lockd.conf
│   ├── mlx4.conf
│   ├── nvdimm-security.conf
│   └── truescale.conf
├── mtab -> /proc/self/mounts
├── os-release -> initrd-release
├── passwd
├── plymouth
│   └── plymouthd.conf
├── sysctl.conf
├── sysctl.d
│   └── 99-sysctl.conf -> ../sysctl.conf
├── systemd
│   ├── journald.conf
│   └── system.conf
├── system-release -> ../usr/lib/fedora-release
├── udev
│   ├── rules.d
│   │   ├── 11-dm.rules
│   │   ├── 59-persistent-storage-dm.rules
│   │   ├── 59-persistent-storage.rules
│   │   ├── 61-persistent-storage.rules

│   │   └── 64-lvm.rules
│   └── udev.conf
├── vconsole.conf
└── virc

10 directories, 35 files

虚拟文件系统

虚拟文件系统是这样一种文件系统,其文件并不真正存在于磁盘上;相反,整个文件系统在内存中都是可用的。这是各有利弊的;例如,您获得了非常高的吞吐量,但是文件系统不能永久存储数据。initramfs 中有三个可用的虚拟文件系统,分别是devprocsys。在这里,我对文件系统做了一个简单的介绍,但是我们将在接下来的章节中详细讨论它们:

[root@fedorab boot]# ls -lah dev
total 8.0K
drwxr-xr-x.  2 root root  4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root  4.0K Dec 19 14:33 ..
crw-r--r--.  1 root root 5,  1 Dec 19 14:30 console
crw-r--r--.  1 root root 1, 11 Dec 19 14:30 kmsg
crw-r--r--.  1 root root 1,  3 Dec 19 14:30 null
crw-r--r--.  1 root root 1,  8 Dec 19 14:30 random
crw-r--r--.  1 root root 1,  9 Dec 19 14:30 urandom

[root@fedorab boot]# ls -lah proc/
total 8.0K
drwxr-xr-x.  2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root 4.0K Dec 19 14:33 ..

[root@fedorab boot]# ls -lah sys/
total 8.0K
drwxr-xr-x.  2 root root 4.0K Dec 19 14:30 .
drwxr-xr-x. 12 root root 4.0K Dec 19 14:33 ..

偏差

到目前为止,只有五个默认的设备文件,但是当系统启动时,udev将完全填充这个目录。consolekmsgnullrandomurandom设备文件将由内核自己创建,或者换句话说,这些设备文件是使用mknod命令手工创建的,但是其余的设备文件将由udev填充。

过程和系统

一旦内核控制了引导过程,内核就会创建并填充这些目录。proc文件系统将保存所有进程的相关信息,如/proc/1/status,而sys将保存设备及其驱动程序的相关信息,如/sys/fs/ext4/sda5/errors_count.

usr, var

众所周知,现在usr是根文件系统中一个独立的文件系统层次结构。我们的/bin/sbin/lib/lib64不过是到usr/binusr/sbinusr/libusr/lib64的符号链接。

# ls -l bin
lrwxrwxrwx. 1 root root 7 Dec 21 12:19 bin -> usr/bin

# ls -l sbin
lrwxrwxrwx. 1 root root 8 Dec 21 12:19 sbin -> usr/sbin

# ls -la usr
total 40
drwxr-xr-x.  8 root root  4096 Dec 21 12:19 .
drwxr-xr-x. 12 root root  4096 Dec 21 12:19 ..
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 bin
drwxr-xr-x. 12 root root  4096 Dec 21 12:19 lib
drwxr-xr-x.  4 root root 12288 Dec 21 12:19 lib64
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 libexec
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 sbin
drwxr-xr-x.  5 root root  4096 Dec 21 12:19 share

# ls -la var

total 12
drwxr-xr-x.  3 root root 4096 Dec 21 12:19 .
drwxr-xr-x. 12 root root 4096 Dec 21 12:19 ..
lrwxrwxrwx.  1 root root   11 Dec 21 12:19 lock -> ../run/lock
lrwxrwxrwx.  1 root root    6 Dec 21 12:19 run -> ../run
drwxr-xr-x.  2 root root 4096 Dec 21 12:19 tmp

lib,lib64

库差不多有 200 个,几乎都是由glibc提供的,比如libc.so.6

liblib64目录是usr/libusr/lib64的符号链接。

# ls -l lib
lrwxrwxrwx. 1 root root 7 Dec 21 12:19 lib -> usr/lib

# ls -l lib64
lrwxrwxrwx. 1 root root 9 Dec 21 12:19 lib64 -> usr/lib64

# ls -la lib/
total 128
drwxr-xr-x. 12 root root  4096 Dec 21 12:19 .
drwxr-xr-x.  8 root root  4096 Dec 21 12:19 ..
drwxr-xr-x.  3 root root  4096 Dec 21 12:19 dracut
-rwxr-xr-x.  1 root root 34169 Dec 21 12:19 dracut-lib.sh
-rw-r--r--.  1 root root    31 Dec 21 12:19 fedora-release
drwxr-xr-x.  6 root root  4096 Dec 21 12:19 firmware
-rwxr-xr-x.  1 root root  6400 Dec 21 12:19 fs-lib.sh
-rw-r--r--.  1 root root   238 Dec 21 12:19 initrd-release
drwxr-xr-x.  6 root root  4096 Dec 21 12:19 kbd
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 modprobe.d
drwxr-xr-x.  3 root root  4096 Dec 21 12:19 modules
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 modules-load.d
-rwxr-xr-x.  1 root root 25295 Dec 21 12:19 net-lib.sh
lrwxrwxrwx.  1 root root    14 Dec 21 12:19 os-release -> initrd-release
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 sysctl.d
drwxr-xr-x.  5 root root  4096 Dec 21 12:19 systemd
drwxr-xr-x.  2 root root  4096 Dec 21 12:19 tmpfiles.d
drwxr-xr-x.  3 root root  4096 Dec 21 12:19 udev

# ls -la lib64/libc.so.6 

lrwxrwxrwx. 1 root root 12 Dec 21 12:19 lib64/libc.so.6 -> libc-2.30.so

# dnf whatprovides lib64/libc.so.6
glibc-2.30-5.fc31.x86_64 : The GNU libc libraries
Repo        : @System
Matched from:
Filename    : /lib64/libc.so.6

initramfs 引导

initramfs 中引导序列的基本流程很容易理解:

  1. 由于 initramfs 是一个根文件系统(temporary),它将创建运行进程所必需的环境。initramfs 将被挂载为根文件系统(临时的/),systemd 之类的程序将从这里启动。

  2. 之后,来自硬盘或网络的新用户的根文件系统将被挂载到 initramfs 中的一个临时目录中。

  3. 一旦用户的根文件系统被挂载到 initramfs 中,内核将启动init二进制文件,它是操作系统的第一个进程systemd的符号链接。

    # ls init -l
    lrwxrwxrwx. 1 root root 23 Dec 21 12:19 init -> usr/lib/systemd/systemd
    
    
  4. 一旦一切就绪,临时根文件系统(initramfs 根文件系统)将被卸载,systemd 将负责引导序列的其余部分。第七章将介绍系统引导。

我们可以交叉验证内核是否真的一提取 initramfs 就启动init/systemd进程。我们可以为此修改init脚本,但是问题是 systemd 是二进制的,而init曾经是一个脚本。我们可以很容易地编辑init,因为它是一个脚本文件,但是我们不能编辑 systemd 二进制文件。然而,为了更好地理解和验证我们的引导序列,看看内核一提取 initramfs,systemd 是否就被调用,我们将使用一个基于init的系统。这将是一个很好的例子,因为 systemd 将取代init系统。另外,init仍然是 systemd 的符号链接。我们将使用 Centos 6 系统,这是一个基于init的 Linux 发行版。

首先提取 initramfs。

# zcat  initramfs-2.6.32-573.el6.x86_64.img  |  cpio –idv

[root@localhost initramfs]# ls -lah
total 120K
drwxr-xr-x. 26 root root 4.0K Mar 27 12:56 .
drwxr-xr-x.  3 root root 4.0K Mar 27 12:56 ..
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 bin
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 cmdline
drwxr-xr-x.  3 root root 4.0K Mar 27 12:56 dev
-rw-r--r--.  1 root root   19 Mar 27 12:56 dracut-004-388.el6
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 emergency
drwxr-xr-x.  8 root root 4.0K Mar 27 12:56 etc
-rwxr-xr-x.  1 root root 8.8K Mar 27 12:56 init
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 initqueue
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 initqueue-finished
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 initqueue-settled
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 initqueue-timeout
drwxr-xr-x.  7 root root 4.0K Mar 27 12:56 lib
drwxr-xr-x.  3 root root 4.0K Mar 27 12:56 lib64
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 mount
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 netroot
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 pre-mount
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 pre-pivot
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 pre-trigger
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 pre-udev
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 proc
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 sbin
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 sys
drwxr-xr-x.  2 root root 4.0K Mar 27 12:56 sysroot
drwxrwxrwt.  2 root root 4.0K Mar 27 12:56 tmp
drwxr-xr-x.  8 root root 4.0K Mar 27 12:56 usr
drwxr-xr-x.  4 root root 4.0K Mar 27 12:56 var

打开一个init文件,并在其中添加以下横幅:

#vim init
   "We are inside the init process. Init is replaced by Systemd"
<snip>
#!/bin/sh
#
# Licensed under the GPLv2
#
# Copyright 2008-2009, Red Hat, Inc.
# Harald Hoyer <harald@redhat.com>
# Jeremy Katz <katzj@redhat.com>
echo "we are inside the init process. Init is replaced by Systemd"
wait_for_loginit()
{
    if getarg rdinitdebug; then
        set +x
        exec 0<>/dev/console 1<>/dev/console 2<>/dev/console
        # wait for loginit
        i=0
        while [ $i -lt 10 ]; do
.
.
.
</snip>

test.img名重新打包 initramfs。

[root@localhost initramfs]# find . | cpio -o -c | gzip -9 > /boot/test.img
163584 blocks

# ls -lh /boot/
total 66M
-rw-r--r--. 1 root root 105K Jul 23  2015 config-2.6.32-573.el6.x86_64
drwxr-xr-x. 3 root root 1.0K Aug  7  2015 efi
-rw-r--r--. 1 root root 163K Jul 20  2011 elf-memtest86+-4.10
drwxr-xr-x. 2 root root 1.0K Dec 21 16:12 grub
-rw-------. 1 root root  27M Dec 21 15:55 initramfs-2.6.32-573.el6.x86_64.img
-rw-------. 1 root root 5.3M Dec 21 16:03 initrd-2.6.32-573.el6.x86_64kdump.img
drwx------. 2 root root  12K Dec 21 15:54 lost+found
-rw-r--r--. 1 root root 162K Jul 20  2011 memtest86+-4.10
-rw-r--r--. 1 root root 202K Jul 23  2015 symvers-2.6.32-573.el6.x86_64.gz
-rw-r--r--. 1 root root 2.5M Jul 23  2015 System.map-2.6.32-573.el6.x86_64
-rw-r--r--. 1 root root  27M Mar 27 13:16 test.img
-rwxr-xr-x. 1 root root 4.1M Jul 23  2015 vmlinuz-2.6.32-573.el6.x86_64

使用新的test.img initramfs 启动,打开 initramfs 后,您会注意到我们的横幅正在打印。

<snip>
.
.
.
cpuidle: using governor ladder
cpuidle: using governor menu
EFI Variables Facility v0.08 2004-May-17
usbcore: registered new interface driver hiddev
usbcore: registered new interface driver usbhid
usbhid: v2.6:USB HID core driver
GRE over IPv4 demultiplexor driver
TCP cubic registered
Initializing XFRM netlink socket
NET: Registered protocol family 17
registered taskstats version 1
rtc_cmos 00:01: setting system clock to 2020-03-27 07:53:44 UTC (1585295624)
Initalizing network drop monitor service
Freeing unused kernel memory: 1296k freed
Write protecting the kernel read-only data: 10240k
Freeing unused kernel memory: 732k freed
Freeing unused kernel memory: 1576k freed
we are inside the init process. Init is replaced by Systemd
dracut: dracut-004-388.el6
dracut: rd_NO_LUKS: removing cryptoluks activation
device-mapper: uevent: version 1.0.3
device-mapper: ioctl: 4.29.0-ioctl (2014-10-28) initialised: dm-devel@redhat.com
udev: starting version 147
dracut: Starting plymouth daemon
.
.
</snip>

内核如何从内存中提取 initramfs?

让我们花一分钟时间,试着回忆一下到目前为止我们所学的内容。

  1. 引导加载程序首先运行。

  2. 引导装载程序复制内存中的内核和 initramfs。

  3. 内核会自行提取。

  4. 引导装载程序将 initramfs 的位置传递给内核。

  5. 内核在内存中提取 initramfs。

  6. 内核从提取的 initramfs 运行 systemd。

提取发生在内核的init/initramfs.c文件中。populate_rootfs函数负责提取。

populate_rootfs 函数:

<snip>
.
.
646 static int __init populate_rootfs(void)
647 {
648         /* Load the built in initramfs */
649         char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
650         if (err)
651                 panic("%s", err); /* Failed to decompress INTERNAL initramfs */
652
653         if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE))
654                 goto done;
655
656         if (IS_ENABLED(CONFIG_BLK_DEV_RAM))
657                 printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
658         else
659                 printk(KERN_INFO "Unpacking initramfs...\n");
660
661         err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
662         if (err) {
663                 clean_rootfs();
664                 populate_initrd_image(err);

665         }
666
667 done:
668         /*
669          * If the initrd region is overlapped with crashkernel reserved region,
670          * free only memory that is not part of crashkernel region.
671          */
672         if (!do_retain_initrd && initrd_start && !kexec_free_initrd())
673                 free_initrd_mem(initrd_start, initrd_end);
674         initrd_start = 0;
675         initrd_end = 0;
676
677         flush_delayed_fput();
678         return 0;
679 }
.
.
</snip>

unpack_to_rootfs 函数:

<snip>
.
.
443 static char * __init unpack_to_rootfs(char *buf, unsigned long len)
444 {
445         long written;
446         decompress_fn decompress;
447         const char *compress_name;
448         static __initdata char msg_buf[64];
449
450         header_buf = kmalloc(110, GFP_KERNEL);
451         symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
452         name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
453
454         if (!header_buf || !symlink_buf || !name_buf)
455                 panic("can't allocate buffers");
456
457         state = Start;
458         this_header = 0;
459         message = NULL;
460         while (!message && len) {
461                 loff_t saved_offset = this_header;
462                 if (*buf == '0' && !(this_header & 3)) {
463                         state = Start;
464                         written = write_buffer(buf, len);
465                         buf += written;
466                         len -= written;
467                         continue;

468                 }
469                 if (!*buf) {
470                         buf++;
471                         len--;
472                         this_header++;
473                         continue;
474                 }
475                 this_header = 0;
476                 decompress = decompress_method(buf, len, &compress_name);
477                 pr_debug("Detected %s compressed data\n", compress_name);
478                 if (decompress) {
479                         int res = decompress(buf, len, NULL, flush_buffer, NULL,
480                                    &my_inptr, error);
481                         if (res)
482                                 error("decompressor failed");
483                 } else if (compress_name) {
484                         if (!message) {
485                                 snprintf(msg_buf, sizeof msg_buf,
486                                          "compression method %s not configured",
487                                          compress_name);
488                                 message = msg_buf;
489                         }
490                 } else
491                         error("invalid magic at start of compressed archive");
492                 if (state != Reset)
493                         error("junk at the end of compressed archive");
494                 this_header = saved_offset + my_inptr;
495                 buf += my_inptr;
496                 len -= my_inptr;
497         }
498         dir_utime();
499         kfree(name_buf);
500         kfree(symlink_buf);
501         kfree(header_buf);
502         return message;
503 }
.
.
</snip>

populate_rootfs函数内部有一个unpack_to_rootfs函数。这是一个 worker 函数,它解包 initramfs,如果失败则返回 0,如果成功则返回 1。还要注意有趣的函数参数。

  • __initramfs_start:这是一个加载的 initramfs 的确切位置/地址(initramfs 将由 bootloader 加载,所以显然地址位置也是 bootloader 通过boot_protocol提供的)。

  • __initramfs_size:这是 initramfs 映像的大小。

内核如何以 Root 身份挂载 initramfs?

initramfs blob 只是一个(可选压缩的)cpio文件。内核通过在内存中创建一个 tmpfs/ramfs 文件系统作为根文件系统来提取它。所以,并没有一个固定的位置。内核只是在运行过程中为提取的文件分配内存。我们已经看到 GRUB 2/boot loader 将内核放在一个特定的位置,这个位置依赖于体系结构,但是 initramfs 映像提取并不发生在任何特定的位置。

现在,在我们进一步进行引导序列之前,我们需要理解 dracut 工具,它生成 initramfs。这个工具将让我们更好地理解 initramfs 和 systemd。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值