引导过程是指从用户打开主机 (物理机或虚拟机) 电源,到操作系统完全加载这段时间所发生的事情。
它主要包括三个阶段:由硬件 (物理硬件或虚拟硬件) 执行的初始启动、操作系统初始阶段加载、启动在系统中运行所需服务的机制。本章围绕这三个阶段展开讨论,并会涉及一些系统救援相关的操作。
不需要对引导过程的前两个阶段做过多修改,但熟悉它们,有助于在遇到故障时进行恢复。
第三个阶段由 systemd 管理,可以在此阶段修改系统中默认运行的服务。在前几章中,已经介绍过很多 systemd 相关的知识,本章会对它们进行一些回顾。
进入第一阶段的讨论。
23.1 BIOS、UEFI 引导过程介绍
计算机包含硬件嵌入式软件控制器,也称为固件 (firmware),通过固件可以管理底层硬件。固件首先识别系统中可用的硬件、以及硬件启用了哪些功能 (如 pre-boot network execution,PXE)。
x86 架构的个人电脑 (PC) 的嵌入式固件称为 BIOS,是基本输入输出系统 (Basic Input and Output System) 的缩写。
Linux 系统的 BIOS 启动过程有以下步骤:
-
主机上电并加载 BIOS 固件; -
固件初始化设备,如键盘、鼠标、存储等外设; -
固件读取配置,包括引导顺序、指定继续引导过程的存储设备; -
选择存储设备后,BIOS 在其上加载 MBR (Master Boot Record),运行操作系统加载器。在 Linux 中,操作系统加载器称为 GRUB (Grand Unified Bootloader); -
GRUB 加载配置以及在配置中指定的操作系统内核和 initramfs。内核存储在 vmlinuz 开头的文件中,初始引导镜像存储在 initramfs 开头的文件中。GRUB 配置文件、vmlinuz、initramfs 都存储在 /boot/
分区中; -
初始引导镜像加载系统中的第一个进程 systemd; -
systemd 加载系统的其它服务。
要实现此过程,磁盘上必须有一个 MBR 分区表,并且分配给 /boot/
的分区必须标记为 “bootable”
MBR 分区表格式有限,只允许四个主分区,并使用扩展分区等扩展功能来弥补这一限制。除非必要,否则不建议使用 MBR 分区。
UEFI (Unified Extensible Firmware Interface,统一可扩展固件接口) 的引导过程与 BIOS 的引导过程非常相似,主要区别是 UEFI 可以直接访问和读取磁盘分区。过程如下:
-
机器上电并加载 UEFI 固件; -
固件初始化设备,如键盘、鼠标、存储等外设; -
固件读取配置,指定继续引导过程的存储设备和可引导分区,不再需要 MBR 引导; -
选择存储设备后,会从 GUID 分区表 (GUID Partition Table,简称 GPT) 读取分区。访问 VFAT 格式的第一个分区。加载并运行 EFI 引导器。EFI 引导器位于 /boot/efi/
目录下。EFI 引导器加载 GRUB。 -
GRUB 加载操作系统内核,内核存储在 vmlinuz 开头的文件中,初始引导镜像存储在 initramfs 开头的文件中。GRUB 配置、vmlinuz、initramfs 文件都存储在 /boot/
分区中; -
初始引导镜像加载系统中的第一个进程 systemd; -
systemd 加载系统的其余服务。
与 BIOS 相比,UEFI 有几个优势,能够实现更完整的 pre-boot 环境和其他功能,如安全引导和 GPT 分区,GPT 分区可以超越 MBR 分区的 2TB 限制。
安装器负责创建引导,如果需要,还将创建 UEFI 分区和二进制文件。
通过 BIOS 或 UEFI,可以选择操作系统从哪个存储设备加载并进入下一个阶段。
下一节开始讨论第二个阶段。
23.2 GRUB、bootloader、initramfs
pre-boot 执行完成后,系统将运行 GRUB bootloader。
GRUB 的任务是加载操作系统内核,向内核传递参数和选项,并加载 initramfs。
GRUB 可以通过 grub2-install 命令安装。需要确定用作设备引导的磁盘,在本例中是 /dev/nvme0n1
:
[root@rocky08-host ~]# grub2-install /dev/nvme0n1
Installing for i386-pc platform.
Installation finished. No error reported.
grub-install 命令后边指定用于引导系统的磁盘,也就是在 BIOS 或者 UEFI 中配置的引导磁盘。这条命令会手动重建或修复损坏的系统引导。
GRUB 文件存放在 /boot/grub2/
目录下。主配置文件为 /boot/grub2/grub.cfg
。查看文件,会看到以下开头:
[root@rocky08-host ~]# head -n 6 /boot/grub2/grub.cfg
#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub2-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#
文件是自动生成的,不要编辑这个文件。如果必须修改它,有两种方法:
-
第一种方法是,按照 grub.cfg
文件中的提示,编辑/etc/default/grub
文件和/etc/grub.d/
目录中的内容,然后通过 grub2-mkconfig 命令重新生成 GRUB 配置。 -
第二种方法是,使用 grubby 命令。
在 Linux 中,当更新内核时,不会对现有内核进行升级,而是安装新的内核版本,保留旧的内核版本,并在 GRUB 中添加条目。这样方便在有需要时回退内核版本。在安装新内核的过程中,会为新内核创建一个新的更新后的 initramfs。
可以使用 grubby 命令查看当前的内核配置。--default-kernel 选项显示默认加载的内核文件:
[root@rocky08-host ~]# grubby --default-kernel
/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
--default-title 选项可以显示引导时使用的名称:
[root@rocky08-host ~]# grubby --default-title
Rocky Linux (4.18.0-372.9.1.el8.x86_64) 8.6 (Green Obsidian)
使用 --info 选项,可以查看内核的更多信息:
[root@rocky08-host ~]# grubby --info /boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
index=0
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 rhgb quiet $tuned_params"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="Rocky Linux (4.18.0-372.9.1.el8.x86_64) 8.6 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-4.18.0-372.9.1.el8.x86_64"
可以看到传递给 GRUB 的选项:
-
index:显示指定内核的索引号; -
kernel:包含将加载以运行操作系统核心的内核文件; -
root:分配给 /
目录并挂载的分区或逻辑卷; -
initrd:包含 RAM 磁盘的文件,用于执行引导过程的初始部分; -
title:在启动过程中向用户显示的描述性信息; -
id:引导项的标识符。
使用 grubby 命令查看默认内核的信息:
[root@rocky08-host ~]# grubby --info $(grubby --default-kernel)
可以对内核的启动参数进行修改,例如删除 quiet 和 rhbg 参数,使引导过程更详细:
[root@rocky08-host ~]# grubby --remove-args="rhgb quiet" --update-kernel=/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
[root@rocky08-host ~]# grubby --info $(grubby --default-kernel)
index=0
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 $tuned_params"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="Rocky Linux (4.18.0-372.9.1.el8.x86_64) 8.6 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-4.18.0-372.9.1.el8.x86_64"
通过 systemctl reboot 命令重启主机验证上面的修改:
[root@rocky08-host ~]# systemctl reboot
也可以使用 --args 选项向内核添加参数:
[root@rocky08-host ~]# grubby --args="quiet rhbg" --update-kernel=/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64
要在启动后查看引导信息,可以使用 dmesg 命令。
--info 和 --update-kernel 选项接受 ALL 选项,用来检查或对所有已配置的内核执行操作。例如:
[root@rocky08-host ~]# grubby --info ALL
index=0
kernel="/boot/vmlinuz-4.18.0-372.9.1.el8.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 $tuned_params quiet rhbg"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img $tuned_initrd"
title="Rocky Linux (4.18.0-372.9.1.el8.x86_64) 8.6 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-4.18.0-372.9.1.el8.x86_64"
index=1
kernel="/boot/vmlinuz-4.18.0-348.23.1.el8_5.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 rhgb quiet $tuned_params"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-4.18.0-348.23.1.el8_5.x86_64.img $tuned_initrd"
title="Rocky Linux (4.18.0-348.23.1.el8_5.x86_64) 8.5 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-4.18.0-348.23.1.el8_5.x86_64"
index=2
kernel="/boot/vmlinuz-4.18.0-348.el8.0.2.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 rhgb quiet $tuned_params"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-4.18.0-348.el8.0.2.x86_64.img $tuned_initrd"
title="Rocky Linux (4.18.0-348.el8.0.2.x86_64) 8.5 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-4.18.0-348.el8.0.2.x86_64"
index=3
kernel="/boot/vmlinuz-0-rescue-259d4d4e70e8414fb7dc745fbd14737e"
args="ro crashkernel=auto resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap biosdevname=0 net.ifnames=0 rhgb quiet"
root="/dev/mapper/rl-root"
initrd="/boot/initramfs-0-rescue-259d4d4e70e8414fb7dc745fbd14737e.img"
title="Rocky Linux (0-rescue-259d4d4e70e8414fb7dc745fbd14737e) 8.5 (Green Obsidian)"
id="259d4d4e70e8414fb7dc745fbd14737e-0-rescue"
以上是修改内核参数的方法。下面进入 initramfs 部分。
initramfs 文件包含用于启动系统的最小系统。通过 grubby --info 命令可知当前使用的是 /boot/initramfs-4.18.0-348.23.1.el8_5.x86_64.img
。可以通过 dracut 命令重新生成 initramfs 文件:
[root@rocky08-host ~]# dracut --force --verbose
...
dracut: *** Creating initramfs image file '/boot/initramfs-4.18.0-372.9.1.el8.x86_64.img' done ***
...
当 initramfs 文件损坏时,可以使用该命令重建 initramfs。在不同的硬件上通过备份恢复系统时,也可以使用该命令生成正确的存储驱动。
以上是引导过程初期阶段的基本知识,足够用来排查启动问题了。接下来进入第三个阶段的讨论。
23.3 使用 systemd 管理引导顺序
GRUB 加载内核和 initramfs,为系统启动做准备。接着启动系统的第一个进程,也就是 PID 为 1 的进程。这个进程负责加载系统所需的服务。在 Rocky Linux 中,PID 为 1 的进程是 systemd。
systemd 与引导顺序相关的前两件事是重启系统和关闭电源。使用 systemctl 命令完成这两项操作:
[root@rocky08-host ~]# systemctl reboot
执行上面的命令,系统会重新启动。可以使用 uptime 命令查看系统已经运行的时间:
[root@rocky08-host ~]# uptime
20:03:37 up 0 min, 1 user, load average: 0.85, 0.30, 0.11
接下来讨论 poweroff。但在这么做之前,要确保有办法启动主机。关闭主机的命令:
[root@rocky08-host ~]# systemctl poweroff
执行完该命令,需要重新开启主机电源。
systemctl halt 命令会挂起系统,但不发送关闭主机电源的信号。该命令很少使用,但知道它没有坏处。
之前的命令可以缩写为 reboot 和 poweroff。查看 /usr/sbin/poweroff
文件,会发现它是指向 systemctl 的符号链接。
第五章介绍了 systemctl 设置默认 systemd target。然而,可以使用 grubby 命令在引导时通过向内核传递 systemd.unit 参数来覆盖默认配置:
[root@rocky08-host ~]# systemctl get-default
multi-user.target
[root@rocky08-host ~]# grubby --args="systemd.unit=emergency.target" --update-kernel=$(grubby --default-kernel)
[root@rocky08-host ~]# systemctl reboot
系统重启,“systemd.unit=emergency.target” 参数已经被 GRUB 传给了内核,再由内核传给 systemd,后者将忽略默认配置,加载 “emergency target” 所需的服务。
最终系统在紧急模式下启动,等待输入 “root” 密码以登录控制台:
在紧急模式下,没有配置网络,也没有运行其他进程。在该模式下,可以在没有其他用户访问系统的情况下对系统进行更改。此外,/
文件系统是以只读模式挂载的。
如果操作系统的文件系统损坏了,这是在没有任何服务访问的情况下检查文件系统的好方法。用 fsck 命令来检查文件系统:
[root@rocky08-host ~]# fsck /boot
示例中的文件系统是正常的。但如果有需要修复的问题,可以在它上面运行 xfs_repair 命令,因为它是一个 xfs 文件系统。
根文件系统以只读的形式挂载在 /
上,如何对它进行更改呢?可以将 /
文件系统以读写的方式重新挂载:
[root@rocky08-host ~]# mount -o remount -o rw /
现在根文件系统以读写的方式挂载在 /
中。还需要执行 mount /boot 命令:
[root@rocky08-host ~]# mount /boot
挂载 /boot
后,执行一些管理任务,例如删除在 GRUB 中使用的 systemd.unit=emergency.target 参数:
[root@rocky08-host ~]# grubby --remove-args="systemd.unit=emergency.target" --update-kernel=$(grubby --default-kernel)
[root@rocky08-host ~]# reboot
系统又能正常启动了。在 Linux 中,这可能不是进入紧急模式的实用方法,但它演示了如何在引导时将参数传递给 systemd。
rescue.target 可以加载更多服务,并且也更容易,可以按照上面的步骤尝试进入 rescue.target 模式。
23.4 介入引导过程获得对系统的访问权
在工作交接中,时有遇到遗失 root 密码的系统。
想找回 root 密码,需要重启操作系统。在 BIOS/UEFI 检查完成后,系统会加载 GRUB。在等待选择内核的界面,按向上或向下的箭头终止计数,如图23.2 所示:
选择第一个条目。
阅读屏幕底部的帮助信息,找到编辑所选项目的说明。按 E
键编辑菜单中选择的行,会输出以下五行内容:
带有 load_video、set gfx_payload=keep 和 insmod gzio 的前三行是 GRUB 的设置选项。最后的两个选项很重要:
-
linux:定义加载的内核以及向内核传递的参数。 -
initrd:定义加载 initrd 的位置以及是否有任何选项。
将光标移动到 linux 行的末尾,添加 rd.break 选项,如图23.4 所示:
按 Ctrl + x 键,引导编辑过的行。rd.break 选项在加载 initrd 之前停止引导过程。现在的情况如下:
-
加载单 shell; -
当前挂载在 /
上的文件系统是一个具有基本管理命令的最小文件系统; -
目标根文件系统以只读的形式挂载在 /sysroot
(而不是挂载在/
); -
没有挂载其他文件系统; -
没有启动 SELinux。
使用 chroot 命令切换到真正磁盘上的 /
文件系统:
chroot /sysroot
以读写方式重新挂载 /
文件系统:
mount -o remount -o rw /
使用 passwd 命令修改 root 用户的密码:
passwd
root 用户的密码已经更改,并且 /etc/shadow
文件也已更新。但是在没有启用 SELinux 的情况下修改的,在下次引导时可能会导致问题。为了避免这种情况,需要创建 /.autorelabel
隐藏的空文件,系统在下次启动时修复 SELinux 标签。然后重启系统:
touch /.autorelabel
在这种状态下,可能需要强制关闭主机电源,然后再开启。系统重启后,就可以使用 root 用户及其新密码登录操作系统了。
文章评论