RHEL9启动过程全解析:从固件到systemd的完整链路与排障
有一次我在机房把一台刚装好 RHEL9 的服务器重启结果它卡在“启动过程”的后半段屏幕上一个光标闪了快十分钟登录提示符就是不出来。一开始以为是硬件故障拔内存、换硬盘都试过最后才发现是某个 systemd 服务在等待超时。那次排查让我把 RHEL9 的启动过程从头到尾重新梳理了一遍这里面的门道比我以前用 CentOS 7 时想象的要多不少。网上搜“启动过程”还会蹦出来一堆“启动过程组”那是项目管理里的概念跟操作系统启动完全是两码事别被带偏。这篇文章就把 RHEL9 从按下电源键到出现登录提示符的完整链路捋清楚包括固件、GRUB2、内核、initramfs、systemd 每个阶段做了什么以及我实际排障时确认过的方法。适合刚从 CentOS 7/8 迁移到 RHEL9 的运维也适合第一次系统学习 Linux 启动机制的人。1. 整条启动链路从按下电源到登录提示符1.1 启动四阶段固件、引导、内核、用户空间RHEL9 的启动过程宏观上可以分成四个阶段固件阶段、引导加载程序阶段、内核初始化阶段、用户空间初始化阶段。很多人习惯把“开机启动”理解为 BIOS 界面一闪然后直接进系统实际上中间每一步都有严格的先后顺序任何一个环节出了问题表现都可能不同。固件阶段由主板上的 UEFI 固件或传统 BIOS 完成主要工作是设备自检、初始化硬件、从启动设备读取引导程序。RHEL9 默认推荐 UEFI 模式尤其在开启 Secure Boot 的场景下传统 BIOS 的兼容层 CSM 会带来额外的安全风险和引导复杂性。引导加载程序阶段由 GRUB2 负责。GRUB2 会读取磁盘分区上的配置展示启动菜单再把内核 vmlinuz 和 initramfs 镜像加载到内存。这个阶段是运维最常接触的因为临时修改内核参数、进入救援模式、修复损坏的引导全都发生在这里。内核初始化阶段从 GRUB2 把控制权交给内核开始。内核会解压自身、初始化 CPU 和内存、枚举设备、加载驱动然后使用 initramfs 作为临时根文件系统最终挂载真正的根分区并切换过去。用户空间初始化阶段由 PID 1 进程接管。RHEL9 里的 PID 1 是 systemd它会按照依赖关系启动系统里的各项服务最终把系统带到 multi-user.target 或 graphical.target也就是我们看到的命令行登录或图形界面登录。1.2 RHEL9 比 RHEL7/8 变了哪些东西从我实际使用感受看RHEL9 的启动链路整体框架和 RHEL7/8 没有颠覆性变化还是“GRUB2 systemd initramfs”这套组合但细节改动不少。内核方面RHEL9 基于 5.14 内核分支硬件驱动和文件系统支持比 8 代更全。比如对现代 NVMe、Intel 12 代以上 CPU 的大小核调度、以及 Btrfs 的一些新特性都有更好支持。如果你在 RHEL8 上遇到过新硬件不被识别的问题换到 RHEL9 后大概率会好很多。initramfs 的生成工具还是 dracut但默认参数和行为有调整。RHEL9 对压缩算法和模块包含策略做了优化生成的镜像更精简。不过副作用是某些自定义硬件驱动如果没被识别可能在 initramfs 阶段就失败而不会像以前那样进入系统后还能补救。systemd 的版本和默认配置也变了启动目标、单元依赖关系比 RHEL8 更严格。RHEL9 对 SysV init 脚本的兼容几乎完全放弃了老项目里靠 chkconfig 管理的那种自定义启动脚本迁移过来经常需要改成原生 systemd unit 文件。还有一点容易被忽略RHEL9 默认使用长时间的启动超时策略。有些服务如果启动失败不会再像老版本那样立刻跳出报错而是会等完整个超时时间再放弃。这就是很多人觉得 RHEL9“启动变慢”的一个重要原因不一定是系统真的慢而是等待机制变了。2. 固件阶段与引导加载程序UEFI 和 GRUB2 的约定2.1 UEFI 固件如何找到 GRUB2RHEL9 在 x86_64 架构上默认使用 UEFI 启动。UEFI 固件不像传统 BIOS 那样直接读取硬盘第一个扇区它有自己的启动管理器通过 NVRAM 里的启动条目决定从哪个 EFI 文件启动。正常安装 RHEL9 后系统会在 EFI 系统分区ESP里写入一个引导链。ESP 通常挂载在 /boot/efi里面有一个目录结构类似/boot/efi/EFI/redhat/。这里放着两个关键文件shimx64.efi和grubx64.efi。如果开启了 Secure Boot固件只会加载通过微软签名的 shim再由 shim 去验证和加载 GRUB2。RHEL9 官方支持 Secure Boot这也是企业环境里要求“开箱即安全”时的必经路径。如果你在 BIOS 设置里开启 Secure Boot 后直接重启变成了蓝屏或者提示找不到引导多半是 shim 和 grub 的签名链断了需要从救援模式重新安装。操作系统里的启动条目可以通过efibootmgr查看。我习惯在排查引导问题时先执行下面这条命令确认固件当前从哪个路径启动efibootmgr -v如果输出里有BootCurrent指向一条带EFI\redhat\shimx64.efi的条目说明引导链正常。如果没有或者固件直接跳过了这个条目就需要用安装介质的救援模式修复。2.2 GRUB2 菜单与 BLS 引导项RHEL9 的 GRUB2 配置和以前 CentOS 6 时代的 grub.conf 已经完全不同。/boot/grub2/grub.cfg是由工具生成的正常情况下不要手改因为内核升级或者调整默认启动项后重新生成配置会把你的手工修改覆盖掉。RHEL9 引入了 Boot Loader SpecificationBLS每个内核都有独立的条目文件放在/boot/loader/entries/目录下。比如ll /boot/loader/entries/你会看到类似6.5.0-rc1.el9.x86_64.conf这样的文件。每个文件里记录了该内核的启动参数、root 设备、initramfs 路径等信息。GRUB2 启动时动态扫描这些条目生成菜单。这种设计的最大好处是安装多个内核时GRUB2 菜单会自动更新不需要手动维护。缺点是你想改某个内核的启动参数时如果直接改/etc/default/grub再重新生成可能会影响所有内核反而不够灵活。我比较推荐用grubby管理。先看当前内核的配置grubby --info DEFAULT给所有内核加一个通用内核参数比如关闭大页表或打开某个模块调试开关grubby --update-kernelALL --argsmitigationsoffgrubby会直接修改 BLS 文件里的对应字段比手动编辑安全得多。2.3 临时修改内核启动参数的实操方法有时候你只需要一次性修改内核参数来测试某个功能不用永久保留。GRUB2 菜单界面按e键就能编辑当前启动条目。进入编辑界面后找到以linux开头的那一行。这行结尾通常带有rhgb quiet这两个参数分别代表图形化启动和静默输出。你可以在这行末尾追加参数。比如怀疑某个服务启动卡住就追加上systemd.unitemergency.target或者想让 initramfs 阶段停在命令提示符方便排查根文件系统挂载问题可以加rd.break编辑完成后按Ctrlx或F10启动这次启动就会使用你追加的参数重启后恢复原样。这是一种低风险、可逆的排查方式但如果做了持久化修改就要记得用grubby --query确认参数确实写进去了。注意在 GRUB2 编辑界面里的修改只对本次启动生效千万不要因为临时能启动就忘了回写否则下次重启问题还会重现。3. 内核初始化从压缩内核到根文件系统挂载3.1 内核解压、早期初始化和 initramfs 机制GRUB2 把vmlinuz-*.x86_64和initramfs-*.img两个文件加载到内存后控制权就交给了内核。内核本身是压缩过的首先要完成自我解压然后进入早期初始化流程。早期初始化要做的事情非常多解析 CPU 特性、设置内存分页、初始化中断、识别 ACPI 表、枚举 PCI 设备、加载必要的驱动。这个阶段屏幕输出很少因为你加了rhgb quiet大部分消息都被过滤掉了。如果想看点对点输出可以在 GRUB2 编辑界面把rhgb quiet删掉再看启动日志会发现密密麻麻的硬件初始化信息。但内核不可能把所有设备驱动都编进去因为那会让内核镜像大得离谱而且很多驱动在部署环境里根本用不到。所以 RHEL9 采用 initramfs 方案先加载一个包含必要驱动和工具的小型临时根文件系统让内核可以在内存中启动起来再通过里面的工具挂载真正的根文件系统。initramfs 本质上是一个 cpio 格式的压缩归档里面是完整的、精简的文件系统树。RHEL9 的 initramfs 由 dracut 生成里面除了系统必需的驱动模块外还包含了 udev、lvm、mdadm、cryptsetup 等工具。正是因为有了它RHEL9 才能从容处理根分区在 LVM 逻辑卷、软件 RAID 甚至加密设备上的情况。3.2 为什么 RHEL9 里 initramfs 仍然必不可少有人会问内核不是可以直接挂载根文件系统吗在极简场景下确实可以只要把根文件系统所需的驱动全编进内核并且内核参数里写清楚 root 设备就行。但企业服务器几乎不可能这么干。RHEL9 默认安装就会使用 LVM 来划分根分区。如果你执行过手动分区应该见过类似这样的布局/boot ext4 或 xfs /boot/efi vfat / xfs放在 LVM 逻辑卷里LVM 的 PV、VG、LV 扫描和激活需要 lvm2 工具链mdadm 组 RAID 需要相应软件网络根文件系统需要网络驱动。这些都不在你的主内核里必须通过 initramfs 预先加载。我遇到过一台机械盘服务器根分区本来没有问题但系统升级后重启直接进入 initramfs 的 shell。排查发现是用户手工编译的内核模块没有同步打进 initramfs导致根分区所在的 LVM 卷没被激活。这种情况下在 initramfs 的提示符里手动执行vgchange -ay看到逻辑卷出现基本就能确认是 dracut 配置缺失。重建 initramfs 的标准命令dracut -f /boot/initramfs-$(uname -r).img $(uname -r)-f是强制覆盖后面指定内核版本。如果你刚刚修改过/etc/crypttab或者 LVM 配置一定要重新生成 initramfs否则下一次启动可能又回到这个坑。3.3 从 initramfs 切换到真实根文件系统initramfs 里也有一个 systemd它作为临时 PID 1 运行负责完成根设备发现、多路径识别、加密卷解锁、文件系统检查然后把真正的根文件系统挂载到一个临时目录通常是/sysroot。这个过程是自动的但如果你在 GRUB2 里加了rd.break系统会在挂载真实根前停下来给你一个 shell。这个 shell 非常有用我经常在这里执行lsblk vgscan vgchange -ay xfs_repair -n /dev/vg0/root确认设备都正常后输入exit继续启动。systemd 会完成最后的 switch_root 操作把根目录从 initramfs 切换到/sysroot然后重新执行真正的 systemd。这个“二段启动”的设计绕开了 Linux 早期启动里一个经典死锁内核要先加载根文件系统驱动但驱动文件本身在根文件系统里。initramfs 作为中间层提供了先卸货、再开店的方案逻辑上非常优雅。4. systemd 的接管PID 1 与启动目标解析4.1 systemd 如何决定启动到什么状态切换到真实根文件系统后内核会启动/sbin/init。RHEL9 里/sbin/init是符号链接实际指向 systemd。systemd 启动后干的第一件事就是读取/etc/systemd/default.target这个文件通常是一个符号链接指向multi-user.target或graphical.target。你可以用下面这条命令查看当前默认启动目标systemctl get-default服务器系统默认就是multi-user.target也就是无图形界面的多用户命令行环境桌面工作站则是graphical.target它依赖multi-user.target额外拉起图形界面相关服务。启动目标不是简单的运行级别它是一组单元的集合。default.target会声明依赖关系systemd 会根据Wants、Requires、After等关键字拉出整棵依赖树再并行启动其中没有依赖冲突的所有单元。这就是 RHEL9 启动比老式 SysV init 快得多的核心原因。SysV init 按顺序逐条执行脚本一个脚本卡住后面全堵systemd 则像一张有向无环图能同时推进互不依赖的任务。4.2 单元依赖关系与并行启动理解 systemd 启动过程核心是学会读单元文件里的依赖声明。以 sshd 为例systemctl cat sshd.service会看到Afternetwork-online.target、Wantsnetwork-online.target这样的行。After表示顺序关系Wants表示弱依赖。即使没有严格排序systemd 也会尽量保证网络就绪后再启动 sshd。看整个启动目标的依赖树可以用systemctl list-dependencies multi-user.target这个输出会列出所有将要启动的单元但不会展示并行顺序。想看实际谁先谁后可以用时间戳systemd-analyze输出里会告诉你firmwarexxx、loaderxxx、kernelxxx、initrdxxx、userspacexxx以及总耗时。RHEL9 里这些阶段都是被独立计时的我排查启动慢问题时第一步就是看这个输出先确定瓶颈在哪个阶段。4.3 multi-user、graphical、rescue、emergency 目标systemd 里常见的启动目标对应着系统不同的运行状态multi-user.target标准命令行多用户环境服务器常态。graphical.target多用户加图形界面桌面工作站使用。rescue.target单用户救援模式挂载基本文件系统启动最少服务。emergency.target紧急模式只挂载根文件系统为只读适合修复系统文件。实际运维中如果某个服务导致系统一直进不去可以在 GRUB2 菜单里给内核追加systemd.unitrescue.target先绕开异常服务进入单用户模式。rescue和emergency的区别很多人搞混。rescue.target会把部分文件系统挂载上也启动少量关键服务emergency.target则几乎不挂载任何东西直接给你一个 root shell适合在根文件系统损坏时手动修复。进入系统后你也可以用systemctl isolate切换目标systemctl isolate rescue.targetisolate会停掉当前目标里不需要的依赖切换比较彻底。从救援模式回到正常状态执行systemctl default就可以回到默认启动目标。5. 常见启动故障排查实录5.1 启动慢定位systemd-analyze 三连先说最常遇到的问题系统能起来但启动特别慢。RHEL9 里我几乎每次都靠 systemd-analyze 三连定位。第一连看整体时间消耗systemd-analyze第二连看每个服务启动耗时systemd-analyze blameblame从耗时最长的服务排到最短一眼就能看出是不是某个服务花了 90 秒超时。第三连看关键链路systemd-analyze critical-chain这个命令会从目标单元沿着依赖链往回追溯标出哪条链路耗时占比最高。我遇到过一台机器启动总耗时 180 秒critical-chain显示卡在网络等待上。最后排查发现是网卡固件在等待 DHCP 时反复超时在网卡配置里加了静态 IP 后好处立竿见影。还有更精确的看上一次启动里每个单元的状态和时间systemctl list-units --failed如果某个服务失败会直接列出来。systemctl status 服务名 -l可以看完整日志journalctl -u 服务名可以看该服务的启动记录。5.2 进不了系统救援模式与 emergency 模式怎么选启动过程里卡住进不去系统优先用救援模式而不是重装系统。RHEL9 的安装 U 盘就是一个现成的救援介质启动安装盘后选择“Troubleshooting”再选“Rescue a RHEL system”就能进入一个可以挂载原系统的环境。进入后系统会检测已有的 RHEL 安装问你是否挂载选择1后原系统会被挂到/mnt/sysroot。想真正做修复需要 chroot 进去chroot /mnt/sysroot如果不确定根文件系统状态可以先用 emergency 模式自己挂载。在 GRUB2 菜单里追加systemd.unitemergency.target启动后系统停在 root shell且根文件系统是只读的。先查看分区lsblk确认根分区设备后手动挂载为只读再做文件系统检查mount -o ro /dev/vg0/root /sysroot xfs_repair -n /dev/vg0/rootxfs_repair -n是只检测不修复确认没问题后再去掉-n真正修复。XFS 文件系统必须离线修复挂在只读状态下可以操作但别在系统正常运行期间强行执行。5.3 修复损坏的 GRUB 引导引导损坏的典型症状是开机直接进入固件设置界面或者提示No bootable device found。这种情况往往是 ESP 分区里的引导文件缺失或者 NVRAM 启动条目被清掉了。用安装介质进救援模式chroot 到原系统后先重新生成 GRUB 配置grub2-mkconfig -o /boot/grub2/grub.cfg接着重新安装引导到磁盘。UEFI 模式下的命令grub2-install --targetx86_64-efi --efi-directory/boot/efi需要注意--efi-directory指向的是 ESP 的挂载点不是你整个/boot。如果你不确定当前是 UEFI 还是 BIOS 模式先看/sys/firmware/efi目录是否存在存在就是 UEFI。顺手把内核引导文件也检查一下kernel-install add $(uname -r) /lib/modules/$(uname -r)/vmlinuz有些情况下是内核包损坏可以直接重新安装内核dnf reinstall kernel-core每次执行完引导修复后都建议重启前检查一下 BLS 条目是否正常ls -l /boot/loader/entries/如果条目文件为空GRUB2 菜单自然也就什么都没有。5.4 内核崩溃、服务卡死与驱动问题内核启动到一半直接黑屏或循环重启最常见的原因是显卡驱动、CPU 微码或者新硬件兼容性。这种时候先去掉内核参数里的图形相关设置再追加nomodesetnomodeset会让内核不要主动切换显示模式很多安装阶段的显示问题都能解决。如果怀疑 CPU 微码或者 ACPI 问题可以尝试acpioff但acpioff会让电源管理失效只适合定位问题不适合长期使用。如果是某个服务卡住导致启动超时可以在 GRUB2 临时参数里加systemd.mask服务名比如确认是postfix.service卡住直接屏蔽它继续启动。但要注意这只是定位手段最终还是要找到服务的根本原因。进入系统后如果需要永久禁用某个服务systemctl disable --now 服务名如果服务之间存在循环依赖systemd 会在日志里打印警告。查看journalctl -b -p 3这里-b表示本次启动-p 3表示错误级别。日志里出现的dependency或ordering cycle字样基本都和 systemd 单元依赖写错有关。6. 我的日常工作习惯和一些细节补充6.1 启动管理清单改参数之前先备份踩过几次坑之后我给自己定了一套规矩。任何涉及内核参数或引导文件的操作先备份原始文件cp -a /boot/grub2/grub.cfg /boot/grub2/grub.cfg.bak.$(date %F)使用grubby修改参数后验证一下变更结果grubby --infoALL | grep args修改/etc/fstab或者 LVM 结构后一定重新生成 initramfs。很多启动故障都是因为配置改了但 initramfs 里的旧配置没更新。还有一个特别容易忽略的点RHEL9 默认使用 NetworkManager网络管理相关的服务network.service已经不存在了。老版本里在/etc/rc.local写启动命令的习惯也别再带过来RHEL9 里直接写 systemd unit 文件才是正路。6.2 从 RHEL7/8 迁移的人最需要改掉的习惯如果你之前长期用 CentOS 7RHEL9 的启动排查思路要从“改运行级别”转成“改 target”。/etc/inittab早就名存实亡chkconfig也没必要怀念统一用 systemctl 操作。老版本里常看的/var/log/messages在 RHEL9 里也逐步退出主流官方推荐统一用 journald。虽然 live 环境下journalctl已经能满足大部分排障需求但持久化配置还是建议打开mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal systemctl restart systemd-journald这样日志才能跨重启保存发生启动故障后你才能用journalctl -b -1看上一次启动的日志。如果你还需要排查“上一个启动过程为啥失败”这个命令比任何第三方工具都直接。6.3 给新手上路的一句实话我现在拿到一台新 RHEL9 机器第一件事不是急着部署业务而是先在干净状态下重启一次记录systemd-analyze的基准数据。系统启动基线一旦建立后面任何一次异常重启我都能快速判断是硬件变化、内核更新还是服务依赖出了问题。RHEL9 的启动过程并不神秘它就是一条“固件找引导、引导拉内核、内核起 initramfs、initramfs 交换根、systemd 拉服务”的接力链。把这几个环节的关键命令和排查工具记熟绝大多数启动问题都能在十几分钟内定位到具体阶段。真正麻烦的不是某个环节有多难而是你连系统当前停在哪一步都不知道。从 systemd-analyze、journalctl、grubby 这三件套上手你会发现所谓“启动慢”“启动失败”其实都有非常明确的答案。