ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux启动过程全解析:从BIOS到systemd的故障排查实战

Linux启动过程全解析:从BIOS到systemd的故障排查实战

作为长期和Linux服务器打交道的系统管理员,我觉得启动过程控制是整个系统管理知识链里最容易被轻视、但实际价值极高的一块。很多同学对"开机"的理解停留在按下电源键、看到桌面或登录提示符这一步,一旦遇到服务器重启后起不来、误改启动参数导致卡死、或者GRUB菜单丢失这类故障,就完全不知道从哪里下手。这篇文章把RH134课程第10章"控制启动过程"的重点整理一遍,从开机加电到systemd完成初始化,把整个启动链路的每个环节、各环节之间的交接逻辑、以及常见故障的处理思路讲清楚。文章适合准备考RHCSA认证的读者、刚接手服务器运维的新人,以及那些想系统补全Linux启动机制这块拼图的开发者。

1. 启动流程全览:从加电到登录界面的完整链路

1.1 固件层:BIOS与UEFI的初始化

很多人以为按下电源键后第一个干活的是内核,实际上真正的第一棒交给固件程序。传统BIOS和现代UEFI在这个阶段做的事情高度相似:初始化CPU、内存、芯片组等核心硬件,执行Power-On Self-Test(自检),然后按照Boot Order去搜索可引导设备。RHEL 7之后的Linux发行版基本都默认使用UEFI引导方式,但兼容模式下仍然会走BIOS风格的MBR引导流程,所以两种路径都得理解。

  • BIOS + MBR路径:固件从磁盘的第一个扇区读取主引导记录,GRUB2的第一阶段引导代码通常安装在这里,然后这条链逐步把控制权交给真正的Bootloader主体。
  • UEFI + EFI路径:固件直接读取EFI系统分区(ESP分区,通常是/boot/efi)下的.efi文件,比如grubx64.efi,跳过了传统MBR的限制,加载速度和灵活性都有提升。

实际运维中,区分这两种路径非常重要。比如你在基于UEFI引导的系统上尝试用旧式grub-install /dev/sda的方式修复引导,很可能失败,因为UEFI固件根本不会理睬磁盘MBR区域的代码。反过来,老旧的BIOS固件也读不懂EFI分区。理解这一点后,再看启动故障的报错信息,很多线索就清晰了。

1.2 GRUB2引导加载阶段:准备内核运行环境

固件把控制权交给GRUB2后,GRUB2要做的事情并不是"直接启动内核"这么简单。它需要读取配置文件/boot/grub2/grub.cfg(UEFI路径下通常是/boot/efi/EFI/redhat/grub.cfg),然后按菜单项找到内核镜像vmlinuz-*和初始RAM磁盘镜像initramfs-*.img。

这里有个关键概念:为什么不能直接加载vmlinuz后就让内核挂载硬盘上的根文件系统?原因在于内核自身只包含基本的存储驱动代码,而根文件系统可能位于LVM逻辑卷、软件RAID阵列或者加密设备上,这些设备需要额外的驱动模块才能被识别。initramfs就是解决这个问题的小型临时根文件系统,它先把必要的驱动模块加载进内存,让内核有能力挂载真实根分区,然后完成交接。

实际运维时可以这样验证:在GRUB2菜单界面按e键编辑启动项,你会看到linux开头的一长串参数,其中包含了root=/dev/mapper/rhel-root之类的根设备指定方式,以及rd.lvm.lv=...这类与initramfs相关的参数。改错这些字段,系统就会在启动过程中报Failed to mount /sysroot之类的错误,最常见的根因就是根设备标识写错、LVM卷名不匹配或者SELinux 标签异常。

1.3 内核初始化与PID 1的交接

内核镜像被加载到内存并解压后,内核初始化所有必须的核心子系统,比如内存管理、进程调度器、中断处理、网络协议栈等。等到内核把这些基础服务准备好了,它会挂载真正的根文件系统,然后启动系统的第一个用户空间进程——PID 1。在RHEL 7以上版本中,这个PID 1就是systemd。

内核与systemd的交接有一个容易被忽略的细节:systemd启动后并不是马上并行拉起所有服务,而是先执行一个基础初始化流程,包括设置主机名、挂载/proc、/sys、/dev等虚拟文件系统、应用内核参数、启动systemd-journald日志服务等。这一阶段完成的动作是后续所有服务启动的前提。如果这里的某个环节出问题,比如/etc/fstab中某个挂载点配置错误,systemd会尝试多次挂载,最终超时后继续启动别的单元,但你会在日志里看到大量异常报错。这也是为什么启动故障排查时,第一个动作永远是查看日志。

1.4 启动目标与用户空间的建立

当systemd完成了基础初始化后,它读取默认的启动目标(target)。所谓的target可以理解为一组服务的集合和依赖关系的编排单元。默认target可能是multi-user.target(多用户命令行界面,对应传统运行级别3),也可能是graphical.target(图形登录界面,对应运行级别5)。systemd会解析这个target的全部依赖,并行激活所有需要的服务单元,最终把系统带到可登录状态。

整个链路用一句话概括:固件找引导程序,引导程序加载内核,内核挂载根文件系统,systemd拉起用户空间服务。任何一个环节的故障,表现可能天差地别:固件层问题表现为无显示输出或找不到引导设备;GRUB2问题表现为停在引导菜单或直接黑屏;内核问题表现为内核恐慌(Kernel Panic);systemd阶段问题则表现为启动卡在某处或部分服务失败。后续所有排查技巧,本质上都是在定位你当前到底卡在链路的哪一环。

2. systemd目标机制:启动过程的路线图

2.1 target与runlevel的对应关系

RHEL 7之前的SysVinit用运行级别(runlevel)来区分系统状态,数字0到6各有含义。systemd用target抽象出了更灵活的单元组织方式,但为兼容旧习惯,两者之间有明确的对应关系。实际使用中,这些知识直接决定了你能否通过切换target进入恢复环境。

runleveltarget单元用途
0poweroff.target关机
1rescue.target单用户模式,适用于修复系统
2multi-user.target命令行多用户模式(老系统中2级不带网络服务,新systemd统一用multi-user)
3multi-user.target命令行多用户模式
4multi-user.target命令行多用户模式
5graphical.target图形界面模式
6reboot.target重启

生产环境中最常用的切换命令就是systemctl isolate multi-user.target或systemctl isolate graphical.target。isolate表示立即停止当前target下所有非依赖项服务,切换到新的target。如果你把它理解成"运行级别切换"的现代替代方案,很多操作逻辑就顺理成章了。

2.2 查看与修改默认启动目标

服务器重装后最常见的需求就是确认当前系统默认进什么模式。用两个命令就能搞定:

# 查看当前默认target systemctl get-default # 修改默认target systemctl set-default multi-user.target

需要注意:systemctl set-default修改的只是默认启动目标,它不会影响当前正在运行的目标,必须重启后才生效。如果你想立刻切换到目标模式,需要用systemctl isolate multi-user.target。我有一次在某台测试服务器上执行了systemctl set-default graphical.target,但当时系统已经处于多用户模式,我以为操作失败,反复执行了几次才发现需要重启才能看到效果,这种细节在考试和实际环境里都很容易踩坑。

此外,systemctl list-units --type=target可以列出当前活动的target单元,systemctl list-dependencies multi-user.target则能查看该target依赖的所有服务单元。这些命令在排查"为什么某个服务没有启动"时非常有用,因为你可以顺着依赖树逆推,看是哪个上游单元拖住了整个链。

2.3 救援目标与紧急目标的使用场景

target机制真正厉害的地方在于内置了rescue.target和emergency.target两个恢复环境。两者听起来类似,但差别很大。

  • rescue.target:传统意义上的单用户模式。它会挂载根文件系统,启动基础系统服务,但不启动网络服务、不启动多用户环境。适合修复/etc/fstab错误、重置密码、重新生成GRUB配置等场景。
  • emergency.target:最小的紧急Shell,连根文件系统在最初都不会完整挂载。它在你连rescue模式都无法正常进入时兜底。适合处理根文件系统损坏、关键配置严重损坏等极端的场景。

在GRUB菜单中,你可以在选中的启动条目上按e进入编辑模式,找到linux起始的那一行,在行尾追加systemd.unit=rescue.target或systemd.unit=emergency.target,然后按Ctrl+x启动。这个操作是RHCSA考试里的重点,也是真实运维中进入救援环境的快捷方式。有人会误以为必须在GRUB菜单按某个隐藏键才能进入单用户模式,其实通过修改内核命令行参数来指定目标,是systemd时代最干净可靠的方式。

2.4 生产环境中修改target的真实案例

之前我接手一台启动后卡在黑屏、只显示光标闪烁的服务器,远程终端里完全无法操作。通过带外管理控制台进入GRUB菜单,给内核追加了systemd.unit=rescue.target后,系统直接进入了root Shell。排查后发现是某次更新后默认target从multi-user.target变成了不存在的自定义target,导致systemd无法解析。用systemctl set-default multi-user.target恢复默认值后重启,一切正常。

这种问题的根源在于:systemctl set-default虽然会校验target文件是否存在,但如果有人手动修改了/etc/systemd/system/default.target的软链接,指向了一个被误删的target文件,系统启动时就会卡在寻找默认目标的阶段。善用rescue.target,通常十分钟内就能把这类问题解决掉。

3. GRUB2引导配置:启动菜单的前世今生

3.1 GRUB2的层次结构与配置文件关系

GRUB2采用两级配置设计。vi /etc/default/grub编辑的是核心变量文件,它并不直接控制启动菜单,真正被GRUB2读取的是编译生成的/boot/grub2/grub.cfg。每次修改/etc/default/grub之后,都需要运行grub2-mkconfig -o /boot/grub2/grub.cfg重新生成配置文件,修改才能生效。

这里有一个新手常犯的错误:直接去手动编辑grub.cfg,结果系统升级内核或重装GRUB时,所有手动修改都被覆盖。正确的做法永远是把改动写在/etc/default/grub中,然后通过生成命令同步到实际配置。如果系统是UEFI引导方式,生成的输出路径需要用-o /boot/efi/EFI/redhat/grub.cfg,很多人在这一步混淆了路径,导致改了配置但引导完全没变化。

3.2 修改默认启动项与菜单超时时间

最常用的GRUB变量有三个:

# 默认启动项,可以是数字,也可以是菜单项名称 GRUB_DEFAULT=0 # 菜单显示时间 GRUB_TIMEOUT=5 # 额外传递给内核的参数 GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"

GRUB_DEFAULT=0表示默认启动第一个菜单项。但系统安装多内核或双系统后,菜单项顺序变化频繁,依赖数字编号并不可靠。更稳定的做法是使用grub2-set-default命令设置指定的菜单项名称,或者用grub2-editenv list查看当前引导记录中记录的saved_entry值:

# 查看当前保存的默认启动项 grub2-editenv list # 设置某个菜单项为默认启动 grub2-set-default "Red Hat Enterprise Linux (3.10.0-1160.el7.x86_64) 7.9 (Maipo)" # 再生成一次grub.cfg,确保设置持久化 grub2-mkconfig -o /boot/grub2/grub.cfg

修改GRUB_TIMEOUT时也要注意:生产服务器通常建议设置成5秒以上,但如果你把超时时间改成0,开机后GRUB会立即按默认项启动,用户根本没有机会按e进入编辑模式,一旦默认项有问题,就只能靠安装介质救援了。内部测试机可以这样操作,生产机千万别省这两秒。

3.3 单系统GRUB修复的无坑操作

最常见的一种故障是,根文件系统本身完好,但GRUB阶段一数据损坏,开机后提示GRUB>或者Unknown filesystem。这种情况不需要重装系统,通过安装介质或者救援光盘进入救援模式即可修复。

如果系统能进入rescue模式但GRUB损坏场景较复杂,常规操作是chroot到硬盘根文件系统后重新生成GRUB配置并安装到磁盘:

# 假设已经进入rescue环境,并定位到真实根目录 mount -o remount,rw /sysroot chroot /sysroot # 重新生成GRUB配置 grub2-mkconfig -o /boot/grub2/grub.cfg # 重新安装GRUB到磁盘(BIOS引导) grub2-install /dev/sda # UEFI引导系统则执行 grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg

注意,以上命令的执行环境必须和真实系统完全对应。用chroot进入/sysroot之前,需要确认根分区和/boot分区已经挂载到正确位置。有些同学在chroot后看不到/boot/grub2目录,就是因为/boot可能是独立分区,没有提前挂载。这一步看似简单,却是修复失败的头号原因。

3.4 修改内核启动参数的临时手段

在GRUB菜单按e编辑启动项时,可以直接修改linux行的内核参数,比如临时加上rdbreak(在initramfs阶段中断)、selinux=0(临时关闭SELinux)、systemd.unit=emergency.target等。这种修改只在本次启动有效,重启后自动恢复。

这个临时修改手段非常实用。比如你配置了错误的GRUB_CMDLINE_LINUX,内核在加载根设备时崩溃,你可以通过编辑启动项把那条参数临时删掉,先进系统把配置改回来,再正常重启。比如rhgb(图形化启动)和quiet(隐藏启动日志)这两个参数,在排障时最好临时去掉,因为完整的启动日志直接显示在屏幕上,比事后翻journal更快定位卡点。

4. 启动故障排查与修复:把命令变成肌肉记忆

4.1 启动失败后的第一屏日志侦察

系统启动进入systemd阶段后如果出现异常,最直观的表现是启动过程卡顿、服务反复重启,或者直接黑屏。此时第一个排查动作就是查看日志:

# 查看本次启动的所有错误级别的日志 journalctl -b -p err # 查看本次启动从哪个时间点开始 journalctl -b --no-pager | head -50 # 列出启动失败的服务单元 systemctl list-units --failed

journalctl -b中的-b表示读取本次启动的日志,如果想查看上一次启动的记录,可以用journalctl -b -1。这个功能在系统反复重启但问题只出现在某一次启动中时特别有用。

实际使用中,我最推荐的组合是systemctl list-units --failed+journalctl -b -p err。前者告诉你哪个服务没起来,后者告诉你为什么没起来。有一次我在某台服务器上看到systemd-networkd反复失败,前者只报了服务名,后者的日志里明确指出是网卡配置文件里的MACAddress=字段和实际网卡不符,十分钟内就定位了问题。

4.2 进入救援模式完成根文件系统修复

当内核已经启动但根文件系统挂载失败时,通常的表现是启动过程跳出一堆Failed to mount /sysroot或You might want to fix this problem之类的错误,然后自动进入emergency shell。这时需要手动确认LVM卷、分区和文件系统状态。

# 查看磁盘和LVM状态 lvs vgs # 手动尝试挂载根卷 mount /dev/mapper/rhel-root /mnt # 如果文件系统有错误,修复根卷 xfs_repair /dev/mapper/rhel-root

需要注意RHEL 7及以后版本默认文件系统是XFS,损坏后的修复方式不是fsck,而是xfs_repair,而且目标卷必须先卸载或只读挂载。在emergency target中根文件系统本来就未完整挂载,执行修复的时机比较理想。等文件系统修好后,再重新启动,系统通常会恢复正常。整个过程的关键是不要慌,先确认挂载信息,再动手修。

4.3 重置root密码的全流程

忘记root密码是几乎每个管理员都会遇到的场景,控制启动过程的知识在这里直接派上用场。整体思路是:通过GRUB临时修改启动参数,进入rescue target,把根文件系统重新挂载为读写,然后用passwd命令重置密码。操作步骤如下。

  1. 在GRUB菜单选中启动项,按e进入编辑模式。
  2. 找到linux开头的行,在行尾追加systemd.unit=rescue.target(如果文件系统是SELinux启用的,还需要追加rd.break来提前断开,但更保守的方式是直接进rescue)。
  3. 按Ctrl+x启动进入rescue shell。
  4. 挂载根文件系统可写:mount -o remount,rw /sysroot
  5. 切换到真实根目录:chroot /sysroot
  6. 执行passwd root设置新密码。
  7. 关键额外操作:修改密码后,如果系统启用了SELinux,需要执行touch /.autorelabel,让下次启动时自动重新标记文件系统标签。否则,SELinux可能阻止带新标签的/etc/shadow文件被读取,造成密码修改无效或登录异常。
  8. 退出chroot,重启系统。

这里有三个容易翻车的地方:第一,忘记mount -o remount,rw /sysroot,直接chroot进去会发现文件系统是只读状态,passwd命令报错;第二,忘记touch /.autorelabel,重启后因为SELinux上下文错误无法正常启动;第三,在UEFI系统上多次编辑GRUB参数时,不小心删掉了linuxefi行中的必要参数,导致内核无法加载。这些细节在我实际带的团队新人身上都已经出现不止一次了。

4.4 通过内核命令行参数绕过故障

有些故障并不需要进入救援环境,直接通过临时修改内核参数就能绕过。比如系统卡在某个挂载点无法启动,而/etc/fstab里确实有一条错误配置,你可以在GRUB编辑界面给内核追加emergency或systemd.unit=rescue.target启动,然后修改/etc/fstab。

更精细的绕过方式是用参数systemd.mask=服务名屏蔽掉某个故障服务,或者systemd.wants=服务名临时追加依赖。虽然这些手段在日常运维中用得不算频繁,但在RHCSA考试里出现过类似场景:某个服务导致图形界面无法启动,要求考生临时屏蔽该服务以恢复系统。我们要知道,屏蔽操作并不是彻底删除服务文件,而是创建指向/dev/null的软链接,恢复时可以systemctl unmask取消屏蔽。这套逻辑在启动过程中同样适用。

5. 常见问题与排查技巧实录

5.1 修改GRUB配置后立即重启却毫无变化

这个问题出现频率极高。原因基本只有一个:修改/etc/default/grub后没有运行grub2-mkconfig -o /boot/grub2/grub.cfg。有些同学改了文件以为保存就生效,但GRUB2并不实时读取/etc/default/grub,它只认编译出的grub.cfg。另外还需要确认当前系统到底是BIOS引导还是UEFI引导,生成配置的目标路径不能搞混。可以在系统里执行ls /sys/firmware/efi来确认是否存在EFI运行时变量,如果目录存在就是UEFI引导。

如果确认路径没错、配置也重新生成了,还是没有效果,再检查GRUB_DEFAULT的设置值。有些菜单项名称包含空格和版本号,你用grub2-set-default设置时输入的名称必须与grub.cfg中的menuentry标题完全一致,差一个空格都会导致参数无效。

5.2 默认target被更名后,系统无限重启

我在某次练习中手动把/etc/systemd/system/default.target这个软链接指向了一个自己创建的无效target文件,重启后系统在启动早期阶段就报错,并自动进入紧急模式。排查方式很简单:在紧急模式下执行ls -l /etc/systemd/system/default.target检查链接指向,然后用systemctl set-default multi-user.target重建软链接,最后重启恢复。

更隐蔽的情况是target依赖链里有服务单元缺失。此时系统虽然能进入某个target,但启动过程会反复报Failed to start 服务名,而且systemctl list-units --failed里能看到多个失败项。排查时先看失败的单元,再顺着systemctl list-dependencies找到依赖源,通常都是某个服务文件被误删或配置错误导致的连锁反应。

5.3 SELinux与启动过程的相互作用

启动过程控制中很少人意识到,但SELinux安全策略异常本身就是启动失败的高频诱因。常见症状包括:一切系统文件看起来都正常,但服务启动时无法读写某些文件,或者重启后直接卡在SELinux相关的错误提示。

这种情况下,进入救援模式或使用GRUB编辑追加selinux=0(临时关闭SELinux)启动系统,然后重新标记文件系统:

# 进入rescue环境后 mount -o remount,rw /sysroot touch /sysroot/.autorelabel # 退出并重启,让系统在下次启动时重新生成SELinux标签

autorelabel的过程可能比较耗时,尤其是在文件很多的生产服务器上,但这是保证SELinux策略正常应用的正确方式。考试里如果出现“修改过SELinux上下文,重启后系统无法登录”这类题目,十有八九是要用到.autorelabel这个文件。

5.4 启动配置文件的备份与回滚习惯

控制启动过程的所有操作都涉及系统关键配置文件,一个低级的配置错误可能导致整机起不来。我在实际运维中给自己定了一个规矩:修改/etc/default/grub、/etc/fstab、以及任何target相关配置之前,先复制一份备份文件。

cp /etc/default/grub /etc/default/grub.bak.$(date +%F) cp /etc/fstab /etc/fstab.bak.$(date +%F)

这些备份文件体积小、成本低,一旦操作失误,可以迅速用grub2-mkconfig -o ...配合备份内容恢复配置,而不需要进救援模式手动改回。尤其是fstab,它是启动过程中极易出错的位置,一个错误的UUID挂载配置足以让系统彻底卡死在挂载阶段。养成改前备份的习惯,可以省下大量排障时间。

5.5 我踩过的几个关键坑位

最后分享几条从实际操作中总结的经验,不一定写在教科书里,但非常实用。

第一,不要在GRUB_CMDLINE_LINUX里随便追加关闭驱动的参数,比如modprobe.blacklist=xxxx,设置容易、排查艰难。这类参数一旦出错,网卡、磁盘控制器可能找不到,系统会以非常诡异的方式启动失败,日志里又没有直接报错指向该参数。排障时首先要想到把最近追加过的参数全部临时移除,再逐个确认。

第二,使用systemctl set-default之前一定要确认target名称拼写。系统不会因为你输错了名字就友好提示,它会在下次启动时找不到目标而进入紧急模式。排障时可以通过systemctl list-units --type=target --all查看所有可用的target。

第三,在UEFI环境中修复GRUB的顺序非常重要。重新生成grub.cfg后,如果仍然提示找不到引导文件,先确认ESP分区是否挂载在/boot/efi,再用efibootmgr -v查看固件中的引导项是否指向正确文件。有时候问题不在GRUB本身,而是固件启动项丢失或顺序混乱,需要重新创建引导项才彻底解决。

启动过程控制这块知识,平时安安静静躺在系统里,但每次真出故障时,它就是整个运维安全网的最后一根绳子。把这套流程想明白、练熟练,比记住一两条孤立的命令有用得多。我自己的习惯是每周挑一台测试机,故意制造一种启动故障,然后强制自己用救援模式恢复,反复几次之后,遇到生产环境出问题,手速和思路都会稳很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进