Linux 基础体系 · 第 25/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 无法启动与系统故障排查:救援模式、磁盘、服务和内核证据

Linux 启动失败并不是一个单一故障。屏幕上看到的“无法启动”可能发生在完全不同的阶段:

  • Firmware 没有找到可执行的启动程序;
  • Bootloader 找到了内核,但内核参数或 initramfs 不正确;
  • 内核已经运行,但无法挂载根文件系统;
  • systemd 已经启动,却无法满足某个关键依赖;
  • 系统已经进入用户空间,但磁盘、服务、网络或内核模块随后发生故障。

这些阶段使用不同的证据源和恢复手段。用“反复重启”“直接运行 fsck”“删除日志”代替分层诊断,往往会破坏现场,甚至扩大损坏范围。

本文以现代主流 Linux 发行版为背景,重点讨论 systemd、initramfs、LVM、软件 RAID、LUKS 和 UEFI 场景。Debian、Ubuntu、RHEL、Fedora、SUSE 等发行版的工具名称和目录可能不同,命令执行前应确认本机实现。涉及磁盘写入、文件系统修复、重建 initramfs 和重装 Bootloader 的操作,都需要 root 权限,并且具有生产风险。


一、先建立启动故障的分层模型

1. Firmware、Bootloader、Kernel、initramfs 和 systemd 分别做什么

一次典型的 UEFI Linux 启动可以抽象为:

flowchart LR
    A[Firmware<br/>UEFI 或 BIOS] --> B[Bootloader<br/>GRUB 等]
    B --> C[Linux Kernel]
    B --> D[initramfs]
    C --> D
    D --> E[真实根文件系统]
    E --> F[systemd PID 1]
    F --> G[目标 target]
    G --> H[登录、网络、业务服务]

各阶段的职责不同:

  1. Firmware
    固件初始化 CPU、内存和部分硬件,然后根据启动顺序寻找启动项。UEFI 通常从 EFI System Partition,简称 ESP,加载 .efi 程序;传统 BIOS 则把控制权交给磁盘引导扇区中的代码。

  2. Bootloader
    典型实现是 GRUB。它读取配置,选择一个内核文件和对应的 initramfs,并把内核命令行传递给内核。Bootloader 自己通常不负责挂载完整的 Linux 根文件系统,而是负责把后续所需文件装入内存并跳转到内核。

  3. Kernel
    内核初始化内存管理、调度器、驱动、网络栈和虚拟文件系统。它需要找到并挂载根文件系统,或者把这一任务交给 initramfs 中的用户空间程序。

  4. initramfs
    initramfs 是启动早期使用的临时根文件系统,通常包含内核模块、udev、挂载工具、LVM、LUKS、RAID 和启动脚本。它解决“真正的根文件系统还不可访问,但访问它所需的工具必须先运行”的循环依赖。

  5. systemd
    在常见发行版中,systemd 作为 PID 1 启动。它读取 unit 文件,建立依赖关系,启动挂载点、交换空间、网络和服务,并把系统推进到 multi-user.target 或图形目标等状态。

因此,错误信息本身通常已经暗示故障层次:

现象 优先怀疑
No bootable device、固件找不到启动项 Firmware、ESP、Bootloader
GRUB 菜单出现,但找不到内核 /boot、GRUB 配置、磁盘或文件系统
Kernel panic - not syncing: VFS: Unable to mount root fs 内核参数、initramfs、根设备、存储驱动
Timed out waiting for device UUID 错误、磁盘未出现、LVM/LUKS/RAID 未解锁
Dependency failed for /home /etc/fstab、文件系统、网络挂载
Entering emergency mode systemd 关键挂载或依赖失败
已出现登录提示,但业务不可用 服务、网络、权限、资源或内核运行时故障

“系统没起来”不是足够精确的诊断结论。第一步应当确定最后一个成功阶段。


二、救援模式不是一种模式

1. rescue.targetemergency.target

systemd 将系统运行状态表示为 target。两个容易混淆的目标是:

  • rescue.target:单用户救援环境。通常会挂载本地文件系统,并启动较少的基础服务,通常需要 root 认证。
  • emergency.target:更早、更小的紧急环境。它只提供尽可能少的用户空间,常用于根文件系统或关键依赖无法正常建立时的人工干预。

可以把二者理解为:

rescue.target:
    根文件系统可用
    较多本地挂载通常已尝试完成
    有基本服务和 shell
    适合修改配置、禁用服务、检查文件系统

emergency.target:
    启动链更早中断
    只保证最少环境
    可能只有根文件系统
    适合处理 fstab、根设备、早期挂载问题

实际行为受发行版和 unit 配置影响,不应假定所有挂载都已成功。

已能登录系统时,可以查看目标:

systemctl get-default
systemctl list-units --type=target
systemctl isolate rescue.target

isolate 会停止不属于新目标依赖的单元。在远程生产主机上执行可能立即断开 SSH 或停止业务,必须先确认控制台、带外管理和回滚方式。

2. 从 GRUB 临时进入救援环境

在 GRUB 菜单中选择内核条目,按 e 编辑启动参数。常用参数包括:

systemd.unit=rescue.target
systemd.unit=emergency.target

将参数追加到以 linuxlinuxefi 开头的内核命令行末尾,然后使用屏幕提示启动。这个修改通常只对本次启动有效,重启后恢复。

进入后先确认根文件系统状态:

id
cat /proc/cmdline
findmnt /
mount | head

如果根文件系统是只读,可以先判断是否因为内核检测到错误而保护性挂载为只读:

findmnt -no SOURCE,FSTYPE,OPTIONS /
dmesg | tail -n 100

只有在确认底层文件系统没有持续 I/O 错误、并且确实需要修改配置时,才考虑:

mount -o remount,rw /

若内核日志持续出现 I/O errorUNCmedium errorresetting link 等信息,不应通过强制读写挂载掩盖硬件或链路问题。

3. rd.break:在 initramfs 和真实根文件系统之间停下

在使用 dracut 或类似 initramfs 实现的发行版中,常见调试参数是:

rd.break

它使系统在 initramfs 阶段暂停,通常会得到一个 switch_root 之前的 shell。此时当前 / 不是已经安装好的真实根文件系统,而是 initramfs 自身。

因此必须先确认环境:

cat /proc/cmdline
findmnt /
ls /

在这个 shell 中,真实根文件系统常被挂载在 /sysroot,但具体路径取决于实现:

findmnt
ls -la /sysroot

若要修改真实系统内的文件,通常需要:

mount -o remount,rw /sysroot
chroot /sysroot

对于 SELinux 系统,直接在救援环境中修改文件可能造成标签问题。修改后可以考虑:

touch /.autorelabel

这会要求下一次启动重新标记文件,可能耗时较长;是否需要这样做取决于修改内容和 SELinux 状态。

4. init=/bin/bash 的边界

也可以把内核参数改为:

init=/bin/bash

这会让内核直接启动 shell,而不是正常启动 init/systemd。该方式适合极端修复,但环境非常不完整:

  • /proc/sys/dev 可能尚未挂载;
  • 没有 systemd 管理;
  • 网络、日志和设备事件处理通常不可用;
  • 根文件系统可能是只读;
  • 退出 shell 后通常无法继续正常启动。

它不是普通的“安全模式”,而是绕过正常 PID 1 的调试入口。修复完成后通常执行:

mount -o remount,rw /
sync
exec /sbin/reboot -f

强制重启会绕过正常卸载流程,只应作为最后手段,并且必须先同步数据。


三、救援环境中的第一原则:先保存证据,再改变状态

故障排查的证据包括:

  • 当前内核命令行:/proc/cmdline
  • 内核环形缓冲区:dmesg
  • systemd journal:journalctl
  • 设备发现状态:lsblkudevadm
  • 挂载和依赖:findmntsystemctl
  • 文件系统和块设备元数据:blkidfile -s
  • 磁盘健康信息:smartctl
  • 运行时资源:freedfvmstat

在系统仍可运行时,可以先建立一个诊断目录:

sudo -i
stamp=$(date +%Y%m%d-%H%M%S)
mkdir -p "/root/incident-$stamp"

dmesg -T > "/root/incident-$stamp/dmesg.txt"
journalctl -b 0 --no-pager > "/root/incident-$stamp/journal-current.txt"
cat /proc/cmdline > "/root/incident-$stamp/cmdline.txt"
lsblk -e7 -o NAME,PATH,TYPE,SIZE,FSTYPE,UUID,MOUNTPOINTS > "/root/incident-$stamp/lsblk.txt"
findmnt -A > "/root/incident-$stamp/findmnt.txt"

如果系统已经无法启动,应从救援 ISO 或另一台系统挂载目标根文件系统,并把证据保存到独立介质。不要把唯一副本写回疑似故障的磁盘。

日志不一定存在于当前启动

journalctl -b 0 表示当前启动,journalctl -b -1 表示上一次启动,前提是上一次日志已经持久化并且仍然存在:

journalctl --list-boots
journalctl -b -1 -p warning..alert --no-pager

如果 /var/log/journal 不存在,journald 可能只使用内存日志;系统断电或早期崩溃后,上一轮日志可能丢失。journalctl -b -1 没有输出不等于上一次没有故障。

在救援系统中读取离线日志:

journalctl \
  --directory=/mnt/target/var/log/journal \
  --list-boots

journalctl \
  --directory=/mnt/target/var/log/journal \
  -b -1 --no-pager

如果日志文件位于加密根文件系统中,必须先解锁并挂载对应设备。


四、磁盘故障:区分块设备、文件系统和挂载配置

“磁盘坏了”至少可能指三类问题:

  1. 块设备不可用:磁盘、控制器、线缆、虚拟磁盘或云盘没有出现;
  2. 文件系统损坏:块设备存在,但文件系统元数据不一致;
  3. 挂载配置错误:设备正常,但 /etc/fstab 中的 UUID、路径、选项或依赖不正确。

1. 从设备树开始,而不是从 /dev/sda

lsblk -e7 -o NAME,PATH,TYPE,SIZE,FSTYPE,FSVER,LABEL,UUID,MOUNTPOINTS
blkid
findmnt -A

可能看到类似结果:

NAME        TYPE  SIZE FSTYPE      UUID                                 MOUNTPOINTS
nvme0n1     disk  477G
├─nvme0n1p1 part  512M vfat        7A3B-21F0                            /boot/efi
├─nvme0n1p2 part    1G xfs         ...
└─nvme0n1p3 part 475G LVM2_member  ...
  ├─vg-root  lvm    60G xfs        ...                                  /
  └─vg-data  lvm   415G xfs        ...                                  /data

这里有三个重要区别:

  • /dev/nvme0n1 是整块磁盘;
  • /dev/nvme0n1p3 是分区;
  • /dev/mapper/vg-root 是 LVM 逻辑卷。

不能对错误层级运行文件系统工具。例如,fsck /dev/nvme0n1 通常不是修复根文件系统的正确操作;应先找到实际承载文件系统的逻辑卷或分区。

2. LVM、LUKS 和 RAID 的激活顺序

复杂存储通常具有这样的层次:

磁盘分区
  └── LUKS 加密容器
        └── LVM Physical Volume
              └── Volume Group
                    └── Logical Volume
                          └── 文件系统

也可能是:

磁盘分区
  └── mdadm RAID
        └── LUKS
              └── LVM
                    └── 文件系统

每一层都必须先使下一层可见。救援环境中可逐层检查:

lsblk
cat /proc/mdstat
mdadm --detail --scan
pvs
vgs
lvs -a -o +devices
cryptsetup luksDump /dev/nvme0n1p3

激活 LVM:

vgchange -ay
lvs

解锁 LUKS:

cryptsetup luksOpen /dev/nvme0n1p3 cryptroot
ls -l /dev/mapper/cryptroot

组装软件 RAID:

mdadm --assemble --scan
cat /proc/mdstat

这些命令不是无条件安全的。mdadm --create 会创建新阵列元数据,可能破坏原有数据;在不确定阵列成员和布局时,不能把 --create 当作修复命令。LUKS 的 luksDump 是读取元数据,而 luksFormat 会初始化容器,二者风险完全不同。

3. 文件系统检查必须在未挂载状态进行

对 ext4,常见检查命令是:

fsck.ext4 -f -n /dev/mapper/vg-root

其中:

  • -f 请求即使看起来干净也执行检查;
  • -n 不写入修复结果,只报告问题。

确认根文件系统已卸载、并且有备份或快照后,才可能执行实际修复:

fsck.ext4 -f /dev/mapper/vg-root

XFS 不使用传统 fsck.xfs 进行在线修复。通常先使用:

xfs_repair -n /dev/mapper/vg-root

-n 是只检查不修改。实际 xfs_repair 需要卸载文件系统,并且某些严重损坏场景可能要求先处理日志。Btrfs、ZFS 等文件系统有各自的检查和恢复工具,不能套用 ext4 或 XFS 的流程。

核心规则是:

文件系统检查工具针对的是文件系统,不是任意块设备;修复前必须确认目标、类型、挂载状态和数据副本。

对已挂载的根文件系统运行离线修复,可能导致元数据和内核缓存不一致,风险远高于“检查失败”。

4. /etc/fstab 是启动失败的高频来源

/etc/fstab 描述系统启动时如何把设备挂载到目录。现代系统常使用 UUID:

UUID=xxxx-xxxx  /boot/efi  vfat  umask=0077  0  1
UUID=...        /          ext4  defaults    0  1
UUID=...        /data      xfs   defaults    0  2

检查配置与实际设备是否一致:

cat /etc/fstab
blkid
findmnt --verify

findmnt --verify 能发现部分语法和挂载配置问题,但不能保证设备在下一次启动时一定可用。

临时设备或非关键挂载可以使用:

UUID=...  /archive  ext4  defaults,nofail,x-systemd.device-timeout=10s  0  2

含义是:

  • nofail:该挂载失败时,不把整个启动判定为失败;
  • x-systemd.device-timeout=10s:等待设备出现的时间限制。

但不能把所有挂载都加上 nofail。根文件系统、关键业务数据和安全审计存储如果挂载失败却继续启动,可能产生“应用正常启动但实际写入临时目录”的更隐蔽故障。

修改前先备份:

cp -a /etc/fstab "/etc/fstab.bak.$(date +%s)"

如果只是某一行配置错误,可以在救援模式中注释它,再重启验证。修复后必须确认实际挂载来源:

findmnt /data
df -hT /data

不能只看目录存在;目录存在但未挂载时,程序仍会把数据写入根文件系统。

5. 磁盘空间满与 inode 用尽是两类故障

df -hT
df -ih

df -h 显示数据块空间,df -i 显示 inode 使用情况。例如:

  • 数据块 100%:大文件、日志、镜像或数据库占满空间;
  • inode 100%:大量小文件耗尽 inode,即使还有 GB 空间,也无法创建新文件。

进一步定位:

du -xhd1 /var 2>/dev/null | sort -h
find /var -xdev -type f -size +1G -ls

du 统计目录中的可见文件,而已删除但仍被进程打开的文件不会正常反映在目录树中。此时检查:

lsof +L1

输出中 LINK COUNT 为 0 的大型文件可能仍被进程占用。删除目录项并不会立即释放空间,只有进程关闭文件描述符后空间才会回收。重启可能释放空间,但会中断业务;更稳妥的做法是先识别进程并按服务支持的方式重新打开日志或重启服务。

6. SMART 和内核 I/O 证据

SATA/SAS 磁盘可以尝试:

smartctl -a /dev/sda

NVMe 设备常用:

nvme smart-log /dev/nvme0

SMART 是设备自报状态,不是绝对证明。虚拟机中可能无法直接访问物理设备,云环境应结合云平台磁盘事件。

内核证据:

dmesg -T | egrep -i \
'error|fail|timeout|reset|I/O|medium|unc|nvme|ata|scsi|ext4|xfs|btrfs'

例如,文件系统报告日志错误,同时内核出现设备重置和 I/O timeout,应优先怀疑底层存储,而不是先反复运行文件系统修复。修复工具在不稳定设备上持续写入,可能把可恢复状态变成更严重的损坏。


五、服务故障:从 systemd 状态、依赖和日志还原因果

1. unit 的状态不是单一布尔值

systemd 管理的对象称为 unit,常见类型包括:

  • .service:服务进程;
  • .mount:挂载点;
  • .device:设备出现状态;
  • .socket:套接字激活;
  • .target:一组依赖的逻辑目标;
  • .timer:定时触发器。

查看服务:

systemctl status nginx.service
systemctl is-enabled nginx.service
systemctl is-active nginx.service
systemctl is-failed nginx.service

这些命令回答不同问题:

  • status:最近状态、主进程、退出码和部分日志;
  • is-enabled:是否配置为开机启用;
  • is-active:当前是否处于 active;
  • is-failed:是否处于 failed 状态。

“服务未启动”也有多种原因:

  • 没有启用,所以启动目标没有拉起它;
  • 被依赖关系阻止;
  • 进程启动后立即退出;
  • 配置检查失败;
  • 端口被占用;
  • 权限、证书、挂载点或网络条件不满足;
  • systemd 达到启动超时;
  • 服务被 OOM killer 终止。

2. 先看直接错误,再看完整依赖

systemctl status myapp.service --no-pager -l
journalctl -u myapp.service -b --no-pager
systemctl show myapp.service \
  -p LoadState,ActiveState,SubState,Result,ExecMainCode,ExecMainStatus

ExecMainStatus 是进程退出状态,Result 是 systemd 对这次执行结果的归类。二者不能简单等同于应用自己的错误码,但能帮助区分“未执行”“启动失败”和“被信号终止”。

查看依赖:

systemctl list-dependencies myapp.service
systemctl cat myapp.service
systemctl show myapp.service \
  -p Requires,Wants,After,Before,ConditionResult,AssertResult

这里有一个重要区别:

  • Requires= 表示依赖关系,依赖单元失败通常会影响当前单元;
  • Wants= 是较弱的拉起关系,被拉起的单元失败不必然使当前单元失败;
  • After= 只规定启动顺序,不表示依赖;
  • Before=After= 的反向顺序关系,不表示必须启动;
  • Condition... 不满足时,单元可能被跳过,而不是报出普通启动失败;
  • Assert... 不满足时,通常会使单元失败。

因此,看到:

After=network-online.target

不能推导出网络一定可用。它只表达排序;是否有真实网络等待服务、网络管理器是否提供该 target,需要进一步检查。

systemctl status network-online.target
systemctl list-dependencies network-online.target
systemd-analyze critical-chain myapp.service

critical-chain 显示影响目标到达的关键等待链,但不是完整因果图。并行启动时,一个慢单元可能位于关键链上,却不一定是根因;还要结合时间戳和日志判断。

3. 日志时间和退出方式比一句错误更重要

journalctl -u myapp.service -b \
  --since "10 minutes ago" \
  -o short-precise \
  --no-pager

要关注:

  • 配置文件解析失败;
  • Permission denied
  • Address already in use
  • 依赖文件或目录不存在;
  • TLS 证书过期;
  • 数据库不可连接;
  • status=1/FAILURE
  • signal=KILLsignal=SEGV
  • 启动后立即停止的重复循环。

端口占用:

ss -ltnp
ss -lxnp

配置校验应优先使用应用自带的检查命令,例如 Nginx:

nginx -t

不要因为 systemctl restart 失败就连续重启。反复重启会覆盖时间关系,触发服务的限速机制,并可能造成数据库恢复、重复任务或连接风暴。

4. 修改 unit 的正确位置与回滚

临时测试可直接:

systemctl edit --runtime myapp.service

持久化覆盖应使用:

systemctl edit myapp.service

它通常创建 /etc/systemd/system/myapp.service.d/override.conf,优先级高于发行版提供的 /usr/lib/systemd/system/lib/systemd/system 文件。

修改后:

systemctl daemon-reload
systemctl restart myapp.service
systemctl status myapp.service --no-pager

daemon-reload 只让 systemd 重新读取 unit 文件,不会自动重启服务。若使用 systemctl edit,工具通常会自动处理 reload,但显式理解这一步仍然重要。

回滚本地覆盖:

systemctl revert myapp.service
systemctl daemon-reload

生产环境应保留原配置和变更记录。直接编辑 /usr/lib/systemd/system/*.service 容易在软件升级时被覆盖,也破坏包管理器对文件的管理。


六、内核证据:区分 panic、Oops、驱动故障和资源压力

1. dmesg 记录内核视角,journal 记录更完整的启动上下文

查看内核消息:

dmesg -T --level=emerg,alert,crit,err,warn

查看本次启动中与内核相关的 journal:

journalctl -k -b 0 --no-pager

两者并不总是相同:

  • dmesg 读取内核环形缓冲区,旧消息可能已被覆盖;
  • journalctl -k 读取 journald 收集的内核消息,是否完整取决于 journald 是否运行、是否持久化及权限;
  • 早期内核消息可能只存在于控制台、串口、netconsole 或 pstore。

如果当前系统还能运行,先保存:

dmesg -T > /root/dmesg.$(date +%s).txt
journalctl -k -b 0 > /root/kernel-journal.$(date +%s).txt

2. 内核 Oops 和 Kernel Panic 的区别

Oops 通常表示内核检测到异常,例如非法访问或某个内核线程发生错误。系统可能继续运行,但受影响的进程、驱动或子系统已经不可靠。日志中可能有:

BUG: unable to handle page fault
Oops: 0002
Call Trace:

Kernel panic 表示内核无法安全继续,可能停止所有任务或重启。常见根文件系统启动失败信息包括:

VFS: Cannot open root device
Kernel panic - not syncing: VFS: Unable to mount root fs

这里的因果链通常是:

根设备参数错误
    或存储驱动不在 initramfs
    或 LVM/LUKS/RAID 未激活
    或文件系统损坏
        ↓
initramfs 无法提供根目录
        ↓
内核无法切换到真实根文件系统
        ↓
panic 或进入早期救援环境

不能仅凭 Kernel panic 判断是“内存坏了”。必须读取 panic 前的设备、文件系统、调用栈和启动参数。

3. 检查启动参数和内核模块

cat /proc/cmdline
uname -a
lsmod
modinfo <module-name>

常见参数包括:

  • root=UUID=...:根设备定位;
  • ro:早期以只读方式挂载根文件系统;
  • rd.lvm.lv=vg/root:initramfs 中激活某个 LVM 逻辑卷;
  • rd.luks.uuid=luks-...:initramfs 中解锁 LUKS;
  • resume=...:休眠恢复设备;
  • systemd.unit=...:systemd 目标;
  • rd.break:在 initramfs 中暂停。

必须注意:rd.* 参数通常由 initramfs 使用,真实根系统中的 systemd 不一定读取它们;没有 rd. 前缀的参数可能由真实用户空间处理。不要把参数拼写错误归因于“内核不支持”。

4. OOM:内核杀进程不等于服务自己退出

内存压力下,内核可能触发 OOM killer:

journalctl -k -b 0 | grep -i -E 'out of memory|oom|killed process'

也可以检查:

free -h
swapon --show
cat /proc/pressure/memory

如果日志显示:

Out of memory: Killed process 1234 (myapp)

那么 systemd 看到的往往是进程被信号终止,而不是应用主动返回错误。此时只修改 systemd 的 Restart= 只能让它反复被杀,不能解决内存压力。

cgroup 限制也可能导致服务在整机还有空闲内存时被杀。检查:

systemctl show myapp.service \
  -p MemoryCurrent,MemoryMax,TasksCurrent,TasksMax

MemoryMax 是该 cgroup 的限制,不等于整机物理内存。

5. 设备驱动和硬件错误的证据

journalctl -k -b 0 | grep -i -E \
'firmware|failed|timeout|reset|link is down|I/O error|segfault|machine check|edac'

不同信息指向不同层次:

  • firmware: failed to load:设备可能使用了缺失的固件,但不等于设备物理损坏;
  • link is down:可能是网线、交换机、驱动或接口状态;
  • segfault:用户进程崩溃,不自动等于内核故障;
  • Machine CheckEDAC:可能涉及 CPU、内存或内存控制器硬件;
  • 存储 timeout 和设备 reset:应优先检查设备链路与硬件,而不是只重启业务。

为了保留 panic 后的证据,生产系统可以根据发行版配置:

  • pstore/ramoops:由固件或预留内存保存崩溃信息;
  • kdump:启动一个捕获内核,把崩溃内存保存为 vmcore
  • netconsole 或串口控制台:把内核消息发送到外部接收端。

这些能力依赖内核配置、硬件、引导参数和发行版服务,不能假定安装后自动可用。验证 kdump 时要确认预留内存、捕获内核和转储目录,而不是只看服务是否 active


七、一个可复用的启动故障诊断流程

第一步:确认故障发生在哪一层

在控制台记录屏幕信息,不要先按重启。回答:

  1. 固件是否显示启动项?
  2. 是否出现 GRUB 菜单?
  3. 内核是否开始输出日志?
  4. initramfs 是否识别根设备?
  5. systemd 是否打印 StartedFailedDependency failed
  6. 是否已经出现登录提示?

如果在 GRUB 前失败,优先检查 ESP、UEFI 启动项和磁盘可见性;如果出现 VFS,优先检查根设备和 initramfs;如果进入 emergency mode,优先检查挂载和 systemd 依赖。

第二步:从低风险读取命令开始

cat /proc/cmdline
lsblk -f
findmnt -A
blkid
dmesg -T | tail -n 200
journalctl -b 0 -p warning..alert --no-pager
systemctl --failed

这些命令主要读取状态,不会改变磁盘内容。结果应保存下来。

第三步:建立“期望路径”和“实际路径”

例如,配置期望:

root=UUID=1111-2222
/var/lib/app 使用 UUID=3333-4444

实际检查:

blkid
findmnt /
findmnt /var/lib/app

若 UUID 不存在,可能是设备未出现、LUKS 未解锁、LVM 未激活或磁盘更换。若设备存在但挂载失败,再检查文件系统类型、超级块和内核错误。

第四步:做最小、可逆的修复

按风险从低到高:

  1. 修正明显错误的 /etc/fstab
  2. 禁用刚刚变更的服务或 unit 覆盖;
  3. 激活正确的 LVM、解锁正确的 LUKS、组装正确的 RAID;
  4. 在离线状态检查文件系统;
  5. 重建 initramfs;
  6. 修复 Bootloader 或 ESP;
  7. 对文件系统执行写入性修复或从备份恢复。

每一步都要有验证标准,例如:

findmnt /
systemctl is-system-running
systemctl --failed
journalctl -b 0 -p err..alert

systemctl is-system-running 可能返回 degraded,这表示系统已启动但至少有失败单元;不应把它等同于完全正常。


八、完整算例:LVM 根卷能看到,但启动进入 emergency mode

假设屏幕显示:

Timed out waiting for device dev-disk-by\x2duuid-ABCD.device
Dependency failed for /data
You are now in emergency mode

1. 从错误推导故障路径

systemd 在等待一个根据 UUID 生成的 .device unit:

UUID=ABCD 对应的块设备没有在超时时间内出现
        ↓
/data 对应的 mount unit 无法获得设备
        ↓
/data 的挂载失败
        ↓
依赖它的启动目标可能失败
        ↓
进入 emergency.target

这还不能证明磁盘坏了,因为 UUID 可能只是配置写错。

2. 在救援 shell 中确认设备

lsblk -f
blkid
cat /etc/fstab

假设结果是:

/dev/mapper/vg-data  xfs  UUID=EFGH

/etc/fstab 写的是:

UUID=ABCD /data xfs defaults 0 2

此时最小修复是把 ABCD 改成实际的 EFGH,并保留备份:

cp -a /etc/fstab /etc/fstab.bak.before-data-fix
sed -i 's/UUID=ABCD/UUID=EFGH/' /etc/fstab

然后验证:

mount /data
findmnt /data

mount 成功,说明配置错误的可能性很高。继续检查文件系统容量和服务状态:

df -hT /data
systemctl --failed

3. 如果 vg-data 根本不存在

继续检查:

pvs
vgs
lvs
cat /proc/mdstat

可能的路径是:

磁盘分区未出现
  或 RAID 未组装
  或 LUKS 未解锁
  或 LVM 卷组未激活

此时不能直接对 /data 运行 fsck,因为真正的文件系统设备还没有确定。应按实际层次恢复可见性后再挂载或检查。

4. 如果设备存在但挂载时报文件系统错误

先卸载:

umount /data

再根据 FSTYPE 选择对应工具,并先执行只读检查:

xfs_repair -n /dev/mapper/vg-data

如果是 ext4:

fsck.ext4 -f -n /dev/mapper/vg-data

只有确认检查结果、备份状态和业务窗口后,才执行写入性修复。修复完成后:

mount /data
findmnt /data
systemctl daemon-reload
systemctl default

systemctl default 会尝试回到默认启动目标;如果仍失败,应回到新的日志,而不是假设已经修复。


九、完整算例:服务“启动成功”但业务仍不可用

假设:

systemctl is-active myapp

输出:

active

但客户端连接失败。

不能由 active 推出业务可用。可能存在以下数据流:

systemd 启动进程
    ↓
进程保持运行
    ↓
应用只监听 localhost
    或端口尚未准备好
    或后端数据库不可用
    或错误配置导致请求全部失败

逐步检查:

systemctl status myapp --no-pager -l
ss -ltnp
journalctl -u myapp -b --since "10 minutes ago" --no-pager

如果监听结果是:

127.0.0.1:8080

而客户端从其他主机访问,那么 systemd 和进程都可能正常,但监听地址限制了网络可达性。

如果端口存在,再从本机执行应用协议检查,例如 HTTP:

curl -v http://127.0.0.1:8080/health

可能出现:

  • TCP 连接成功但返回 500:应用或后端依赖故障;
  • 连接被拒绝:监听已消失或绑定地址不同;
  • 超时:线程池、网络路径或后端调用阻塞;
  • 返回健康检查失败:应用的运行状态和业务状态不同。

此时“重启服务”只是改变状态,不是证据。应将 unit 状态、监听地址、应用日志和健康检查结果放在同一条因果链中。


十、从救援系统离线修复已安装系统

当目标系统无法启动,但根文件系统本身可以读取时,常见流程如下。假设真实根卷是 /dev/mapper/vg-root,启动分区是 /dev/nvme0n1p2,ESP 是 /dev/nvme0n1p1

mount /dev/mapper/vg-root /mnt/target
mount /dev/nvme0n1p2 /mnt/target/boot
mount /dev/nvme0n1p1 /mnt/target/boot/efi

mount --rbind /dev  /mnt/target/dev
mount --make-rslave /mnt/target/dev

mount --rbind /proc /mnt/target/proc
mount --make-rslave /mnt/target/proc

mount --rbind /sys  /mnt/target/sys
mount --make-rslave /mnt/target/sys

mount --rbind /run  /mnt/target/run
mount --make-rslave /mnt/target/run

这些挂载的作用是:

  • /dev:让 chroot 中看到设备节点和 udev 状态;
  • /proc:提供进程、内核参数和挂载相关接口;
  • /sys:提供设备、驱动和固件信息;
  • /run:提供运行时状态,某些工具需要它。

然后:

chroot /mnt/target /bin/bash
export PATH=/usr/sbin:/usr/bin:/sbin:/bin

此时 shell 的根目录视图变成目标系统,但内核仍然是救援系统的内核,不要把 uname -r 当作目标系统内核版本的唯一判断依据。可查看目标系统安装的内核包和 /boot 内容。

重建 initramfs

Debian/Ubuntu 常见:

update-initramfs -c -k all

如果已有对应镜像,常用更新形式是:

update-initramfs -u -k all

RHEL/Fedora 等常见:

dracut --regenerate-all --force

这两个命令不能混用。应先确认发行版和工具行为:

cat /etc/os-release
command -v update-initramfs dracut
ls -lh /boot

重建 initramfs 适用于内核模块、LVM、加密或根设备信息缺失等问题;如果根设备 UUID 写错,单独重建 initramfs 不会修正 /etc/fstab 或 Bootloader 配置。

更新 Bootloader 配置

GRUB 配置命令也有发行版差异,常见形式是:

update-grub

或:

grub2-mkconfig -o /boot/grub2/grub.cfg

UEFI 系统还需要确保 ESP 已正确挂载。不要在未确认启动模式时随意运行 Bootloader 安装命令:

[ -d /sys/firmware/efi ] && echo UEFI || echo BIOS

如果救援环境以 BIOS 模式启动,而目标系统原本使用 UEFI,/sys/firmware/efi 的结果会影响后续判断。重装 GRUB 可能写入错误磁盘或错误启动模式,生产环境应先记录:

efibootmgr -v
lsblk -o NAME,PATH,SIZE,FSTYPE,MOUNTPOINTS

完成后退出并按逆序卸载:

exit
umount -R /mnt/target
reboot

卸载前执行:

sync

如果有进程占用挂载点,先定位占用者,而不是直接使用强制卸载破坏正在进行的写入。


十一、启动恢复与备份恢复不是同一件事

修复启动链解决的是“系统能否运行”;备份恢复解决的是“数据和状态能否回到可信版本”。两者不能互相替代。

例如:

  • 修复 XFS 元数据可能使文件系统重新挂载,但不能保证被破坏文件内容恢复;
  • LVM 快照可以提供一致性视图,但快照空间耗尽后可能失效;
  • 块级镜像包含空闲块和可能的损坏状态,不等于应用一致性备份;
  • 数据库文件复制完成不等于数据库事务一致;
  • 恢复 /etc/fstab 可以恢复启动,但错误的配置可能再次导致数据写入错误挂载点。

恢复前至少确认:

恢复源来自哪个时间点?
是否包含正确的应用一致性状态?
目标设备是否足够大?
是否需要先解锁加密或激活卷组?
恢复后如何验证文件、权限、标签、服务和业务数据?

在生产中,快照、文件备份和块设备镜像的恢复路径应定期演练。没有验证过的备份只能证明“曾经生成过某个文件”,不能证明“故障时可以恢复系统”。


十二、常见误区与反例

误区一:看到 emergency mode 就运行 fsck

反例:/etc/fstab 中 UUID 拼写错误。此时文件系统可能完全健康,运行 fsck 不会修复配置,反而可能误操作到错误设备。

正确顺序是:

lsblk -f
blkid
cat /etc/fstab
findmnt -A

先确认“设备是否存在”和“配置是否指向它”。

误区二:看到服务 failed 就只执行 restart

反例:端口已被另一个进程占用。重启会继续失败,且可能触发 systemd 的启动限速。

应先看:

journalctl -u service -b
ss -ltnp
systemctl show service -p Result,ExecMainStatus

误区三:把 After= 当作依赖

反例:

After=network-online.target

这只要求排序,不会自动创建网络依赖。若服务必须依赖网络,应根据实际设计使用 Wants=Requires=,但这仍然不保证远端服务可用。

误区四:把 dmesg 中的任何 error 都当作根因

内核可能记录一个最终无关的设备探测错误,也可能记录真正导致根文件系统失败的 I/O timeout。根因判断必须结合:

时间顺序
设备层级
错误是否重复
错误是否发生在故障目标上
后续状态是否因此改变

例如 USB 设备探测失败通常不会解释 NVMe 根盘无法挂载;NVMe reset 和根设备 timeout 则高度相关。

误区五:目录存在就认为挂载成功

如果 /data 没挂载,目录仍然可以存在。应用可能把 TB 级数据写入根分区,直到根分区被填满。

验证必须使用:

findmnt /data
df -hT /data

而不是只执行:

ls -ld /data

十三、如何让下一次故障留下更多证据

1. 持久化 journal

在支持 systemd-journald 的系统上,持久化日志通常位于:

/var/log/journal

检查:

ls -ld /var/log/journal
journalctl --disk-usage

配置项位于 /etc/systemd/journald.conf,例如:

[Journal]
Storage=persistent
SystemMaxUse=1G

修改后:

systemctl restart systemd-journald

具体保留策略、压缩和磁盘占用应结合生产容量设置。日志持久化本身也依赖 /var 或根文件系统可写;根盘损坏时,本地 journal 仍可能无法保存。

2. 保留 panic 和硬件事件

对于内核 panic,应评估:

  • 是否有串口或带外控制台;
  • 是否启用 pstore;
  • 是否配置 kdump;
  • 是否将日志发送到远端;
  • 是否有硬件监控和存储平台事件。

远端日志的价值在于:即使本机磁盘损坏或系统重启,故障前的关键记录仍可能保留。但网络日志不能取代本地 panic 转储,因为 panic 可能发生在网络栈不可用的早期阶段。

3. 记录每次启动的内核和配置

uname -r
cat /proc/cmdline
systemctl --version
ls -l /boot

服务故障往往来自“最近一次升级”或“最近一次配置变更”。因此应把内核版本、initramfs 时间、Bootloader 配置、unit 覆盖和软件包变更纳入变更记录。


十四、最终验证:启动成功不等于故障已关闭

修复后至少验证四层:

启动层

systemctl is-system-running
systemctl --failed
journalctl -b 0 -p err..alert --no-pager

存储层

findmnt -A
lsblk -f
df -hT
df -ih

确认关键目录确实挂载到期望设备,而不是落在根文件系统上。

服务层

systemctl status myapp --no-pager
ss -ltnp
journalctl -u myapp -b --no-pager

确认服务的进程、监听地址、依赖和日志都正常。

业务层

使用真实协议或健康检查验证:

curl -fS http://127.0.0.1:8080/health

数据库、消息队列、存储服务还需要执行应用级读写或只读一致性检查。只有业务层也恢复,才可以认为启动故障处理完成。

Linux 启动排查的核心不是记住更多“救援命令”,而是沿着系统的数据流和状态变化建立证据链:

Firmware 是否交出控制权
→ Bootloader 是否加载正确内核和参数
→ Kernel 是否识别存储和驱动
→ initramfs 是否构造出真实根设备
→ systemd 是否完成挂载和依赖
→ 服务是否运行并提供正确接口
→ 内核和硬件是否持续稳定

每次修改都应对应一个明确假设、一个可验证结果和一个可执行回滚。这样才能把“无法启动”从模糊现象还原为可定位、可恢复、可复盘的系统故障。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。