Day 7 · QEMU 跑起来¶
预计时长:2 小时 类型:实验为主 前置条件:完成 Day 1–6,已成功交叉编译内核
做什么¶
让交叉编译的 ARM 内核在 QEMU 中完整启动,理解从 zImage/Image 到 shell 的全过程。PenguinLab 项目提供了统一的 QEMU 启动脚本和 rootfs 构建脚本,本节将综合使用它们。
结束时你应该: - 能用一条命令启动 ARM32 / ARM64 内核 - 看懂内核启动日志的关键节点 - 能用 GDB 远程调试内核代码
要了解什么¶
1. QEMU 在嵌入式开发中的角色¶
QEMU 在嵌入式 Linux 开发中有三个核心价值:
- 快速验证:无需真机即可验证内核配置和驱动修改
- 调试能力:通过 GDB 远程调试内核代码(单步跟踪
start_kernel) - 快速迭代:从修改代码到看到结果,无需刷写固件
局限性: - QEMU virt 是虚拟平台,外设为 VirtIO 通用设备,不能测试硬件特定驱动 - 无法验证真实时序和性能 - 某些子系统(如 DMA engine、特定中断控制器)行为可能不同
2. ARM32 vs ARM64 在 QEMU 中的差异¶
| 特性 | ARM32 (vexpress-a9) | ARM64 (virt) |
|---|---|---|
| 内核镜像 | zImage(压缩) | Image(未压缩) |
| DTB | 需要 -dtb 参数 |
QEMU 自动生成(可选) |
| 串口 | ttyAMA0 (PL011) | ttyAMA0 (PL011) |
| defconfig | vexpress_defconfig | defconfig |
| 推荐用途 | imx6ull 开发对照 | 通用 ARM64 开发/学习 |
3. 最小 rootfs 的构成¶
一个能让内核启动到 shell 的最小 initramfs 需要: - BusyBox:提供 sh、ls、cat 等基本命令 - /init 脚本:内核启动后第一个执行的用户态程序 - 挂载点:/proc、/sys、/dev
PenguinLab 的 scripts/rootfs-minimal-maker.sh 自动完成 BusyBox 交叉编译和 rootfs 打包。
4. QEMU virt machine 与真实硬件对比¶
| 特性 | QEMU virt | Rockchip RK3399 | 说明 |
|---|---|---|---|
| CPU | cortex-a72 | 2×A72 + 4×A53 | 可模拟 A72 大核 |
| 串口 | ttyAMA0 (PL011) | ttyS2 (UART2) | 设备节点不同 |
| 网络 | virtio-net | r8169/fec | VirtIO 通用驱动 |
| 存储 | virtio-blk | dw-mmc/EMMC | 虚拟块设备 |
| GPIO | virtio-gpio | rk gpio | 驱动接口不同 |
关键区别: - virt machine 是虚拟平台,外设都是 VirtIO 通用设备 - 真实开发板有专用外设(GPU、NPU、专用 GPIO 控制器) - 内核启动到shell 阶段之前,流程基本一致 - 适合学习核心子系统(调度器、内存管理、驱动框架),不适合 BSP 特定代码
练习¶
练习 1:编译 ARM64 内核 + rootfs¶
# 1. 配置并编译 ARM64 内核
ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- \
LINUX_DEFCONFIG=defconfig \
./scripts/linux-action-scripts.sh config_and_build
# 2. 编译 BusyBox 并制作最小 rootfs
ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- \
./scripts/rootfs-minimal-maker.sh
# 3. 验证产物
ls out/build_latest_arm64/arch/arm64/boot/Image
ls out/rootfs_arm64/
说明:
linux-action-scripts.sh会将aarch64映射为内核的arm64架构,构建输出到out/build_latest_arm64/。
练习 2:一键启动 QEMU¶
成功的话,你会看到内核启动日志滚动,最终进入 BusyBox ash shell:
退出 QEMU:按 Ctrl+A,松开后再按 X
- [ ] 确认看到
Linux version 6.19.9的启动日志 - [ ] 确认进入了 BusyBox shell
练习 3:ARM32 vexpress 启动¶
# 1. 编译 ARM32 内核(vexpress)
LINUX_DEFCONFIG=vexpress_defconfig \
./scripts/linux-action-scripts.sh config_and_build
# 2. 编译 ARM32 rootfs
./scripts/rootfs-minimal-maker.sh
# 3. 启动 vexpress-a9
QEMU_ARCH=arm QEMU_MACHINE=vexpress-a9 ./scripts/qemu-run.sh run
- [ ] 对比 ARM32 和 ARM64 的启动日志差异
- [ ] 注意 ARM32 需要 DTB 文件,ARM64 virt 不需要手动指定
练习 4:理解启动日志¶
在 QEMU shell 中执行:
找到这些关键节点:
| 日志关键字 | 含义 |
|---|---|
Linux version 6.19.9 |
内核版本和编译信息 |
Memory: ... available |
物理内存初始化完成 |
CPU: ... |
CPU 检测和特性 |
clk: ... |
时钟子系统初始化 |
NET: Registered PF_INET |
网络协议栈注册 |
VFS: Mounted rootfs |
根文件系统挂载 |
Freeing unused kernel memory |
释放 init 段内存(内核初始化结束) |
- [ ] 记录内核从启动到 shell 的秒数(看日志时间戳)
- [ ] 找到
start_kernel是在哪一行打印的 - [ ] 找到 init 进程启动的日志行(搜索
Run /init或Starting init)
练习 5:自定义内核启动参数¶
# 开启全部调试输出
QEMU_KERNEL_CMDLINE="console=ttyAMA0,115200 debug ignore_loglevel" \
./scripts/qemu-run.sh run
# 安静模式(只看关键信息)
QEMU_KERNEL_CMDLINE="console=ttyAMA0 quiet" ./scripts/qemu-run.sh run
# 最大调试(含早期启动信息)
QEMU_KERNEL_CMDLINE="console=ttyAMA0,115200 earlyprintk=serial,ttyAMA0 debug loglevel=10" \
./scripts/qemu-run.sh run
- [ ] 用
quiet模式启动,对比正常模式的日志行数
练习 6:网络与 QEMU 交互¶
# 启用 user-mode 网络(端口转发 2222→22)
QEMU_NET=on ./scripts/qemu-run.sh run
# 在 QEMU shell 中配置网络:
ifconfig eth0 10.0.2.15
ping -c 3 10.0.2.2 # ping QEMU 虚拟网关(即 host)
user-mode 网络:QEMU 内置的 SLIRP 网络栈,无需 root 权限,不需要 TAP/bridge 配置。适合开发调试,性能较低。
练习 7:GDB 远程调试内核(高级)¶
# 终端 1:启动 QEMU,等待 GDB 连接(-s 监听 1234 端口,-S 启动时暂停)
QEMU_EXTRA_OPTS="-s -S" ./scripts/qemu-run.sh run
# 终端 2:启动 GDB
aarch64-linux-gnu-gdb third_party/linux/vmlinux
GDB 命令:
(gdb) target remote :1234 # 连接 QEMU
(gdb) break start_kernel # 在 start_kernel 设断点
(gdb) continue # 继续执行,会在 start_kernel 停下
(gdb) bt # 查看调用栈
(gdb) list # 查看源码
(gdb) next # 单步(不进入函数)
(gdb) step # 单步(进入函数)
(gdb) print init_task # 查看内核变量
- [ ] 在
start_kernel设置断点,查看完整调用栈 - [ ] 单步执行到
setup_arch,观察架构初始化流程 - [ ] 尝试在
rest_init设断点(这是内核初始化的最后阶段)
提示:如果
vmlinux不在third_party/linux/下,检查构建输出目录out/build_latest_arm64/vmlinux。
练习 8:QEMU monitor 命令¶
在 QEMU 运行时按 Ctrl+A,松开后按 C,进入 QEMU monitor:
(qemu) info version # QEMU 版本
(qemu) info status # 虚拟机状态
(qemu) info cpus # CPU 信息
(qemu) info mem # 内存映射
(qemu) info qtree # 设备树
(qemu) info qdm # 设备模型列表
(qemu) quit # 退出
从 monitor 回到 guest shell:再按 Ctrl+A,松开后按 C。
- [ ] 用
info qtree查看 virt machine 的设备层级
项目脚本参考¶
qemu-run.sh 环境变量¶
| 变量 | 默认值 | 说明 |
|---|---|---|
QEMU_ARCH |
aarch64 | 架构:arm 或 aarch64 |
QEMU_MACHINE |
virt | 机器类型:virt、vexpress-a9 |
QEMU_CPU |
cortex-a72 | CPU 型号 |
QEMU_MEMORY |
1G | 内存大小 |
QEMU_SMP |
2 | CPU 核数 |
QEMU_NET |
off | 网络:on 或 off |
KERNEL_IMAGE |
自动检测 | 内核镜像路径 |
DTB_FILE |
自动检测 | 设备树文件(ARM32 需要) |
ROOTFS |
自动检测 | initramfs 路径 |
QEMU_KERNEL_CMDLINE |
console=ttyAMA0,... |
内核启动参数 |
QEMU_EXTRA_OPTS |
无 | 额外 QEMU 参数(如 -s -S) |
常用启动组合速查¶
# ARM64 默认(推荐)
./scripts/qemu-run.sh run
# ARM64 4核 2G 内存
QEMU_MEMORY=2G QEMU_SMP=4 ./scripts/qemu-run.sh run
# ARM32 vexpress
QEMU_ARCH=arm QEMU_MACHINE=vexpress-a9 ./scripts/qemu-run.sh run
# 带网络 + GDB 调试
QEMU_NET=on QEMU_EXTRA_OPTS="-s -S" ./scripts/qemu-run.sh run
其他 QEMU 命令¶
# 查看所有支持的 machine 类型
qemu-system-aarch64 -M help
# 查看支持的 CPU 类型
qemu-system-aarch64 -cpu help
# 查看所有支持的设备
qemu-system-aarch64 -device help
延伸阅读¶
| 资料 | 具体位置 | 说明 |
|---|---|---|
| QEMU 速查手册 | document/qemu-reference.md |
完整的 QEMU 命令、网络、GDB 参考 |
| QEMU 官方文档 | https://www.qemu.org/docs/master/system/target-arm.html | ARM 系统模拟官方文档 |
| 内核文档 | Documentation/admin-guide/kernel-parameters.rst |
全部内核启动参数参考 |
| 《Linux 内核设计与实现》Robert Love | 第 16 章 | 内核调试技术 |