2. QEMU 启动流程分析:BIOS/UEFI 阶段、内核加载、根文件系统挂载

理解 QEMU 的启动流程是定位模拟启动故障、复现硬件异常场景的基础。本章将拆解从虚拟上电到用户态 Shell 就绪的完整链路,重点分析 BIOS/UEFI 固件阶段、内核加载与解压、根文件系统挂载 三个核心环节,并给出每个环节的调试切入点。

调试前置知识: 启动流程中的每一个阶段都可能因配置错误、设备模型不匹配或固件缺失而崩溃。掌握各阶段的标志性日志与断点位置,能将故障定位时间缩短 70% 以上。

2.1 启动流程全景:从虚拟上电到 Shell

QEMU 模拟的启动过程与物理机高度相似,但增加了虚拟化层特有的初始化步骤。下图展示了完整的启动阶段划分与关键事件。

QEMU 启动流程全景图 QEMU 启动流程全景图 阶段 1: 虚拟上电 QEMU 进程初始化 阶段 2: BIOS/UEFI 固件自检与引导 阶段 3: 内核加载 解压与初始化 阶段 4: 根文件系统 挂载与 init 启动 每个阶段都可通过 QEMU 的 -d 日志、GDB 断点或串口输出进行调试 关键事件 - 内存映射初始化 - CPU 寄存器复位 - 设备模型实例化 关键事件 - 固件入口执行 - 引导设备枚举 - BootLoader 加载 关键事件 - 内核解压 (zImage) - 页表初始化 - 驱动探测 关键事件 - 根设备识别 - 文件系统挂载 - /sbin/init 执行 调试方法: -d int 跟踪中断 | -d cpu_reset 观察复位 | -S -s 连接 GDB 断点调试

2.2 BIOS/UEFI 阶段:固件引导与设备枚举

QEMU 默认使用 SeaBIOS(传统 BIOS)或 OVMF(UEFI 固件)作为虚拟固件。该阶段的核心任务是初始化基本硬件、枚举引导设备并加载引导程序。

2.2.1 固件选择与启动参数

固件类型 QEMU 参数 典型文件 调试特点
SeaBIOS (传统 BIOS) -bios seabios.bin /usr/share/qemu/bios.bin 启动快,日志输出少,适合快速验证
OVMF (UEFI) -bios OVMF.fd /usr/share/edk2/ovmf/OVMF.fd 支持 UEFI Shell,可调试 UEFI 应用
无固件 (直接内核) -kernel vmlinuz -append "..." 直接加载内核 跳过固件阶段,适合内核调试
调试技巧: 使用 -d in_asm 可以观察固件执行的第一条指令。对于 SeaBIOS,第一条指令位于 0xFFFFFFF0(复位向量);对于 OVMF,入口在 0xFFFFFFF0 但立即跳转到 SEC 阶段代码。

2.2.2 固件阶段调试断点

在 GDB 中连接 QEMU 后,可以针对固件阶段设置以下关键断点:

# 连接 QEMU (需使用 -s -S 参数启动)
(gdb) target remote localhost:1234

# SeaBIOS 入口断点
(gdb) break *0xFFFFFFF0

# OVMF SEC 阶段入口 (x86_64)
(gdb) break *0xFFFFFFF0

# 引导设备选择函数 (SeaBIOS)
(gdb) break seabios:func_boot

# 打印固件版本信息
(gdb) monitor info roms

2.3 内核加载阶段:解压、页表与初始化

当 BootLoader(如 GRUB)或 QEMU 的 -kernel 直接加载将内核镜像放入内存后,内核进入 解压与初始化 阶段。这是调试内核启动故障的核心区域。

2.3.1 内核加载流程分解

  1. 解压阶段: 对于压缩内核(zImage/bzImage),解压代码将内核解压到高地址内存。
  2. 页表初始化: 内核建立初始页表,从实模式切换到保护模式(x86)或设置页表(ARM64)。
  3. start_kernel 入口: 架构无关的 C 代码入口,初始化调度器、内存管理、中断等核心子系统。
  4. 设备驱动探测: 根据设备树 (DTB) 或 ACPI 表枚举并初始化设备。
内核加载阶段详细流程 内核加载阶段详细流程 1. 内核解压 decompress_kernel() 2. 页表初始化 paging_init() 3. start_kernel C 语言入口 4. 驱动探测 do_initcalls() 各阶段调试断点 解压阶段: break decompress_kernel | 页表阶段: break paging_init start_kernel: break start_kernel | 驱动探测: break do_initcalls 常见故障场景 解压失败: 内核镜像损坏或内存不足 | 页表错误: 导致 #PF 异常 | 驱动崩溃: 设备模型不匹配

2.3.2 内核启动参数调试

通过 -append 传递内核参数可以控制启动行为,以下参数对调试尤其重要:

# 启用内核早期打印 (需内核配置 CONFIG_EARLY_PRINTK)
-append "earlyprintk=serial,ttyS0,115200 console=ttyS0"

# 禁用 ACPI,强制使用设备树
-append "acpi=off"

# 指定根设备 (避免挂载阶段失败)
-append "root=/dev/vda1"

# 启用内核调试输出
-append "debug ignore_loglevel"

# 在 init 之前暂停,等待调试器
-append "initcall_debug"

2.4 根文件系统挂载阶段:根设备识别与 init 启动

根文件系统挂载是启动流程的最后关键步骤。内核必须正确识别根设备、挂载文件系统并执行 /sbin/init。此阶段失败通常表现为 Kernel panic - not syncing: VFS: Unable to mount root fs。

2.4.1 根设备识别流程

步骤 内核函数 说明 调试方法
1. 解析 root= 参数 root_dev_setup() 从 cmdline 提取根设备路径 检查 /proc/cmdline
2. 设备驱动匹配 do_mounts.c 等待根设备对应的驱动就绪 添加 rootdelay=10
3. 文件系统探测 mount_root() 尝试 ext4、btrfs 等文件系统 使用 rootfstype=ext4 指定
4. 执行 init run_init_process() 启动 /sbin/init 或指定程序 使用 init=/bin/bash 调试
常见陷阱: 使用 -kernel 直接启动时,如果未指定 -initrd 或 -drive 提供根文件系统,内核将无法找到根设备并 panic。务必检查 QEMU 的存储设备配置与内核参数中的 root= 是否匹配。

2.4.2 根文件系统挂载故障模拟与调试

以下命令模拟一个典型的根文件系统挂载失败场景,并展示如何通过 QEMU 调试定位:

# 1. 模拟根设备不存在 (故意指定错误的 root 参数)
qemu-system-x86_64 \
  -kernel bzImage \
  -append "root=/dev/sda99 console=ttyS0" \
  -nographic

# 2. 观察内核 panic 输出
# 输出示例:
# [    1.234567] VFS: Cannot open root device "sda99" or unknown-block(0,0)
# [    1.234567] Please append a correct "root=" boot option; here are the available partitions:
# [    1.234567] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

# 3. 使用 GDB 在 mount_root 设置断点
(gdb) break mount_root
(gdb) continue
# 观察 root_dev 变量的值,确认设备号是否正确

2.4.3 init 进程启动失败调试

即使根文件系统挂载成功,init 进程也可能因缺失或崩溃导致启动失败。调试方法如下:

# 使用 init=/bin/bash 绕过 init,直接进入 shell
qemu-system-x86_64 \
  -kernel bzImage \
  -append "root=/dev/vda1 console=ttyS0 init=/bin/bash" \
  -drive file=rootfs.qcow2,format=qcow2 \
  -nographic

# 进入 shell 后检查 init 文件
# ls -l /sbin/init
# file /sbin/init

# 如果 init 缺失,可以手动挂载并修复
# mount -o remount,rw /
# cp /bin/bash /sbin/init

2.5 启动流程故障速查表

下表总结了各阶段典型故障现象、根因与调试命令,可作为快速参考:

阶段 故障现象 常见根因 调试命令/参数
BIOS/UEFI 黑屏无输出 固件文件缺失或路径错误 -bios /path/to/bios.bin
BIOS/UEFI 循环重启 引导设备未找到 -boot order=c 指定引导顺序
内核加载 Kernel panic - not syncing 内核镜像不兼容或损坏 file bzImage 检查格式
内核加载 Early #PF 异常 页表初始化失败 -d cpu_reset,int 跟踪异常
根文件系统 VFS: Unable to mount root fs root= 参数错误或驱动缺失 root=/dev/vda1 rootfstype=ext4
根文件系统 init 崩溃或未找到 init 文件缺失或权限错误 init=/bin/bash 绕过
核心要点: QEMU 启动调试的本质是 观察 + 断点 + 日志。通过 -d 系列日志观察事件流,通过 GDB 断点深入关键函数,通过串口输出捕获内核消息。掌握这三个工具,即可系统性地定位任意启动阶段故障。