网上聊stm32的启动过程、聊项目管理里那个启动过程组,能搜出一大堆,但真正问 rhel9 的启动过程,很多运维兄弟能说出“GRUB 加载、内核起来、systemd 接管”三步,再往下就含糊了。我最早也是在服务器起不来的时候,才下决心把这条链路彻底捋顺——从按下电源键到登录界面出现,固件、引导器、内核、initramfs、systemd 五个环节各干各的活,任何一个环节断了,机器都进不了系统。这篇文章我就按启动的时间线,把这五段拆开讲透,顺便把每一段对应的排错手段也放在一起。看完你至少能对着故障现象判断出问题出在哪一步,而不是一上来就瞎敲命令。
1. 固件阶段:服务器按电源键之后到底先跑什么
1.1 UEFI 与传统 BIOS,RHEL 9 里两种引导方式的差异
RHEL 9 支持两种固件引导方式:UEFI 和传统 BIOS(Legacy mode)。现在新买的物理机基本都是 UEFI,但虚拟化平台、老掉牙的物理机上还是能见到 Legacy。这个阶段很多人不重视,觉得“反正按了开关就开机了”,其实固件决定了一件事:它去哪块盘、哪个分区、找哪个文件来启动系统。
UEFI 模式下,固件读取主板 NVRAM 里的启动变量,按照 BootOrder 的顺序找到启动项,然后去 ESP 分区(也就是挂载在/boot/efi的那个 FAT32 分区)加载引导程序。RHEL 9 装完后,你在 ESP 里能看到EFI/redhat/shimx64.efi和EFI/redhat/grubx64.efi。传统 BIOS 模式下则是另一条路:固件直接读磁盘 MBR 或 VBR 里的引导代码,再把 GRUB 的 core.img 加载起来。RHEL 9 在 Legacy 模式安装 GRUB 时,会在磁盘上留一个 1MB 的 BIOS boot 分区,没有文件系统,专门给 GRUB 用。很多人装机时看到这个分区以为装坏了,实际上这是正常的。
判断手里这台机器当前是哪种引导模式,非常简单:
# 有这个目录说明跑在 UEFI 模式 ls /sys/firmware/efi # 或者看 efibootmgr 有没有输出 efibootmgr -v如果efibootmgr报“EFI variables are not supported”,大概率就是 Legacy 引导。这个判断在做启动修复时特别重要,因为 UEFI 和 Legacy 的修复命令完全不同,后面第 5 章我会细说。
1.2 Secure Boot 链路:shim、MOK 和那个签名信任链
RHEL 9 在 UEFI + Secure Boot 环境下,默认不走“固件直接加载 grubx64.efi”这条路,而是先加载shimx64.efi。原因是 Secure Boot 要求固件只加载被它信任签名的程序,Red Hat 的密钥当然不被主板信任,但 shim 是被微软签名的,固件认它。于是信任链变成:固件 -> shim -> grubx64.efi -> 内核。shim 会检查 grubx64.efi 是否被 Red Hat 的密钥签名,或者被用户自己导入的 MOK(Machine Owner Key)签名。
这个机制在实际运维中最大的坑,就是第三方内核模块。比如你在 RHEL 9 上装了 Nvidia 闭源驱动、某些自带 DKMS 的网卡驱动,它们编译出来没有合法签名,Secure Boot 开启时内核直接拒绝加载。最典型的表现是开机报Verification failed: (0x1A) Security Violation,或者模块加载时 dmesg 里刷一堆module verification failed。我自己遇到过一台装 IB 网卡驱动的机器,就是卡在这里。
处理办法无非两条:要么在固件里关掉 Secure Boot,要么用mokutil导入自己的密钥。生产环境不建议乱关 Secure Boot,更规范的方式是走 MOK 流程:
# 生成密钥并签名,这一步在开发机上做 mokutil --import key.der # 重启后 shim 会进入 MOK 管理界面,确认导入1.3 固件阶段的故障长什么样
固件阶段出问题,操作系统层面的工具基本都看不到,因为内核还没起来。最常见的现象有三种:黑屏,连 RHEL 的 logo 都没有;开机直接进固件设置界面;卡在Reboot and Select proper Boot device。前两种基本指向启动项丢失或固件变量混乱,第三种多半是引导程序和硬盘失联。
UEFI 启动项被误删或者 ESP 分区被格式化,是运维里比较常见的低级事故。比如有人在 Windows 和 RHEL 双系统环境里手贱删了 ESP 分区,然后把 RHEL 也带崩了。修复思路不是去恢复分区,而是用一个 RHEL 9 安装介质或 Live 环境启动,chroot 进系统后重装 GRUB 并重建启动项,这个我在第 5 章再展开。
2. GRUB2 吃什么配置:从引导菜单到内核参数的下发
2.1 grub.cfg 不是拿来手工编辑的
RHEL 9 里 GRUB2 的实际配置文件是/boot/grub2/grub.cfg。UEFI 模式下 ESP 分区里的/boot/efi/EFI/redhat/grub.cfg只是一个小转发文件,内容基本就是一行configfile /grub2/grub.cfg。很多新人对这个“两层配置”很懵,其实原理很简单:GRUB 本体程序在 ESP 里,配置文件在/boot上,两边各管各的。
关键一点:grub.cfg 是生成出来的,不是手工编辑的。生成命令是:
grub2-mkconfig -o /boot/grub2/grub.cfg它读取/etc/default/grub和/etc/grub.d/下的脚本,再结合当前系统里装的内核生成完整菜单。为什么不能手工改?因为当你安装新内核、修改默认参数、升级 grub 相关包时,系统会自动重新生成配置文件,你手改的部分会被无声覆盖。我在生产环境见过有人直接在 grub.cfg 里加nomodeset修显卡问题,结果下次内核更新后故障复发,还一脸懵——这就是把配置写错地方了。
正确的持久化修改方式是编辑/etc/default/grub:
GRUB_TIMEOUT=5 GRUB_CMDLINE_LINUX="console=ttyS0,115200n8 nomodeset"改完执行grub2-mkconfig -o /boot/grub2/grub.cfg重新生成。另外grub2-editenv list可以查看 GRUB 环境块,里面记录了默认启动条目等信息,这玩意在排障时也经常用到。
2.2 BLS:RHEL 9 的引导菜单是“目录驱动”的
RHEL 9 的引导菜单结构和老版本 CentOS 6、RHEL 6 完全不同。老版本里每个内核都有一段冗长的 menuentry 写在 grub.conf 里,改配置要小心地数花括号。RHEL 9 用的是 BLS(Boot Loader Specification),菜单条目变成了/boot/loader/entries/下一个个独立的小文件。grub.cfg 里通过blscfg模块自动扫描这个目录,把所有.conf文件变成菜单项。
打开一个 RHEL 9 的条目文件,长这样:
$ cat /boot/loader/entries/5.14.0-427.13.1.el9_4.x86_64.conf title Red Hat Enterprise Linux (5.14.0-427.13.1.el9_4.x86_64) 9.4 (Plow) version 5.14.0-427.13.1.el9_4.x86_64 linux /vmlinuz-5.14.0-427.13.1.el9_4.x86_64 initrd /initramfs-5.14.0-427.13.1.el9_4.x86_64.img options root=/dev/mapper/rhel-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rhel-swap rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet每个字段含义都很直白:title 是菜单显示的名字,linux 行是内核文件路径,initrd 行是 initramfs 镜像路径,options 行就是传给内核和 initramfs 的参数。BLS 带来的最大好处是:加一个内核就是一两个文件的事,不用去改那个巨大的 grub.cfg。但代价是有时候菜单文件里写了内核路径,实际/boot下文件却不在了,开机就会报错,第 5 章我会讲这个案例。
日常操作里,查询和管理这些菜单条目,不要手动去编辑.conf,用 grubby 更稳:
# 查看所有内核菜单的完整信息 grubby --info=ALL # 把默认启动项改成第 0 个内核 grub2-set-default 0 # 查看当前默认 grub2-editenv list2.3 内核参数怎么区分“给内核的”和“给 initramfs 的”
RHEL 9 启动时传给命令行的参数,其实分两类,这个区分对排错至关重要。一类是内核自己用的,比如quiet、rhgb、console=ttyS0,115200n8、nomodeset,内核初始化时会消费它们。另一类是给 initramfs 里 dracut 逻辑用的,通常带rd.前缀,比如rd.lvm.lv=rhel/root(告诉 initramfs 根卷在哪个 LVM 逻辑卷上)、rd.luks.uuid=(告诉 initramfs 要解密的 LUKS 分区)。
rhgb quiet是 RHEL 9 默认开启的,它把启动过程藏在一个图形化或滚动条的界面后面,方便普通用户,但排障时必须删掉才能看到实时日志。开机时在 GRUB 菜单界面按e,把光标移到 linux 开头的那行,删掉rhgb quiet,再按Ctrl+x启动,就能看到完整的内核输出。这个临时修改只在本次启动生效,不落盘,是排查启动问题最常用的手段之一。
rd.break是另一个神级参数,加了它之后 initramfs 会在挂载真实根文件系统之前停下来,给你一个 shell。重置 root 密码、修复根文件系统都靠它,后面单独讲。
3. 内核引导与 initramfs:从压缩镜像到根文件系统的接力
3.1 vmlinuz 被加载之后发生了什么
GRUB 把vmlinuz-xxx和initramfs-xxx.img加载到内存后,就交棒给 Linux 内核了。这里有个很多人误解的点:GRUB 不只是“把文件读进内存”,它还会读取内核镜像头部的一个固定结构——boot protocol,告诉内核“内存里哪里有你的镜像、哪里有 initramfs、命令行参数是什么”,然后跳转到内核入口地址。
内核进入后,先执行实模式下的启动代码,做一些基础硬件探测和内存状态检查,然后把自己解压到内存中合适的位置,切换 CPU 到保护模式甚至长模式(x86_64 下的 64 位模式),最后进入 C 语言世界的大本营start_kernel。start_kernel里做的事一连串:初始化调度器、内存管理、中断、时间子系统、创建 PID 1 进程等等。这个过程对运维来说就是个黑盒,但有一点可以确认:如果你在 GRUB 菜单删掉rhgb quiet,屏幕上刷出来的那些[ 0.000000]开头的行,就是这段旅程的实时日记。事后想看,dmesg或journalctl -k -b都能查到。
3.2 initramfs 为什么必不可少
很多人好奇:为什么内核不能直接挂载根文件系统启动,非要搞一个 initramfs 出来?道理很简单:RHEL 9 的根文件系统经常放在 LVM 卷、软 RAID、LUKS 加密盘,甚至 iSCSI 多路径设备上,而内核自身没有能力加载这些设备对应的驱动,也没有 LVM、加密相关的工具。所以需要一个“临时根文件系统”作为跳板:它包含存储驱动、lvm2 工具、cryptsetup 工具、udev 规则以及 dracut 的脚本,先把真实根设备找出来、准备好,再切换过去。
RHEL 9 里 initramfs 镜像由 dracut 生成,对应文件是/boot/initramfs-$(uname -r).img。想确认里面到底装了啥,可以用 lsinitrd:
# 只查看模块和脚本清单 lsinitrd /boot/initramfs-$(uname -r).img # 直接解包到目录,比 lsinitrd 更直观 mkdir /tmp/initramfs-test cd /tmp/initramfs-test lsinitrd --unpack /boot/initramfs-$(uname -r).img我强烈建议新人在测试环境里解包看一次,你会有种“原来系统启动时藏了一个迷你 Linux”的真实感。里面跑着一个精简版的 systemd,dracut 的各个模块被包装成 systemd 单元,按dracut.target下的依赖顺序执行:udev 加载驱动、lvm 激活卷组、cryptsetup 解密、挂载真实根到/sysroot,最终调用systemctl switch-root /sysroot完成切换。switch-root 之后,initramfs 里的东西会被清理,把内存归还给系统。
3.3 SELinux 标签带来的“隐形”阶段
RHEL 9 默认 SELinux 是 Enforcing,这个对启动过程的影响很多人没注意到。如果系统检测到/etc/selinux/config指定了全新策略、或者根文件系统存在/.autorelabel标志文件,开机时会触发全盘文件安全上下文重打标签,也就是 auto relabel。表现就是启动异常慢,屏幕可能长时间停在某个百分比不动,看起来像死机,其实它正在逐个文件打标签。
我在测试环境里把整个系统恢复到旧快照时踩过一次:启动卡在 relabel 阶段特别久,当时还以为内核崩了,后来才发现是 SELinux 在后台工作。如果真的需要跳过(仅限测试环境应急),可以在内核参数里临时加selinux=0,但生产环境千万不要这么干,RHEL 9 关 SELinux 会直接改变系统的安全姿态,等于是绕过了默认安全机制。更正确的做法是等 relabel 跑完,或者手动touch /.autorelabel后重启让它重来。
4. systemd 接管:从 switch-root 到登录界面的目标链
4.1 PID 1 的交接:内核把第一棒交给谁
内核初始化结束后,会启动 PID 为 1 的第一个用户空间进程。RHEL 9 里,无论 initramfs 阶段还是真实系统阶段,这个进程都是/usr/lib/systemd/systemd。initramfs 里的 systemd 在 switch-root 后,就让位给真实根文件系统里的 systemd 继续跑。注意这里不是简单重启,整个 systemd 进程通过重新执行自身的方式完成交接,所以 PID 还是 1。
真实系统里的 systemd 启动后,第一件事是读取/etc/systemd/system/default.target,决定进入哪个目标状态。RHEL 9 装了图形界面的话,默认通常是graphical.target;最小化安装就是multi-user.target。两者是依赖关系,graphical 依赖 multi-user,multi-user 又依赖 basic.target,basic 依赖 sysinit.target,一环套一环。
# 查看当前默认目标 systemctl get-default老运维比较熟悉/etc/inittab,那套 runlevel 体系在 RHEL 9 里已经彻底消失了,只留下 runlevel 与 target 的映射兼容关系。比如 runlevel 3 对应 multi-user.target,runlevel 5 对应 graphical.target。systemd 的厉害之处在于,它先把所有单元文件读进来,分析依赖关系,再按照拓扑顺序并行启动没有依赖冲突的服务。这就是为什么 RHEL 9 启动速度比老系统快一大截。
4.2 网络、SSH 这些关键服务到底什么时候起
对服务器运维来说,最关心的就是 SSH 什么时候能连上、网络什么时候通。RHEL 9 里网络管理统一由 NetworkManager 负责,它在 sysinit 阶段早期就会启动,真正拿到 IP 却要等设备就绪、连接激活。这里有个经典的坑:如果你自己写了一个 systemd 单元,里面写了After=network.target,以为网络一定可用了,其实network.target只是个“松散聚合点”,它不代表 IP 就绪。要确保网络真正可用,应该写:
After=NetworkManager-wait-online.service Wants=NetworkManager-wait-online.service这是我见过最多人掉进去的启动顺序陷阱。sshd 服务属于 multi-user.target 的依赖项,它会比较晚启动,但它启动时不依赖网络完全就绪,因为 SSH 服务可以监听在 0.0.0.0 上,等网络通了自然就能连。firewalld 也是这个阶段启动,它与 NetworkManager、sshd 之间有一套完整的依赖关系,正常情况下你不需要干预。
4.3 用 systemd-analyze 看时间都花在哪
RHEL 9 排查启动慢,第一工具是 systemd-analyze。三个命令我几乎每次都用:
# 总耗时和各阶段耗时 systemd-analyze time # 每个单元从启动到完成的时间排行 systemd-analyze blame # 关键依赖链 systemd-analyze critical-chaintime会告诉你 firmware 多长时间、loader 多长时间、kernel 多久、initrd 多久、用户空间多久——这一下就能把“慢”定位到大阶段。blame按耗时排序,但要注意它统计的是“单元从开始到完成”的时间,包括它在等依赖的时间,未必代表你优化这个单元就能提速。critical-chain会告诉你哪条依赖链最长,这条链上的每一环都是可以深挖的。想要可视化,可以systemd-analyze plot > /tmp/boot.svg,扔进浏览器看甘特图,大段时间一目了然。
RHEL 9 默认情况下 journald 的日志持久化是 auto 模式,也就是说如果/var/log/journal存在就会写盘。为了后续排障不丢启动日志,我建议手动开启持久化:
mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal systemctl restart systemd-journald之后每次开机的完整日志都留在/var/log/journal里,排查“昨天重启到底发生了什么”这类问题会从容很多。
5. 排错实战:怎么把一个起不来的 RHEL 9 拉回来
5.1 先定位故障属于哪一段
我排查启动故障,第一件事永远不是敲命令,而是问三个问题:开机能看到什么?黑屏还是 GRUB 菜单?内核日志刷不刷?能不能进 emergency?
根据现象先分段,再决定工具。下面这张表是我自己的快速定位表,给你参考:
| 故障现象 | 大概率阶段 | 第一排查手段 |
|---|---|---|
| 全黑、直接进固件、Security Violation | 固件 | UEFI 设置、efibootmgr、Secure Boot 策略 |
| 停在 GRUB 菜单、grub> 提示符、报 file not found | 引导器 | 检查 loader/entries、grub.cfg、重新安装 GRUB |
| 内核 panic、堆栈刷屏 | 内核 | 删除 rhgb quiet 看输出、换旧内核、查启动参数 |
| 卡在内核日志后没动静、出现 dracut 求救界面 | initramfs 阶段 | rd.break 进入 shell、lsinitrd 检查镜像、dracut 重建 |
| 服务启动失败、启动极慢、卡在 emergency | systemd | journalctl -b、systemctl list-units --failed |
5.2 从 GRUB 命令行走一条排错链路
进 GRUB 菜单后按e,这算是解锁排错之门的钥匙。你能临时修改内核参数,注意是临时生效,不会写盘,失败了重启就复原,非常适合试错。
最常用的几组改法:
# 删掉这两项,让所有启动日志打到屏幕上 # 把: rhgb quiet 删掉 # 在行尾追加,让 initramfs 挂载真根前停住 rd.break # 在行尾追加,直接进入 emergency 模式 systemd.unit=emergency.targetrd.break停住后,你会得到一个 root shell,但这时根文件系统还没挂载或挂在/sysroot。接下来是一套固定的操作,重置 root 密码就靠它:
# 确保真实根已挂载,如果没挂载先挂上 mount -o remount,rw /sysroot # 进入真实根 chroot /sysroot # 改密码 passwd root # 关键一步:因为 chroot 里跑过修改操作,安全上下文可能变了, # 创建这个文件让重启后自动 relabel,避免 SELinux 报错 touch /.autorelabel exit exit这里的touch /.autorelabel是很多教程不会提、但极其关键的一步。不加它,改完密码直接重启,很可能出现系统起不来或者 SID 相关错误,我见过有人因为这个在 emergency 模式里反复折腾。
5.3 我遇到的一个典型故障:/boot 快照回滚导致的内核文件“幽灵”
最后分享一个真实案例。某台跑 RHEL 9.4 的虚拟机,头一天还正常,第二天开机卡在 dracut 的求救界面,提示找不到根设备。先砍掉rhgb quiet重启,看到内核日志里明确说Failed to mount /sysroot,然后用rd.break进 shell,发现/dev/mapper/rhel-root根本不存在。再一查,是虚拟化平台做了快照回滚,把卷组的元数据回滚到了旧状态,但/boot/loader/entries里的内核和 initramfs 文件却来自较新的状态,两边对不上。
处理方式不复杂但很能说明问题:在 GRUB 菜单按e检查 linux 行,把内核路径改成确实存在的旧版本vmlinuz-xxx和initramfs-xxx.img,先进系统,然后重新生成 initramfs、更新引导配置。整个过程就像是在启动链路的两个节点之间手动搭了一座临时桥。
这个案例给了我很深一个印象:RHEL 9 的启动过程是一条链,固件找 GRUB,GRUB 找内核文件,内核找 initramfs,initramfs 找真实根,systemd 拉起用户空间。每一个引用关系都建立在文件和时间戳的匹配上。排障时脑子里装着这条链,就不会像无头苍蝇一样到处试命令。
我自己现在排查任何一台 RHEL 9 起不来的机器,流程已经固化成肌肉记忆了:先问能看到什么、是否能进 emergency、内核日志在不在,然后顺着链从前往后推,在可疑节点用rd.break或 journald 日志做验证。这套“分段定位”的思路说穿了不值钱,但比任何单一工具都管用。你要是第一次接触 RHEL 9 启动过程,建议拿一台测试机,反复执行systemd-analyze和dracut -f重建 initramfs,再故意把/boot/loader/entries里的路径改错一次,亲眼看看故障长什么样,下次真遇到时就不会慌。