Linux系统启动过程详解:BIOS、GRUB、内核与Systemd

服务器按开机键到出现登录提示符,中间不过几十秒,但这几十秒里发生的事情,往往决定了它能不能正常起来。很多运维新手遇到"服务器卡在某个界面不动了""开机进了紧急模式""改了个内核参数结果起不来了",第一反应是重装系统,其实只要理解启动链路,绝大多数问题都能在几分钟内定位并修复。

这篇文章把 Linux 从按下电源到服务跑起来的完整路径拆开讲:固件(UEFI 或传统 BIOS)如何找到引导程序,GRUB2 如何加载内核和 initramfs,内核如何挂载根文件系统并把控制权交给 systemd,systemd 又是如何拉起一个个服务的。中间会给出 GRUB 配置、救援模式进入方法,以及用 systemd-analyze 分析启动耗时的实操。看完你应该能读懂启动日志,知道每一步失败时该看哪里、改哪个文件。

启动链路全景

一台 x86 服务器的启动顺序,可以归纳成下面这条链路,任何一环出问题,表现都不一样:

阶段 执行者 主要动作 出问题的典型现象
固件自检 UEFI 或 BIOS 硬件初始化、POST 自检 无显示、蜂鸣、找不到启动设备
引导加载 GRUB2 读取配置,加载内核与 initramfs 出现 grub> 提示符、菜单不见
内核初始化 内核 + initramfs 探测硬件、挂载根文件系统 卡在 Loading initial ramdisk、报找不到根设备
根切换 initramfs 的 init switch_root 到真实根 报 Cannot open root device
系统初始化 systemd(PID 1) 按 target 启动服务 进 emergency/rescue 模式、服务失败
登录 getty 或 sshd 提供登录界面 能 ping 通但没有登录提示符

理解这条链的关键是:每一步都在为下一步准备条件。固件的工作是找到引导程序,GRUB 的工作是找到内核,内核的工作是找到根文件系统,systemd 的工作是按依赖关系启动服务。排查时只要知道现象落在哪一段,就缩小了范围。

固件:UEFI 与传统 BIOS

固件是主板上电后运行的第一段程序。传统 BIOS 和现代的 UEFI 差别很大:

  • 传统 BIOS(Legacy) 从磁盘第一个扇区读取 512 字节的 MBR,再跳转到 GRUB 的第一阶段,容量极其受限,于是有了多阶段引导的设计。分区表最多识别 2TB,且只能用 MBR。
  • UEFI 从 EFI 系统分区(ESP,通常是 FAT32 格式,挂在 /boot/efi)读取 .efi 可执行文件,典型路径是 /boot/efi/EFI/centos/grubx64.efi、/boot/efi/EFI/rocky/grubx64.efi 或 /boot/efi/EFI/ubuntu/grubx64.efi。UEFI 支持 GPT 分区表(突破 2TB 限制),启动过程更标准。

判断当前机器用的是哪种模式,最简单的一条命令:

ls /sys/firmware/efi
# 目录存在 => UEFI 启动
# 提示 No such file or directory => Legacy BIOS 启动

也可以查看是否挂载了 ESP:

mount | grep -i efi
df -hT /boot/efi

为什么要在意这个:GRUB 的安装命令在两种模式下完全不同。UEFI 模式下,重装引导要用 grub2-install --target=x86_64-efi(Debian 系为 grub-install --target=x86_64-efi);Legacy 模式下则针对具体的磁盘设备如 /dev/sda。装错目标会导致机器开不了机,所以动引导之前务必先确认模式。

固件阶段的常见故障:设置了错误的启动顺序导致找不到系统盘;UEFI 安全启动(Secure Boot)把未签名的第三方驱动拦下;CMOS 电池没电导致 BIOS 设置丢失。这类问题的处理入口在服务器的带外管理(IPMI、iDRAC、iLO)里,可以远程进入固件设置界面调整。

GRUB2:引导加载程序

GRUB2 是现在绝大多数发行版默认的引导程序。它要完成三件事:显示启动菜单、加载内核(vmlinuz)和初始内存盘(initramfs 或 initrd)、把内核命令行参数传给内核。

关键配置文件位置

这里有个新手最容易踩的坑:GRUB 的主配置 /boot/grub2/grub.cfg 是自动生成的,不要手改,因为它会被覆盖。要改的是这些源文件:

# RHEL / CentOS / Rocky:全局默认参数
/etc/default/grub

# RHEL / CentOS / Rocky:菜单项与内核相关的定制脚本
/etc/grub.d/40_custom

# Debian / Ubuntu:同样是这两个位置
/etc/default/grub
/etc/grub.d/40_custom

/etc/default/grub 里最常改的两行:

GRUB_TIMEOUT=5
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=vg0/root rd.lvm.lv=vg0/swap rhgb quiet"
  • GRUB_TIMEOUT 是菜单停留秒数。调试时改成 10 或 30,方便手动选择。
  • GRUB_CMDLINE_LINUX 是会传给内核的参数,比如 console=ttyS0 开启串口输出(物理机远程调试很有用)、rd.lvm.lv= 指定 LVM 根卷、quiet 减少内核打印。

修改后的生效命令

改完 /etc/default/grub 后,必须重新生成 grub.cfg,两个发行版系命令不同:

# RHEL / CentOS / Rocky(BIOS 与 UEFI 通用)
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

# RHEL / CentOS / Rocky,UEFI 环境通常也会生成到 EFI 分区
sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

# Debian / Ubuntu
sudo update-grub
# 等价于:sudo grub-mkconfig -o /boot/grub/grub.cfg

风险提示:重新生成 grub.cfg 前,建议先备份 /boot/grub2/grub.cfg(或 /boot/grub/grub.cfg)。如果生成过程报错而原文件已被覆盖,机器可能无法启动。备份命令:

sudo cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg.bak

进入 GRUB 命令行与编辑模式

开机时在菜单界面按 e 可以临时编辑启动项,按 c 进入 GRUB 命令行。这些改动只在本次启动生效,不会写盘,是排查问题的安全手段。比如忘记了 root 密码,就可以在启动项的内核行末尾加上 rd.break(RHEL 系)或 init=/bin/bash(通用),进系统后重置密码。

内核与 initramfs

GRUB 把内核(/boot/vmlinuz-*)和 initramfs(/boot/initramfs-*.img 或 /boot/initrd.img-*)加载进内存后,内核开始执行。它要做的最关键一步是挂载根文件系统。

问题在于:根文件系统可能位于 LVM 逻辑卷、LUKS 加密卷、软 RAID 或网络存储上,内核本身并不包含这些复杂存储所需的所有驱动。为了不把内核做得臃肿,Linux 引入了 initramfs——一个包含了必要驱动和工具的小型内存文件系统,先被内核加载,再由它来把真正的根挂起来,最后通过 switch_root 切换到真实根。

# 查看当前使用的内核
uname -r

# 查看 initramfs 与内核文件
ls -lh /boot/vmlinuz-* /boot/initramfs-*

# 重新生成 initramfs(硬件或存储配置变化后常用)
# RHEL / CentOS / Rocky
sudo dracut -f

# Debian / Ubuntu
sudo update-initramfs -u -k all

什么时候必须重建 initramfs:更换了存储控制器、调整了 LVM 结构、给根卷加了加密、或者升级内核之后。如果 initramfs 里缺少新硬件的驱动,内核就会报 Cannot open root device 或 Unable to mount root fs,然后掉进 initramfs 的救援 shell(通常是 (initramfs) 提示符)。

内核启动早期的日志不会写入磁盘,而是输出到控制台和环形缓冲区。排查启动早期问题可以用:

dmesg | head -n 60
journalctl -k -b          # 本次启动的内核日志
journalctl -k -b -1       # 上一次启动的内核日志

-b 参数指定启动次数,-b -1 表示上一次启动,这对分析"上次为什么没起来"特别有用。

systemd:PID 1 与 target

内核挂载根文件系统后,会启动第一个用户态进程,也就是 PID 1。在几乎所有现代发行版上,这个进程就是 systemd。你可以验证:

ps -p 1 -o comm=
# 输出 systemd

systemd 的启动模型和传统的 SysV init 完全不同。传统 init 是按顺序一条条执行 rc 脚本,慢且难以并行;systemd 用单元(unit)来描述资源,用依赖关系来决定启动顺序,能并行的尽量并行。

systemd 用 target 来表示"系统的状态"。传统运行级别和 target 的对应关系如下:

传统运行级 对应 target 含义
0 poweroff.target 关机
1 rescue.target 单用户救援模式
3 multi-user.target 多用户、命令行、无图形
5 graphical.target 多用户、带图形界面
6 reboot.target 重启

服务器最常见的目标是 multi-user.target。查看和切换:

systemctl get-default                     # 查看默认 target
sudo systemctl set-default multi-user.target   # 设为命令行启动
systemctl list-units --type=target        # 列出当前激活的 target

系统从 default.target 出发,根据依赖把需要的服务一个个拉起。想直观看到这棵依赖树:

systemctl list-dependencies multi-user.target | head -n 40
systemd-analyze critical-chain            # 只看关键路径

启动耗时分析:systemd-analyze

这是 systemd 相比传统 init 最实用的诊断能力之一。三条命令就能把启动过程量化:

systemd-analyze
# Startup finished in 2.318s (firmware) + 3.912s (loader) + 4.021s (kernel) + 21.766s (userspace) = 32.018s

输出把时间拆成了固件、引导加载、内核、用户空间四段。如果用户空间占了大头,就往下看服务级耗时:

systemd-analyze blame | head -n 20

blame 按耗时从高到低列出每个服务的初始化时间。常见耗时大户有 NetworkManager-wait-online.service(等待网络就绪,开了 DHCP 的机器可能等几十秒)、plymouth-quit-wait.service(等待开机动画)、cloud-init 相关服务等。

systemd-analyze critical-chain

critical-chain 显示的是关键路径——真正决定总耗时的那条依赖链,用 @ 标出每个服务的激活时刻。如果某个服务很耗时但不在关键路径上,说明它和其他服务并行执行了,不影响总时间,优化它是白费功夫。想看图形化瀑布图还可以生成 SVG:

systemd-analyze plot > boot.svg

救援模式:进不去系统怎么办

当根文件系统被改坏、GRUB 配置错误、fstab 写错导致挂载失败时,系统会掉进 emergency 或 rescue 模式,或者干脆卡住。几种进入救援的方式:

  1. GRUB 菜单加 rd.break(RHEL 系):在菜单里按 e 编辑启动项,在内核行末尾加 rd.break,按 Ctrl+X 启动,会进入 initramfs 的 shell,此时真实根挂载在 /sysroot。
  2. 加单用户参数:在内核行末尾加 single 或 1,进入 rescue.target。
  3. 加 systemd.unit=emergency.target:进入最小化模式,只挂载根文件系统为只读。

以 RHEL 系重置 root 密码为例(物理或带外控制台操作):

# 1. GRUB 菜单按 e,内核行末尾加 rd.break,Ctrl+X 启动
# 2. 进入 initramfs shell 后
mount -o remount,rw /sysroot
chroot /sysroot
passwd root
touch /.autorelabel      # 若开启了 SELinux,必须执行,否则改密后登录不了
exit
exit                     # 或 reboot

注意 touch /.autorelabel:开启了 SELinux 的系统,改完密码后文件上下文可能不对,重启时让系统重新打标签,否则会出现密码正确却登不进去的怪事。

如果连 GRUB 菜单都进不去(比如 grub.cfg 损坏),可以用发行版安装盘启动,选择"救援模式"(Rescue a CentOS/Rocky Linux System),挂载现有系统后 chroot 进去修复。

# 在救援 shell 里
mkdir -p /mnt/sysroot
mount /dev/mapper/vg0-root /mnt/sysroot
mount /dev/sda1 /mnt/sysroot/boot        # 视分区而定
chroot /mnt/sysroot
# 在此修复 grub、fstab 等

常见问题

修改 grub 后重启进了 grub> 提示符,怎么救?

这说明 GRUB 找不到 grub.cfg 或根分区。先别慌,在 grub> 里手动引导、确认设备,再用救援盘启动进 chroot,重新执行 grub2-mkconfig 和 grub2-install。所以改引导配置前一定要备份,并且最好留一个能进救援的带外通道。

改了 /etc/fstab 重启后卡在 emergency 模式?

多半是 fstab 里的 UUID 或设备名写错、或者被挂载的分区不存在。救援模式下以读写方式重挂根分区(mount -o remount,rw /),修正 fstab。建议在 fstab 中给非关键挂载加 nofail 选项,避免一个挂载失败拖垮整个启动。

systemd-analyze blame 里某项特别慢,能直接禁掉吗?

要先确认它是否在 critical-chain 的关键路径上。不在关键路径上的耗时项禁掉不会加速启动;在关键路径上的服务要搞清它的作用再决定,盲目 systemctl disable 可能导致网络、存储等基础设施起不来。

内核升级后多了几个启动项,怎么清理?

旧内核会占用 /boot 空间,满了会导致新内核装不进、更新失败。RHEL 系可用 package-cleanup --oldkernels --count=2(需 yum-utils)保留最近两个;Debian 系用 apt autoremove 清理旧内核。清理前确认当前内核版本,别把正在用的删了。

UEFI 和 Legacy 能在同一台机器上混用吗?

可以,但每个磁盘只能选一种模式引导,混用容易乱。安装系统时就要定好模式,之后的救援盘、GRUB 安装命令都要对应。判断方法前面给过:ls /sys/firmware/efi。

总结

  • 启动链路是"固件找引导 → 引导找内核 → 内核找根 → systemd 拉服务",排查时先按现象定位到哪一段,再看对应的日志。
  • 改 GRUB 要改 /etc/default/grub 这类源文件,再用 grub2-mkconfig -o ...(RHEL 系)或 update-grub(Debian 系)生成,动之前先备份 grub.cfg。
  • 存储结构或内核变化后要重建 initramfs,否则可能报找不到根设备;dmesg 和 journalctl -k -b -1 是排查内核期问题的关键工具。
  • 用 systemd-analyze、blame、critical-chain 量化启动耗时,只优化关键路径上的服务才有效。
  • 进不去系统时优先用 rd.break、single 或安装盘救援模式修复;改完密码记得处理 SELinux 标签,fstab 里的非关键挂载加 nofail 防拖垮启动。