3. GRUB配置与调试

GRUB(GRand Unified Bootloader)是 Linux 系统启动过程中第一个由用户直接交互的组件。当内核早期启动出现崩溃、挂起或控制台无输出时,GRUB 提供了关键的调试入口。本章聚焦于 GRUB 的命令行交互、内核参数传递技巧以及两种重要的故障恢复模式。

3.1 GRUB 命令行模式:启动前的交互式调试

GRUB 命令行模式允许用户在加载内核之前手动编辑启动项、查看设备信息或逐条执行 GRUB 命令。这是排查内核参数错误、根文件系统路径错误或硬件识别失败的第一道防线。

进入 GRUB 命令行: 在 GRUB 菜单界面按下 c 键(部分旧版为 Esc),即可从菜单模式切换到命令行模式。提示符变为 grub>。

常用 GRUB 调试命令:

命令 功能 调试场景
ls 列出所有可识别的分区和设备 确认 GRUB 能否看到磁盘和分区
ls (hd0,1)/ 列出指定分区根目录的文件 验证内核或 initrd 文件是否存在
set 显示当前 GRUB 环境变量 检查 root、prefix 等变量是否正确
cat (hd0,1)/boot/grub/grub.cfg 查看配置文件内容 确认菜单项的内核参数
linux 加载内核并指定参数 手动指定内核路径和调试参数
initrd 加载初始 RAM 磁盘 手动指定 initrd 路径
boot 启动已加载的内核 执行手动配置的启动流程
调试技巧: 在 GRUB 命令行中,可以使用 Tab 键进行路径补全。例如输入 ls (hd0,1)/boot/vm 后按 Tab,GRUB 会自动补全内核文件名,这有助于确认文件路径是否正确。

3.2 内核参数传递:console、earlyprintk 与 debug

内核参数是连接 GRUB 与内核早期调试的桥梁。通过向内核传递特定的参数,可以控制控制台输出设备、启用早期打印功能以及调整内核的调试级别。

3.2.1 console 参数:控制内核消息输出

console 参数用于指定内核日志的输出设备。可以指定多个 console,内核会向所有指定的设备输出消息。

# 将内核消息输出到串口 ttyS0,波特率 115200
console=ttyS0,115200

# 同时输出到串口和 VGA 文本控制台
console=ttyS0,115200 console=tty0

# 仅输出到 VGA 控制台(默认行为)
console=tty0
注意: 当指定多个 console 时,最后一个 console 参数指定的设备会成为内核的“主控制台”(/dev/console)。如果串口和 VGA 同时启用,通常将串口放在前面用于调试,VGA 放在后面作为主控制台。

3.2.2 earlyprintk 参数:捕获早期内核崩溃

earlyprintk 是内核早期启动调试的“杀手锏”。它在内核初始化控制台子系统之前就启用了一个极简的打印机制,能够输出从 start_kernel() 到控制台初始化完成之间的关键日志。

# 输出到串口
earlyprintk=serial,ttyS0,115200

# 输出到 VGA 屏幕(仅 x86)
earlyprintk=vga

# 保持 early console 在正常 console 初始化后继续工作
earlyprintk=serial,ttyS0,115200 keep
典型场景: 当系统在 GRUB 选择内核后直接黑屏或重启,没有任何控制台输出时,添加 earlyprintk=serial,ttyS0,115200 并通过串口线连接另一台机器捕获日志,往往是获取崩溃信息的唯一途径。

3.2.3 debug 参数:提升内核日志详细度

debug 参数是一个通用开关,它会将内核的日志级别提升到最高(KERN_DEBUG),并启用许多内核子系统的额外调试输出。

# 启用内核调试输出
debug

# 同时启用 earlyprintk 和 debug
earlyprintk=serial,ttyS0,115200 debug

# 指定日志级别(数字越小越紧急,7 为 debug 级别)
loglevel=7

参数组合对比:

参数组合 输出时间点 信息详细度 适用场景
console=tty0 控制台初始化后 默认(KERN_INFO 及以上) 正常启动
earlyprintk=serial 从 setup_arch() 开始 默认 早期挂起、无输出
earlyprintk=serial debug 从 setup_arch() 开始 最高(KERN_DEBUG) 内核早期崩溃、驱动初始化失败
earlyprintk=serial debug ignore_loglevel 从 setup_arch() 开始 强制输出所有日志 严重崩溃,日志被抑制

3.3 救援模式与单用户模式

当系统无法正常启动到多用户模式时,GRUB 提供了两种特殊的启动目标:救援模式(rescue.target)和单用户模式(emergency.target / single)。

3.3.1 单用户模式(Single User Mode)

单用户模式启动一个具有 root 权限的最小化 shell,通常用于修复文件系统错误、重置 root 密码或禁用有问题的服务。

进入单用户模式的方法:

  1. 在 GRUB 菜单界面,选中目标内核条目,按下 e 键进入编辑模式。
  2. 找到以 linux 开头的行,在行末添加 single 或 1 或 S。
  3. 按下 Ctrl+x 或 F10 启动。
# 在 linux 行末添加
linux /vmlinuz-5.15.0 root=/dev/sda1 ro single
systemd 系统说明: 在 systemd 管理的系统中,内核参数 single 会被映射为 systemd.unit=rescue.target。也可以直接使用 systemd.unit=rescue.target 或 systemd.unit=emergency.target 来精确控制启动目标。

3.3.2 救援模式(Rescue Mode)

救援模式比单用户模式更底层。它启动一个最小的系统环境,挂载根文件系统为只读,不启动任何服务,甚至不启动网络。适用于根文件系统损坏、关键系统库缺失等严重故障。

救援模式与单用户模式对比:

特性 单用户模式(rescue.target) 救援模式(emergency.target)
根文件系统挂载 读写(rw) 只读(ro)
网络服务 不启动 不启动
多用户支持 否 否
文件系统检查 通常不执行 不执行
适用场景 服务配置错误、密码重置 根文件系统损坏、内核模块缺失
内核参数 single 或 1 systemd.unit=emergency.target

3.4 GRUB 调试与内核参数传递流程

以下 SVG 图展示了从 GRUB 菜单到内核启动的完整调试参数注入流程,以及不同参数生效的阶段。

GRUB 调试与内核参数传递流程 GRUB 菜单界面 按 e 编辑 / 按 c 进命令行 编辑模式 修改 linux 行参数 命令行模式 手动执行 linux/initrd/boot 内核参数注入 console=ttyS0,115200 earlyprintk=serial debug loglevel=7 single / systemd.unit=rescue.target 内核早期启动 setup_arch() earlyprintk 生效 控制台初始化 console 参数生效 init 进程启动 关键说明: 1. earlyprintk 在 setup_arch() 后立即生效,早于 console 参数。 2. 如果内核在控制台初始化前崩溃,只有 earlyprintk 能捕获日志。 3. 单用户/救援模式通过 systemd.unit 参数指定,在 init 进程启动后生效。 4. 使用 keep 参数可让 earlyprintk 在控制台初始化后继续输出。

3.5 实战案例:通过 GRUB 调试串口无输出问题

问题现象: 嵌入式设备启动后,串口终端没有任何输出,系统似乎挂死在 GRUB 加载内核之后。

排查步骤:

  1. 确认 GRUB 阶段正常: 在 GRUB 菜单按 c 进入命令行,执行 ls 确认磁盘可见,执行 cat (hd0,1)/boot/grub/grub.cfg 确认配置文件可读。
  2. 注入 earlyprintk 参数: 按 e 编辑启动项,在 linux 行末添加 earlyprintk=serial,ttyS0,115200 debug ignore_loglevel。
  3. 捕获早期日志: 启动后,串口终端输出如下关键信息:
    Early console on ttyS0 at 0x3f8 (options '115200')
    bootconsole [uart0] enabled
    [    0.000000] Linux version 5.15.0 ...
    [    0.000123] Kernel command line: ... earlyprintk=serial,ttyS0,115200 debug
    [    0.000456] console [ttyS0] enabled
    [    0.000789] bootconsole [uart0] disabled
  4. 分析日志: 日志显示控制台在 [0.000456] 秒时成功启用,但随后在某个驱动初始化时崩溃。通过 debug 参数提供的详细日志,定位到是 8250 串口驱动在探测时访问了错误的 MMIO 地址。
  5. 修复: 在 GRUB 参数中添加 8250.nr_uarts=1 或修正设备树中的串口地址映射,系统正常启动。
经验总结: 对于串口无输出的问题,earlyprintk 是唯一能在控制台初始化前输出信息的机制。配合 debug 和 ignore_loglevel,可以确保所有内核消息都被强制输出,不遗漏任何崩溃前的线索。

3.6 本章小结