有一次我在机房把一台刚装好 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 固件如何找到 GRUB2
RHEL9 在 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 Specification(BLS),每个内核都有独立的条目文件,放在/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-kernel=ALL --args="mitigations=off"grubby会直接修改 BLS 文件里的对应字段,比手动编辑安全得多。
2.3 临时修改内核启动参数的实操方法
有时候你只需要一次性修改内核参数来测试某个功能,不用永久保留。GRUB2 菜单界面按e键就能编辑当前启动条目。
进入编辑界面后,找到以linux开头的那一行。这行结尾通常带有rhgb quiet,这两个参数分别代表图形化启动和静默输出。你可以在这行末尾追加参数。比如怀疑某个服务启动卡住,就追加上:
systemd.unit=emergency.target或者想让 initramfs 阶段停在命令提示符,方便排查根文件系统挂载问题,可以加:
rd.break编辑完成后按Ctrl+x或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会看到After=network-online.target、Wants=network-online.target这样的行。After表示顺序关系,Wants表示弱依赖。即使没有严格排序,systemd 也会尽量保证网络就绪后再启动 sshd。
看整个启动目标的依赖树,可以用:
systemctl list-dependencies multi-user.target这个输出会列出所有将要启动的单元,但不会展示并行顺序。想看实际谁先谁后,可以用时间戳:
systemd-analyze输出里会告诉你firmware=xxx、loader=xxx、kernel=xxx、initrd=xxx、userspace=xxx,以及总耗时。RHEL9 里这些阶段都是被独立计时的,我排查启动慢问题时,第一步就是看这个输出,先确定瓶颈在哪个阶段。
4.3 multi-user、graphical、rescue、emergency 目标
systemd 里常见的启动目标,对应着系统不同的运行状态:
multi-user.target:标准命令行多用户环境,服务器常态。graphical.target:多用户加图形界面,桌面工作站使用。rescue.target:单用户救援模式,挂载基本文件系统,启动最少服务。emergency.target:紧急模式,只挂载根文件系统为只读,适合修复系统文件。
实际运维中,如果某个服务导致系统一直进不去,可以在 GRUB2 菜单里给内核追加systemd.unit=rescue.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.unit=emergency.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 --target=x86_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 问题,可以尝试:
acpi=off但acpi=off会让电源管理失效,只适合定位问题,不适合长期使用。
如果是某个服务卡住导致启动超时,可以在 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 --info=ALL | grep args修改/etc/fstab或者 LVM 结构后,一定重新生成 initramfs。很多启动故障都是因为配置改了,但 initramfs 里的旧配置没更新。
还有一个特别容易忽略的点:RHEL9 默认使用 NetworkManager,网络管理相关的服务network.service已经不存在了。老版本里在/etc/rc.local写启动命令的习惯也别再带过来,RHEL9 里直接写 systemd unit 文件才是正路。
6.2 从 RHEL7/8 迁移的人最需要改掉的习惯
如果你之前长期用 CentOS 7,RHEL9 的启动排查思路要从“改运行级别”转成“改 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 这三件套上手,你会发现所谓“启动慢”“启动失败”,其实都有非常明确的答案。