Linux 基础体系 · 第 3/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 启动完整流程:Firmware、Bootloader、Kernel、initramfs 与 systemd
Linux 启动不是一个单独程序完成的动作,而是一条逐级交接控制权、逐步增加硬件和用户态能力的链:
flowchart TD
A[上电或复位] --> B[Firmware<br/>BIOS 或 UEFI]
B --> C[Bootloader<br/>GRUB、systemd-boot 或 UEFI Stub]
C --> D[加载 Kernel、initramfs、内核命令行]
D --> E[Kernel 解压、初始化 CPU/内存/设备]
E --> F[initramfs 中的 /init]
F --> G[发现并挂载真实根文件系统]
G --> H[switch_root 或 pivot_root]
H --> I[真实根文件系统中的 /sbin/init]
I --> J[systemd PID 1]
J --> K[挂载、设备、网络、服务、登录界面]
这条链并不要求每台机器严格使用相同组件。例如:
- 固件可以直接加载 Linux 内核,而不经过 GRUB。
- Bootloader 可以是 GRUB、systemd-boot、U-Boot,也可以是 Linux 内核的 EFI stub。
initramfs可以由 dracut、mkinitcpio、initramfs-tools 等工具生成。- PID 1 可以是 systemd,也可以是 OpenRC、BusyBox init 或其他实现。
- systemd 还可以在 initramfs 阶段运行,但这不等于它已经成为真实根文件系统中的最终 PID 1。
因此,理解启动流程时,应该区分两件事:
- 规范或接口保证:例如 UEFI 如何加载 EFI 程序、Linux 如何接收内核命令行。
- 主流实现和发行版习惯:例如 Ubuntu 常见 GRUB、Debian 常见 initramfs-tools、Fedora 常见 dracut 和 systemd。
一、先建立四个边界:Firmware、Bootloader、Kernel 和用户态
1. Firmware:硬件上电后的第一段软件
Firmware 是保存在主板、设备或非易失性存储中的固件。PC 上通常指:
- 传统 BIOS;
- 现代 UEFI;
- 某些服务器上的 BMC、Option ROM 等配套固件。
CPU 复位后,不会直接执行磁盘上的 Linux。它首先从预先定义的固件入口开始执行。Firmware 负责完成最早期的环境建立,例如:
- 设置 CPU 的基本执行环境;
- 初始化一部分内存控制器和总线;
- 发现存储设备、键盘、显示设备或网络设备;
- 按启动顺序寻找可启动介质;
- 根据启动模式加载下一阶段程序。
Firmware 通常不负责完整加载 Linux 用户态,也不负责启动 systemd。它的职责是把机器从“刚复位、硬件尚未可用”的状态推进到“可以加载一个启动程序”的状态。
2. Bootloader:选择并加载内核
Bootloader 的核心职责是把内核启动所需的数据放到内存中,并把控制权交给内核。典型数据包括:
- Linux 内核映像;
initramfs或initrd;- Linux 内核命令行;
- 在某些架构上提供的设备树;
- 启动参数和固件接口信息。
GRUB 还可以提供启动菜单、多个内核版本选择、恢复模式入口和命令行编辑能力。systemd-boot 则更简单,通常从 EFI System Partition 中读取内核和 initramfs。某些内核启用了 EFI stub 后,UEFI 可以把内核视为 EFI 可执行文件直接启动。
Bootloader 一般不会把所有硬件初始化都重新做一遍。它主要依赖 Firmware 已经提供的启动环境,然后完成内核加载和参数传递。
3. Kernel:从硬件控制权进入内核管理模型
Linux Kernel 接管控制权后,开始建立自己的抽象和资源管理机制,包括:
- CPU 和调度器;
- 页表、虚拟内存和内存分配器;
- 中断控制器;
- 设备模型和驱动;
- VFS 与文件系统;
- 网络协议栈;
- 内核线程;
- 用户态进程执行环境。
内核不是“启动完就退出”的加载器。它会持续运行,处理系统调用、异常、中断、调度和设备事件。用户态程序通过系统调用进入内核,内核再通过驱动和硬件交互。这也是后续 systemd 能够执行挂载、创建进程、管理设备和服务的基础。
4. initramfs:内核启动早期的临时用户态
initramfs 是一个通常以压缩 cpio 归档形式保存的临时根文件系统。它不是一个特殊的内核模块,也不是一个完整磁盘分区,而是一组在内核早期阶段可用的用户态文件,例如:
/bin/sh
/bin/mount
/sbin/cryptsetup
/usr/lib/systemd/systemd
/lib/modules/<kernel-version>/
/etc/
/init
其中 /init 是关键入口。内核完成早期初始化后,通常会执行 initramfs 中的 /init。这个程序负责解决“真实根文件系统还不能直接访问”的问题,例如:
- 加载存储控制器驱动;
- 等待磁盘或网络块设备出现;
- 组装 RAID;
- 激活 LVM;
- 解锁 LUKS 加密卷;
- 挂载
/usr或真实根文件系统; - 应用内核命令行中的根设备参数;
- 最后切换到真实根文件系统。
5. systemd:真实用户态中的第一个主要进程
在使用 systemd 的发行版中,真实根文件系统通常包含:
/sbin/init -> /lib/systemd/systemd
内核执行的并不是“systemd 这个名字”,而是内核命令行指定的 init=,或者默认寻找的 /sbin/init。如果 /sbin/init 是 systemd,那么 systemd 就成为 PID 1:
$ ps -p 1 -o pid,comm,args
PID COMMAND COMMAND
1 systemd /sbin/init
systemd 是用户态程序,不是内核的一部分。它负责:
- 作为 PID 1 创建和管理服务进程;
- 回收孤儿进程;
- 管理挂载点、设备、socket、定时器和服务;
- 根据依赖关系并行启动任务;
- 接收来自内核和用户态的状态信息;
- 提供日志、登录、会话和电源管理等配套能力。
二、Firmware 阶段:BIOS 与 UEFI 的两条路径
1. BIOS 启动路径
传统 BIOS 启动时,常见过程如下:
- CPU 从固件入口开始执行。
- BIOS 执行 POST 和基本硬件初始化。
- BIOS 按启动顺序寻找设备。
- BIOS 读取启动设备的第一个扇区,通常是主引导记录 MBR。
- BIOS 把其中的启动代码放入内存并跳转执行。
- MBR 中的短代码继续加载更完整的 Bootloader,例如 GRUB 的后续部分。
- Bootloader 加载 Linux 内核和 initramfs。
早期 MBR 空间很小,不能容纳完整 GRUB。因此 GRUB 在 BIOS 模式下常把不同阶段分散到:
- MBR 中的第一阶段代码;
- 磁盘间隙或 BIOS Boot Partition 中的后续代码;
- 文件系统中的 GRUB 模块和配置。
GPT 磁盘通常还会存在一个保护性 MBR,用于避免旧工具误认为磁盘未分区。BIOS + GPT 场景下,GRUB 常需要单独的 BIOS Boot Partition 来存放其嵌入代码。
2. UEFI 启动路径
UEFI 不以“读取磁盘第一个扇区并执行”作为主要接口,而是能够读取 FAT 文件系统中的 EFI 可执行文件。常见过程是:
- UEFI 固件初始化硬件。
- 固件读取 NVRAM 中的 Boot Entry。
- Boot Entry 指向 EFI System Partition,简称 ESP。
- 固件从 ESP 加载一个
.efi文件。 - 该 EFI 程序继续加载 Linux 内核和 initramfs,或者直接加载内核。
ESP 通常是一个较小的 FAT 分区,常见挂载位置为 /boot/efi。它可能包含:
/boot/efi/EFI/
├── BOOT/
│ └── BOOTX64.EFI
├── fedora/
│ └── shimx64.efi
├── ubuntu/
│ ├── shimx64.efi
│ └── grubx64.efi
└── systemd/
└── systemd-bootx64.efi
实际目录名取决于发行版、架构和安装方式。UEFI 的默认回退路径、NVRAM 启动项和 Secure Boot 策略会共同决定最终加载哪个 EFI 程序。
3. Secure Boot 改变的是信任链,不是启动阶段顺序
Secure Boot 通常要求固件只执行签名可信的 EFI 程序。常见链条是:
UEFI 固件
-> 已签名的 shim
-> 已签名或受信任的 GRUB
-> 已验证的 Linux 内核
-> initramfs 和用户态
但具体链条因发行版和配置而异。Secure Boot 的核心是代码签名验证和信任根,不是“系统一定使用 GRUB”。
常见误解是:Secure Boot 会自动阻止所有未签名文件。实际情况更复杂:
- 固件通常验证它直接加载的 EFI 程序;
- Bootloader 可能继续验证内核;
- 内核可能启用模块签名校验;
- initramfs 中的脚本和普通用户态文件是否被单独验证,取决于启动链设计和系统配置。
因此,Secure Boot 启动失败时,应分别检查 EFI 程序、内核、内核模块和 initramfs,而不是只检查“磁盘是否能读”。
4. 用命令确认当前启动模式和 UEFI 条目
在已经启动的 Linux 中:
$ test -d /sys/firmware/efi && echo UEFI || echo BIOS
UEFI
/sys/firmware/efi 存在通常说明当前系统通过 UEFI 启动。它不是判断主板是否支持 UEFI,而是判断这一次启动是否使用了 UEFI。
查看 UEFI 启动项:
$ sudo efibootmgr -v
BootCurrent: 0003
Timeout: 1 seconds
BootOrder: 0003,0001,0000
Boot0003* ubuntu HD(...)File(\EFI\UBUNTU\SHIMX64.EFI)
该命令会读取并可能访问 EFI 变量。读取通常风险较低,但修改 BootOrder、删除启动项可能导致系统无法启动;生产机器上不应未经验证直接执行修改操作。
三、Bootloader 阶段:内核、initramfs 和命令行如何汇合
1. Bootloader 需要准备什么
以内核能够启动为条件,Bootloader 至少需要让内核获得:
- 内核映像;
- initramfs 映像,若启动流程需要它;
- 内核命令行;
- 架构相关的启动信息。
典型 GRUB 配置项类似:
linux /vmlinuz-6.8.0 root=UUID=... ro quiet
initrd /initramfs-6.8.0.img
这里:
linux行指定内核;initrd行指定 initramfs;root=UUID=...是传给内核的命令行参数;ro表示初始以只读方式挂载根文件系统;quiet减少控制台输出,但不会改变核心启动逻辑。
不同发行版的文件名和配置生成方式不同。GRUB 的实际配置可能由 grub-mkconfig、发行版封装脚本或安装器生成,不应直接假定所有系统都使用相同路径。
2. 内核命令行是启动阶段之间的重要数据接口
内核命令行通常可以通过以下方式查看:
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.8.0 root=UUID=... ro quiet splash
它不是只给内核本身使用。启动链中的多个组件都可能读取它:
- 内核读取
root=,ro,console=,panic=等参数; - initramfs 脚本读取存储、加密、网络和调试参数;
- systemd 读取
systemd.unit=,systemd.log_level=等参数。
例如:
root=UUID=xxxx
rd.luks.uuid=luks-yyyy
rd.lvm.lv=vg0/root
systemd.unit=rescue.target
其中 rd.* 参数通常是 initramfs 工具约定的参数,不是 Linux 内核统一规范的一部分。dracut 和其他 initramfs 实现支持的参数可能不同,必须以目标发行版和版本的文档、生成内容为准。
root= 也有边界:它只描述根设备或根文件系统位置,不能自动完成 LUKS 解密、LVM 激活、网络启动或驱动加载。若根设备被加密或位于 LVM 中,initramfs 还必须包含对应工具、配置和驱动。
3. 内核映像并不总是“可直接执行文件”
常见 /boot/vmlinuz-* 通常是经过压缩的 Linux 内核映像。Bootloader 将其加载后,内核早期代码会完成自解压,再进入架构相关入口和通用初始化路径。
逻辑上可以表示为:
压缩内核映像
-> 内核自解压
-> 架构入口
-> start_kernel()
-> rest_init()
-> 内核线程和第一个用户态进程
函数名称属于 Linux 内核实现细节,可能随版本和架构变化,但它们表达了一个稳定的因果关系:内核先建立自身运行所需的环境,再创建执行用户态 /init 的路径。
四、Kernel 早期初始化:从 CPU 控制权到 /init
1. 内核首先建立自己的运行环境
内核早期阶段需要完成多类初始化:
- 解析启动参数;
- 建立页表和内存管理;
- 初始化中断和时钟;
- 初始化调度器;
- 建立 slab 等内核内存分配机制;
- 注册总线和设备模型;
- 初始化 VFS、网络等子系统;
- 加载编译进内核的驱动;
- 准备 rootfs 和 initramfs。
驱动有两种主要形态:
- 内建驱动:直接编译进内核映像;
- 模块驱动:以
.ko文件存在,需要在运行时由modprobe等工具加载。
如果根磁盘控制器驱动、文件系统驱动或加密设备依赖的驱动只是模块,而 initramfs 中没有这些模块,内核即使已经启动,也无法访问真实根文件系统。
2. rootfs 与真实根文件系统不是同一个概念
内核启动后会先拥有一个最小的根目录环境,通常称为 rootfs。它由内核提供,用来承载早期的 /init 和 initramfs 内容。
此时的 / 不是磁盘上的真实根分区。例如:
早期:
/
├── init
├── bin/
├── etc/
└── lib/
切换后:
/
├── sbin/init
├── etc/
├── var/
├── usr/
└── home/
initramfs 的内容被展开到早期 rootfs 中。内核随后尝试执行:
/init
若 /init 不存在、不可执行,或者执行过程中失败,常见错误包括:
Kernel panic - not syncing: Attempted to kill init!
或者:
No working init found. Try passing init= option to kernel.
这类错误与“systemd 服务启动失败”不是同一层问题:此时系统甚至可能还没有进入真实根文件系统中的 systemd。
3. /init 是用户态程序,不是内核函数
内核通过类似 execve 的机制执行 initramfs 中的 /init。这个 /init 可以是:
- shell 脚本;
- BusyBox 提供的程序;
- dracut 的初始化程序;
- initramfs 中的 systemd;
- 其他自定义实现。
因此,initramfs 阶段已经是用户态,但它仍然是一个极简、临时、面向启动的用户态环境。它通常没有完整的服务集合,也没有最终系统的 /var、用户账户和应用程序。
五、initramfs 如何找到并切换到真实根文件系统
1. 为什么必须有 initramfs
考虑一个根文件系统位于以下设备上:
/dev/mapper/vg0-root
但这个设备的生成路径是:
SATA/NVMe 控制器
-> 分区
-> LUKS 解密
-> LVM PV
-> VG 激活
-> LV root
-> ext4/XFS 文件系统
内核若没有相应驱动和用户态工具,不能凭空得到 /dev/mapper/vg0-root。initramfs 就是用来执行这条依赖链的临时系统。
一个典型数据流如下:
sequenceDiagram
participant K as Kernel
participant I as initramfs /init
participant D as 设备与驱动
participant C as cryptsetup
participant L as LVM
participant R as 真实根文件系统
participant P as 最终 PID 1
K->>I: 执行 /init
I->>D: 加载模块、等待设备
D-->>I: 出现磁盘和分区
I->>C: 解锁 LUKS
C-->>I: 创建 /dev/mapper/cryptroot
I->>L: 扫描并激活 VG/LV
L-->>I: 出现 /dev/mapper/vg0-root
I->>R: 挂载真实根文件系统
I->>P: switch_root 后执行 /sbin/init
P-->>P: systemd 读取 unit 并启动系统
并非所有机器都经过全部步骤。普通未加密单分区系统可能只需要加载磁盘和文件系统驱动,然后直接挂载根分区。
2. initramfs 镜像包含什么
可以用发行版提供的工具查看内容。Fedora、RHEL 系常见:
$ lsinitrd /boot/initramfs-$(uname -r).img | less
Debian、Ubuntu 上常见:
$ lsinitramfs /boot/initrd.img-$(uname -r) | less
输出中通常能看到:
usr/lib/modules/6.x.y/
usr/bin/cryptsetup
usr/sbin/lvm
etc/crypttab
etc/fstab
init
实际路径会因 initramfs 工具版本和是否启用 usr-merge 而变化。查看的目的不是记住固定目录,而是确认以下事实:
- 当前内核版本对应的驱动是否存在;
cryptsetup、LVM、RAID 等工具是否被打包;/init是否存在;- 必要的配置文件和 udev 规则是否被包含。
3. switch_root 和 pivot_root 的关系
initramfs 挂载真实根文件系统后,需要将它变成新的 /,再执行真实系统的 init。
常见过程是:
- 将真实根文件系统挂载到临时目录,例如
/sysroot; - 将必要的虚拟文件系统准备好;
- 停止或迁移 initramfs 中仍在运行的进程;
- 使用
switch_root或相关机制切换根目录; - 执行新的
/sbin/init。
pivot_root 是内核提供的根文件系统切换系统调用;switch_root 通常是用户态工具,用于更适合 initramfs 场景的切换流程。两者不应简单视为同义命令。现代 initramfs 实现一般封装了具体细节。
切换完成后,早期 initramfs 的 /init 不再是系统的最终 init。真实根文件系统中的 systemd 才成为 PID 1。
六、systemd 阶段:PID 1 如何把静态文件变成运行系统
1. systemd 读取的不是一个“启动脚本”,而是一组 unit
systemd 使用 unit 描述系统资源和动作。常见 unit 类型包括:
| 类型 | 含义 |
|---|---|
.service |
一个长期运行或一次性完成的进程 |
.target |
一组 unit 的逻辑集合 |
.mount |
一个挂载点 |
.device |
一个内核设备出现的状态 |
.socket |
socket 激活对象 |
.timer |
定时触发器 |
.automount |
自动挂载点 |
.path |
路径变化触发器 |
例如一个服务:
[Unit]
Description=Example Worker
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/sbin/example-worker
Restart=on-failure
[Install]
WantedBy=multi-user.target
这里有三个不同概念:
After=:只规定顺序,不会自动拉起对方;Wants=:建立较弱的拉起关系;Requires=:建立更强的依赖关系,但不等同于顺序;WantedBy=:主要用于systemctl enable时生成依赖链接。
一个常见错误是把 After=network-online.target 理解成“网络一定可用”。它只表达 systemd job 的排序关系;网络管理器是否真的等待地址、路由和上游连通性,取决于具体网络服务和配置。
2. systemd 的启动目标
systemd 通常通过默认 target 进入系统状态:
$ systemctl get-default
graphical.target
常见 target 包括:
emergency.target:尽可能少的环境,用于极端修复;rescue.target:单用户救援环境;multi-user.target:多用户文本模式;graphical.target:在多用户基础上增加图形登录等组件。
可以查看默认目标的实际指向:
$ systemctl list-dependencies graphical.target
graphical.target
● ├─multi-user.target
● └─display-manager.service
这不是固定输出。不同发行版、桌面环境和安装软件会产生不同依赖。
3. systemd 如何处理并发启动
systemd 会把启动请求转换为一组 job,并依据依赖与顺序约束安排执行。若两个服务之间没有顺序依赖,它们可能并行启动。
例如:
A Requires=B
A After=B
C 与 A、B 无依赖
可以推出:
B 必须先于 A 完成启动顺序;
C 可以与 A 或 B 并行;
但 C 若实际需要网络或文件系统,必须显式声明相应依赖。
因此,服务启动顺序不是 unit 文件在磁盘上的排列顺序,也不是服务名称的字典序。启动图由依赖关系决定。
查看实际启动耗时:
$ systemd-analyze
Startup finished in 4.231s (firmware) + 1.102s (loader) + 2.874s (kernel) + 5.410s (userspace) = 13.617s
graphical.target reached after 5.392s in userspace
这些时间由 systemd 根据当前启动记录计算。它们不是性能保证,也不能简单相加来推断每个服务的串行耗时,因为很多任务是并行执行的。
进一步查看关键路径:
$ systemd-analyze critical-chain
graphical.target @5.392s
└─multi-user.target @5.391s
└─example.service @4.800s +500ms
└─network-online.target @4.700s
critical-chain 展示的是影响某个目标到达时间的一条关键依赖链,不代表所有启动任务,也不表示其他服务没有运行。
4. PID 1 的特殊职责
PID 1 不只是“第一个普通进程”。它还承担:
- 孤儿进程重新归属;
- 回收退出子进程,避免僵尸进程积累;
- 处理系统关机、重启和进入特定 target;
- 维护服务状态;
- 根据 unit 规则重新启动失败服务。
可以查看 systemd 的基本状态:
$ systemctl is-system-running
running
可能的状态包括:
initializing:仍在初始化;starting:正在启动;running:系统达到运行状态;degraded:有失败 unit,但 systemd 本身运行;maintenance:进入救援或紧急维护状态;offline:未能正常确认运行状态。
degraded 不等于“系统完全不可用”。它表示至少有 unit 失败,需要进一步查看:
$ systemctl --failed
$ systemctl status <unit>
$ journalctl -b -u <unit>
七、完整启动时序中的关键状态
可以把典型现代 Linux 启动抽象成以下状态转换:
S0: CPU 复位
条件:Firmware 入口可执行
S1: Firmware 完成基本初始化
条件:找到可启动 EFI 程序或 BIOS 启动代码
S2: Bootloader 运行
条件:内核映像、命令行和 initramfs 可读取
S3: Kernel 早期初始化
条件:内存、调度、中断和基本设备模型可用
S4: initramfs 用户态
条件:/init 存在且可执行
S5: 真实根文件系统可访问
条件:根设备出现、必要驱动和文件系统可用
S6: 最终 PID 1 运行
条件:真实根中的 /sbin/init 可执行
S7: systemd 启动目标
条件:目标依赖中的必要 unit 成功或被允许失败
S8: 系统可用
条件:达到预期 target,登录、网络或应用服务按需求就绪
每个状态的失败都对应不同证据来源:
| 阶段 | 常见失败 | 主要证据 |
|---|---|---|
| Firmware | 找不到启动盘、Secure Boot 拒绝 | 固件界面、UEFI 日志、启动项 |
| Bootloader | GRUB 命令行、找不到内核 | GRUB 屏幕、ESP、efibootmgr |
| Kernel | panic、驱动缺失、内存初始化失败 | 控制台、dmesg、串口日志 |
| initramfs | 找不到根设备、LUKS/LVM 失败 | initramfs 控制台、内核命令行 |
| switch_root | 真实根挂载失败、/sbin/init 缺失 |
initramfs 日志、挂载检查 |
| systemd | 服务失败、依赖失败、目标未达到 | journalctl -b、systemctl |
八、一个端到端的检查示例
下面的命令用于检查当前系统“实际使用了什么”,而不是猜测发行版默认配置。
1. 确认内核、命令行和 PID 1
$ uname -r
6.8.0-xx-generic
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.8.0 root=UUID=... ro quiet splash
$ readlink -f /sbin/init
/usr/lib/systemd/systemd
$ ps -p 1 -o pid,comm,args
PID COMMAND COMMAND
1 systemd /sbin/init
这些结果分别说明:
- 当前运行内核版本;
- Bootloader 传递的命令行;
/sbin/init实际指向的程序;- PID 1 是否由 systemd 承担。
如果 readlink -f /sbin/init 不是 systemd,不能继续假设后面的行为符合 systemd 模型。
2. 确认根文件系统和挂载关系
$ findmnt /
TARGET SOURCE FSTYPE OPTIONS
/ /dev/mapper/vg0-root xfs rw,relatime
这个结果说明真实根文件系统来自 LVM 映射设备,文件系统类型为 XFS。它暗示启动时至少可能需要:
- LVM 工具;
- 对应块设备驱动;
- XFS 驱动;
- 若上层有加密,还需要 cryptsetup 和加密驱动。
再查看块设备层级:
$ lsblk -f
NAME FSTYPE UUID MOUNTPOINTS
nvme0n1
├─nvme0n1p1 vfat ... /boot/efi
└─nvme0n1p2 crypto_LUKS ...
└─cryptroot LVM2_member ...
└─vg0-root xfs ... /
lsblk -f 显示的是当前运行状态,不能单独证明 initramfs 中已经包含所有必要组件;还需要查看 initramfs 内容或在失败环境中检查日志。
3. 查看本次启动日志
$ journalctl -b
-b 表示当前启动。查看上一次启动:
$ journalctl -b -1
但这依赖 journald 持久化日志是否启用。若日志只保存在内存中,重启后可能没有 -b -1 可查。
按内核消息过滤:
$ journalctl -k -b
按服务过滤:
$ journalctl -b -u ssh.service
显示失败服务:
$ systemctl --failed
若要观察服务启动失败的直接原因,通常应先看:
$ systemctl status example.service
$ journalctl -b -u example.service --no-pager
systemctl status 适合快速摘要,journalctl 更适合获取完整错误、退出码和前后文。
九、启动失败的分层诊断
1. 屏幕完全没有固件画面
可能位于 Firmware 之前或 Firmware 阶段:
- 电源、主板、内存或 CPU 硬件问题;
- 显示输出切换到其他接口;
- 固件配置损坏;
- 存储设备未被识别。
此时修改 GRUB、重建 initramfs 都没有意义,因为控制权尚未交给 Bootloader。
2. 出现 “No bootable device” 或 UEFI 找不到条目
重点检查:
sudo efibootmgr -v
lsblk
还需要确认:
- 启动模式是否从 UEFI 改成了 BIOS,或反过来;
- ESP 是否存在且可读;
- Boot Entry 是否指向正确的 EFI 文件;
- 磁盘是否被固件识别;
- Secure Boot 是否拒绝该 EFI 程序。
不要因为“Linux 分区还在”就认为一定能启动。UEFI 启动项、ESP 中的 EFI 文件和磁盘上的 Linux 文件是三个不同层次。
3. GRUB 出现,但找不到内核或 initramfs
可能原因包括:
/boot未挂载或内容不完整;- GRUB 配置引用了已删除的内核;
- ESP 或
/boot文件系统损坏; - 内核升级过程中断;
- 手工修改了启动配置。
在已经启动的系统中,可以检查:
ls -lh /boot/vmlinuz-* /boot/init*
findmnt /boot
findmnt /boot/efi
如果 /boot 是独立分区,未挂载时看到的 /boot 目录可能只是根文件系统上的空目录,从而导致错误判断。
4. 内核 panic 或 “No working init found”
这说明问题可能发生在内核进入最终用户态之前。常见原因:
- 内核文件损坏;
- initramfs 缺失;
/init不存在或不可执行;- 内核命令行的
root=指向错误设备; - 关键驱动没有编译进内核或未放入 initramfs;
- initramfs 中的动态链接器或库文件不完整;
- 真实根文件系统损坏。
可以在 Bootloader 菜单临时编辑命令行,删除 quiet,增加内核日志可见性。临时增加参数不会修改磁盘上的配置,但会改变本次启动行为;不要把实验参数直接写入生产永久配置。
5. initramfs 提示找不到根设备
典型表现是:
ALERT! UUID=... does not exist.
Gave up waiting for root file system device.
应按设备链逐层验证:
-
UUID 是否与实际分区一致:
blkid lsblk -f -
控制器驱动是否存在;
-
分区是否能被识别;
-
若使用 LUKS,容器是否能打开;
-
若使用 LVM,VG 和 LV 是否能激活;
-
文件系统是否能挂载;
-
root=、rd.luks.*、rd.lvm.*是否与实际布局匹配; -
initramfs 是否是在硬件布局改变后重新生成。
重建 initramfs 的命令随发行版不同,不能混用:
# Debian/Ubuntu 常见
sudo update-initramfs -c -k "$(uname -r)"
# 或更新所有已安装内核
sudo update-initramfs -u -k all
# Fedora/RHEL 等常见
sudo dracut --force
这些命令会修改 /boot 中的启动文件。若 /boot 空间不足、目标内核版本不对或命令执行中断,可能产生新的启动故障。生产环境应先确认:
df -h /boot
uname -r
ls -lh /boot
6. 进入真实根后 systemd 启动失败
如果已经看到 systemd 日志或进入救援环境,问题通常已经越过 Firmware、Bootloader 和大部分 initramfs 阶段。重点转向:
/etc/fstab中的错误或不存在设备;- 文件系统挂载失败;
- 服务配置错误;
- 服务依赖未满足;
- SELinux/AppArmor 或权限问题;
/usr、/var等独立文件系统未挂载;- 磁盘空间或 inode 耗尽。
检查挂载失败:
systemctl --failed
journalctl -b -p err
findmnt --verify
findmnt --verify 会检查 fstab 与当前挂载描述之间的常见问题,但不能替代对所有启动依赖的验证。
进入救援目标可以在 Bootloader 临时加入:
systemd.unit=rescue.target
进入紧急目标:
systemd.unit=emergency.target
两者差异在于启动的用户态设施数量不同。emergency.target 更接近最小维护环境,适合根文件系统或关键挂载问题;rescue.target 通常会提供更多基础服务。救援环境下通常需要手工将根文件系统重新挂载为可写:
mount -o remount,rw /
该操作会改变磁盘内容。对于怀疑文件系统损坏的机器,不应在未完成备份或一致性评估时随意写入。
十、几个容易混淆的边界
1. initramfs 与 initrd 不是完全相同的历史概念
传统 initrd 通常可以表现为一个内存块设备上的文件系统;现代 Linux 更常用 initramfs,本质上是由内核展开到 rootfs 的 cpio 归档。
很多命令、文件名和文档仍然使用 initrd 这个历史名称,例如:
/boot/initrd.img-...
文件名叫 initrd 不代表其内部一定采用旧的 initrd 机制。判断实际内容应查看对应发行版的生成工具和镜像结构。
2. /init 与 /sbin/init 是两个不同阶段的入口
典型系统中:
initramfs:/init # 临时用户态入口
真实根:/sbin/init # 最终用户态入口,可能是 systemd
如果 /init 失败,systemd 可能还没有启动;如果 /sbin/init 失败,则通常已经完成了根文件系统切换。
3. systemd 不是所有 Linux 的必需组件
Linux 内核只要求存在可执行的 init 入口,并不要求它必须是 systemd。可以通过内核参数指定:
init=/bin/sh
这会让内核尝试执行指定程序,常用于故障分析。但它会绕过正常 systemd 初始化,可能没有挂载、网络、设备管理和日志服务;在生产系统上仅应作为临时救援手段。
4. root= 不等于“内核自己挂载所有根设备”
root=/dev/... 或 root=UUID=... 只是描述目标。真正让设备出现的可能是:
- 内核内建驱动;
- initramfs 中的模块;
- udev 规则;
- mdadm;
- cryptsetup;
- LVM;
- 网络根文件系统工具。
所以看到正确的 root=UUID,并不能证明启动一定成功。
5. systemd 的“服务启动完成”不一定代表应用真正可用
一个服务可能在进程成功执行后就被认为已启动,但应用内部仍可能:
- 尚未完成端口监听;
- 尚未完成数据库连接;
- 尚未加载配置;
- 尚未准备好接受请求。
对于需要严格就绪语义的程序,应使用合适的 Type=、通知机制、健康检查和应用层探针,而不是仅靠 After= 猜测可用性。
十一、如何把启动证据串成一条因果链
排查时可以按“最后出现的证据”定位阶段:
没有固件画面
-> Firmware 或硬件层
有固件,但没有 Bootloader
-> 启动项、ESP、BIOS 启动代码或 Secure Boot
有 Bootloader,但找不到内核
-> /boot、ESP、Bootloader 配置
内核开始输出,但找不到根设备
-> 驱动、initramfs、root=、LUKS、LVM、RAID、文件系统
已经挂载根,但没有 systemd
-> /sbin/init、动态链接器、根文件系统内容
systemd 已运行,但 target 未达到
-> unit、挂载、依赖、服务、权限、资源
系统已登录,但应用不可用
-> systemd 之后的服务就绪和应用自身状态
这条链的价值在于避免跨层修改。例如:
- 根设备找不到时,优先检查 initramfs 和设备链,而不是修改 systemd unit;
- UEFI 找不到 EFI 文件时,重建 initramfs 通常没有帮助;
- systemd 服务失败时,重新安装 Bootloader 通常也不能解决应用配置错误。
十二、启动流程与关机流程的对称性
启动是从固件逐步进入用户态;关机则大体反向进行:
- systemd 停止或隔离目标;
- 服务收到停止信号并退出;
- 挂载点被卸载或重新挂载为只读;
- systemd 请求内核执行重启、关机或挂起;
- Firmware 或硬件电源控制完成最终动作。
若服务忽略停止信号,systemd 可能在超时后发送更强的终止信号。若文件系统仍有未写回数据,内核需要完成同步和卸载。强制断电会跳过这些步骤,增加文件系统和应用数据损坏风险。
可以查看上一次关机前后的日志,但日志是否持久化取决于 journald 配置:
journalctl -b -1 -e
如果系统因突然断电而没有正常关机,日志可能只保留到断电前最后一部分,不能把“没有日志”理解为“没有故障”。
十三、最终模型:每一层只负责解决自己的前置条件
一个可靠的抽象是:
Firmware 解决:
CPU 能否找到并执行下一阶段程序?
Bootloader 解决:
内核能否得到并启动所需的映像和参数?
Kernel 解决:
内存、调度、中断、设备和基本内核抽象是否建立?
initramfs 解决:
真实根文件系统所依赖的设备链能否被构造?
systemd 解决:
真实根上的用户态资源能否按依赖关系进入目标状态?
启动成功不是某个单点程序“运行了”这么简单,而是满足一系列条件:
Firmware 可执行
∧ Bootloader 可加载
∧ Kernel 可初始化
∧ /init 可执行
∧ 根设备可发现并挂载
∧ /sbin/init 可执行
∧ systemd 依赖图可处理
∧ 目标服务达到所需状态
其中任一条件失败,故障表现都会不同。理解 Firmware、Bootloader、Kernel、initramfs 和 systemd 之间的控制权、数据流和职责边界,才能把启动问题从“黑屏或卡住”还原为可验证的具体状态转换。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux 内核与用户态:系统调用、异常、中断和硬件抽象
- 下一篇:Linux 目录与文件系统层次:FHS、挂载点、proc、sysfs 与设备
- 延伸:Linux 无法启动与系统故障排查:救援模式、磁盘、服务和内核证据
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论