当前位置:首页>Linux>[TinyLinux-004] QEMU 虚拟化调试:从启动日志到系统优化

[TinyLinux-004] QEMU 虚拟化调试:从启动日志到系统优化

  • 2026-09-24 14:59:01
[TinyLinux-004] QEMU 虚拟化调试:从启动日志到系统优化

[TinyLinux-004] QEMU 虚拟化调试:从启动日志到系统优化

系列简介:TinyLinux 是由 LabHub 发起的"大道至简"Linux 学习项目。我们通过对内核进行"剔骨"级的物理精简,旨在构建一个 100% 透明、可重现的极简实验室。

公众号:LabHub | 项目仓库:https://gitee.com/lynyujiang/tiny-linux.git


🛠 技术看板

项目
内容
实验目标
掌握 QEMU 虚拟化调试技巧,优化系统启动流程
核心知识点
QEMU 参数配置 / 串口调试 / 启动日志分析 / 性能优化
涉及脚本
scripts/run_qemu.sh
难度等级
🌟🌟🌟 (进阶)
组件版本
QEMU 6.x+ / Kernel 6.12.51
启动时间
约 1.4 秒(从开机到 Shell 提示符)

1. 知识点详注 (Technical Glossary)

  • QEMU:开源的硬件虚拟化模拟器,可模拟 x86、ARM 等多种架构。TinyLinux 使用 qemu-system-x86_64 模拟完整的 PC 硬件环境。
  • **串口控制台 (ttyS0)**:PC 传统的串行通信接口。在虚拟化环境中,串口是调试内核的最佳通道,输出稳定且不受图形界面影响。
  • earlyprintk:内核早期调试输出机制。在内核初始化极早期(内存管理、串口驱动加载前)就开始输出日志,用于定位启动失败问题。
  • initramfs/initrd:初始 RAM 文件系统,内核启动时加载的第一个根文件系统。TinyLinux 使用 cpio 格式 + XZ 压缩。
  • BogoMIPS:Linux 内核启动时计算的"虚假"性能指标,用于校准延迟循环。数值与 CPU 频率相关,无实际性能参考意义。

2. 理论背景 (Deep Theory)

2.1 为什么选择 QEMU 进行调试?

在嵌入式 Linux 开发中,调试手段有限:

  1. 无图形界面:系统可能无法启动图形界面
  2. 无网络:网络驱动可能未编译
  3. 无存储:可能没有硬盘或 Flash

QEMU 提供了完美的解决方案:

  • 串口重定向:将串口输出重定向到终端,无需显示器
  • 可重现:每次启动环境完全一致
  • 快照功能:可保存/恢复系统状态
  • 调试接口:支持 GDB 调试内核

2.2 TinyLinux 的启动流程

┌─────────────────────────────────────────────────────────┐
│  1. BIOS/UEFI (SeaBIOS) - 0.05s                         │
│     - 硬件初始化                                         │
│     - 引导加载器                                         │
├─────────────────────────────────────────────────────────┤
│  2. 内核解压 - 0.05s                                    │
│     - bzImage 解压到内存                                 │
│     - 设置页表                                           │
├─────────────────────────────────────────────────────────┤
│  3. 内核早期初始化 - 0.35s                              │
│     - early_printk 输出                                  │
│     - CPU/内存检测                                       │
│     - 中断控制器初始化                                   │
├─────────────────────────────────────────────────────────┤
│  4. 内核核心初始化 - 0.65s                              │
│     - 调度器初始化                                       │
│     - 设备驱动探测                                       │
│     - 挂载根文件系统                                     │
├─────────────────────────────────────────────────────────┤
│  5. 用户空间 init - 0.40s                               │
│     - 执行 /sbin/init                                    │
│     - 挂载 /proc /sys /dev                              │
│     - 启动 shell                                         │
├─────────────────────────────────────────────────────────┤
│  6. Shell 提示符 - 0.05s                                │
│     - 显示 TinyLinux#                                    │
└─────────────────────────────────────────────────────────┘
总计:约 1.4 秒

2.3 串口通信原理

QEMU 虚拟机                    主机终端
    │                            │
    │  ttyS0 (UART 16550A)       │
    │  波特率:115200            │
    │  数据位:8                 │
    │  停止位:1                 │
    │  校验位:无                │
    │                            │
    └─────── 串行通信 ───────────┘

串口调试的优势:

  • 稳定可靠:不依赖显卡、USB 等复杂驱动
  • 早期输出:内核启动第一行日志即可捕获
  • 双向通信:可输入命令调试系统

3. 源码与脚本深度走读 (Source Code Dive)

3.1 核心启动脚本:run_qemu.sh

#!/bin/bash
###############################################################################
# TinyLinux OS - QEMU Execution Script
###############################################################################
export LC_ALL=C LANGUAGE=C LANG=C

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Auto-load environment
if [ ! "
${TINYLINUX_ENV_LOADED}" ]; then
    source "
${SCRIPT_DIR}/../env.sh"
fi

# Find Artifacts
KERNEL_PATH="
${TINYLINUX_TARGET_DIR}/bzImage"
INITRD_PATH="
${TINYLINUX_TARGET_DIR}/rootfs.cpio.xz"

# QEMU 基础参数
QEMU_ARGS="
-m 512 -smp 1"                    # 512MB 内存,单核 CPU
QEMU_ARGS="
$QEMU_ARGS -kernel ${KERNEL_PATH}"  # 直接内核启动
QEMU_ARGS="
$QEMU_ARGS -initrd ${INITRD_PATH}"  # 加载 initrd
QEMU_ARGS="
$QEMU_ARGS -nographic -no-reboot"   # 无图形界面,崩溃不重启

# 内核启动参数
APPEND="
console=ttyS0 earlyprintk=serial,ttyS0,115200 root=/dev/ram0 rdinit=/sbin/init loglevel=7 debug panic=1"

echo "
Command: qemu-system-x86_64 $QEMU_ARGS -append \"$APPEND\""
echo "
TIP: Press 'Ctrl+A then X' to exit QEMU."

qemu-system-x86_64 $QEMU_ARGS -append "
$APPEND"

3.2 内核启动参数详解

参数
说明
console=ttyS0
将控制台输出定向到串口 ttyS0
earlyprintk=serial,ttyS0,115200
早期调试输出,波特率 115200
root=/dev/ram0
根文件系统为 RAM 磁盘
rdinit=/sbin/init
指定 init 进程路径
loglevel=7
内核日志级别(7=最高详细度)
debug
启用调试模式
panic=1
内核崩溃后 1 秒重启

3.3 常用 QEMU 调试选项

# 1. 基础调试(当前配置)
qemu-system-x86_64 -m 512 -smp 1 \
  -kernel bzImage -initrd rootfs.cpio.xz \
  -nographic -no-reboot

# 2. 启用 GDB 调试
qemu-system-x86_64 ... -s -S
# -s: 在 1234 端口监听 GDB
# -S: 启动时暂停,等待 GDB 连接

# 3. 添加虚拟硬盘
qemu-system-x86_64 ... -hda disk.img

# 4. 启用网络(用户模式)
qemu-system-x86_64 ... -netdev user,id=net0 -device e1000,netdev=net0

# 5. 多核调试
qemu-system-x86_64 ... -smp 4

# 6. 增加内存
qemu-system-x86_64 ... -m 1024

4. 配置详解 (Config & Engineering)

4.1 QEMU 版本要求

QEMU 版本
兼容性
推荐度
5.x
兼容
⭐⭐⭐
6.x
兼容
⭐⭐⭐⭐
7.x
兼容
⭐⭐⭐⭐⭐
8.x
兼容
⭐⭐⭐⭐⭐

4.2 内核配置要求

配置项
值
说明
CONFIG_SERIAL_8250
y
8250 串口驱动
CONFIG_SERIAL_8250_CONSOLE
y
串口控制台支持
CONFIG_EARLY_PRINTK
y
早期打印支持
CONFIG_BLK_DEV_INITRD
y
initrd 支持
CONFIG_RD_XZ
y
XZ 压缩 initrd

4.3 启动日志级别

loglevel
说明
使用场景
0
仅紧急消息
生产环境
4
警告及以上
一般调试
7
所有消息
深度调试
8
包含函数追踪
内核开发

4.4 常见启动问题分析

现象
可能原因
解决方案
黑屏无输出
内核参数错误
检查 console=ttyS0
Kernel panic: no init
init 路径错误
检查 rdinit= 参数
无法输入命令
串口配置错误
检查 -nographic 参数
启动后自动重启
init 脚本退出
检查 /etc/init.d/rcS

5. 工程实验步骤 (Lab Steps)

5.1 基础启动

cd /home/devhub/xlabs/tiny-linux
source env.sh
./scripts/run_qemu.sh

5.2 查看完整启动日志

# 启动后,所有日志会输出到终端
# 使用 tee 保存日志
./scripts/run_qemu.sh 2>&1 | tee boot.log

5.3 分析启动时间

# 启动日志中的关键时间点
[    0.000000] Linux version 6.12.51-tinylinux    # 内核启动
[    0.410866] Unpacking initramfs...             # 解压 initrd
[    0.674819] Run /sbin/init as init process     # init 开始
[    1.440743] TinyLinux#                         # Shell 就绪

5.4 使用 GDB 调试内核

# 终端 1: 启动 QEMU(带 GDB)
qemu-system-x86_64 -m 512 -kernel bzImage -initrd rootfs.cpio.xz \
  -nographic -s -S

# 终端 2: 连接 GDB
gdb vmlinux
(gdb) target remote :1234
(gdb) break start_kernel
(gdb) continue

5.5 自定义 QEMU 参数

# 创建自定义启动脚本
cat > scripts/run_qemu_debug.sh << 'EOF'
#!/bin/bash
source env.sh

qemu-system-x86_64 \
  -m 1024 \
  -smp 2 \
  -kernel output/target/bzImage \
  -initrd output/target/rootfs.cpio.xz \
  -nographic \
  -no-reboot \
  -append "console=ttyS0 loglevel=7 debug" \
  -monitor stdio
EOF
chmod +x scripts/run_qemu_debug.sh

5.2 观察点 (Observation Points)

启动过程中重点关注:

1. BIOS 阶段(约 0.05s)
   SeaBIOS (version 1.16.3...)
   Booting from ROM...

2. 内核版本信息
   Linux version 6.12.51-tinylinux
   Command line: console=ttyS0...

3. 内存映射
   BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
   Memory: 498316K/523768K available

4. 设备探测
   serial8250: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200)
   rtc_cmos rtc_cmos: registered as rtc0

5. initramfs 加载
   Unpacking initramfs...
   Freeing initrd memory: 996K

6. init 进程启动
   Run /sbin/init as init process

7. Shell 就绪
   TinyLinux#

5.3 避坑指南

现象
原因
解决方案
Ctrl+A X
 无法退出
终端捕获了 Ctrl+A
使用 Ctrl+A C 切换到 QEMU 控制台,再按 x
日志乱码
波特率不匹配
确保 earlyprintk 使用 115200
找不到 bzImage
路径错误
先运行 ./scripts/make_all.sh
权限错误
需要 KVM
sudo usermod -aG kvm $USER

6. 验证与重现

6.1 启动测试

./scripts/run_qemu.sh

6.2 预期启动日志(精简版)

SeaBIOS (version 1.16.3-debian-1.16.3-2)
Booting from ROM...

[    0.000000] Linux version 6.12.51 (gcc ... 14.2.0) 
#1 Sun Mar 8 12:51:17 CST 2026
[    0.000000] Command line: console=ttyS0 earlyprintk=serial,ttyS0,115200 root=/dev/ram0 rdinit=/sbin/init loglevel=7 debug panic=1
[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000001ffdffff] usable
[    0.085365] clocksource: tsc-early: mask: 0xffffffffffffffff max_cycles: 0x2df1207f605, max_idle_ns: 440795276719 ns
[    0.333567] Memory: 498316K/523768K available (6144K kernel code, 1191K rwdata, 904K rodata, 792K init, 2072K bss, 22988K reserved)
[    0.410866] Unpacking initramfs...
[    0.570831] Freeing initrd memory: 996K
[    0.672333] Freeing unused kernel image (initmem) memory: 792K
[    0.674819] Run /sbin/init as init process
[    1.440743] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x2df1207f605, max_idle_ns: 440795276719 ns

Please press Enter to activate this console.
TinyLinux#

6.3 系统验证命令

# 1. 查看内核版本
uname -a
# 输出:Linux TinyLinux 6.12.51 #1 Sun Mar 8 12:51:17 CST 2026 x86_64 GNU/Linux

# 2. 查看内存使用
free -h
# 输出:约 487MB 可用

# 3. 查看进程
ps
# 输出:init + shell 进程

# 4. 查看挂载点
mount
# 输出:proc, sysfs, devtmpfs

# 5. 查看设备
ls -l /dev/
# 输出:ttyS0, null, zero 等

6.4 对应提交

项目
信息
对应提交
9e35606c4
 (refactor: separate configuration from build scripts)
检出命令
git checkout 9e35606c4
重现步骤
source env.sh && ./scripts/run_qemu.sh
预期输出
TinyLinux#
 提示符

⚠️ 注意:Git 提交信息请执行 git log --oneline -5 获取最新提交。


7. 总结与延伸

7.1 本章收获

通过本章,我们掌握了 QEMU 虚拟化调试的核心技能:

  • ✅ 理解了 QEMU 启动参数和内核命令行
  • ✅ 学会了串口调试和日志分析
  • ✅ 掌握了 GDB 调试内核的方法
  • ✅ 能够独立排查启动问题

7.2 思考题

  1. 如何测量 TinyLinux 从开机到 Shell 就绪的精确时间?
  2. 如果要启用 KVM 加速,需要修改哪些参数?
  3. 如何在 QEMU 中模拟网络环境并测试网络功能?
  4. 如何使用 QEMU 的快照功能保存/恢复系统状态?

7.3 下一章预告

在 [TinyLinux-005] 中,我们将探索内核模块的编译和加载,让系统支持更多硬件设备。敬请期待!


🌟 互动与支持

如果你觉得这个项目对理解 Linux 底层有帮助,请不要吝啬你的支持:

  • 点赞 & 在看:点击右下角,让更多极客看到这个"活的代码教科书"。
  • 关注 LabHub:第一时间获取 TinyLinux 的最新"手术"报告。
  • Star 项目:点击 Gitee 仓库地址 给我们一个五星好评!

本文由 LabHub 团队原创,转载请注明出处。


项目地址:https://gitee.com/lynyujiang/tiny-linux.git

最新文章

随机文章