4. QEMU RISC-V支持现状
说实话,很多初学者拿到 QEMU 第一反应就是懵。命令行参数一长串,看着跟天书似的。我刚开始接触 QEMU 那会儿也是这样,对着文档翻半天,最后还是靠反复试错才摸清楚门道。
这一章我就把 QEMU 命令行里最常用的参数掰开揉碎讲清楚。你掌握了这些,后面搭建 RISC-V 仿真环境就会顺手很多。
4.1 QEMU 命令行基本结构
QEMU 的命令行格式其实很规整。说白了就是:
qemu-system-riscv64 [选项] [参数]
每个选项前面带一个短横线或双短横线。我习惯把参数分成几类:
- 机器配置:-machine、-cpu、-smp
- 内存配置:-m
- 设备配置:-drive、-device、-netdev
- 显示与交互:-nographic、-serial、-vga
- 调试相关:-gdb、-S、-d
嗯,这里要注意:参数的顺序有时候会影响解析结果。我踩过这个坑,后面会细说。
4.2 -machine 与 -cpu 选项
这两个选项决定了你仿真的是哪块板子、哪颗芯片。它们是最基础的配置,也是最容易出错的地方。
-machine:选择目标板卡
QEMU 支持多种 RISC-V 板卡。你可以用 -machine help 查看当前版本支持哪些:
qemu-system-riscv64 -machine help
常见的 RISC-V 机器类型有:
| 机器名称 | 描述 | 适用场景 |
|---|---|---|
| virt | 通用虚拟平台 | 开发调试、裸机程序 |
| sifive_u | SiFive Unleashed 开发板 | 嵌入式 Linux 启动 |
| spike_v1.10 | Spike 模拟器兼容 | RISC-V 指令集验证 |
我个人最常用的是 virt 机器。它没有真实硬件限制,设备配置灵活,非常适合做嵌入式调试。我在项目中遇到过用 sifive_u 跑 Linux 内核,结果因为设备树不匹配折腾了两天——后来换成 virt 就顺了。
-machine dumpdtb=myboard.dtb 可以导出当前机器的设备树文件。这个文件对调试驱动非常有用。
-cpu:选择处理器核心
RISC-V 的 CPU 配置比 ARM 灵活得多。你可以指定指令集扩展、特权级别、甚至自定义 CSR。
qemu-system-riscv64 -cpu help
常用 CPU 类型:
- rv64:基础 64 位 RISC-V CPU
- sifive-u54:SiFive U54 核心(支持 S 模式)
- thead-c906:平头哥 C906 核心(支持向量扩展)
你还可以用 ? 查看某个 CPU 的详细特性:
qemu-system-riscv64 -cpu rv64,?
这会列出所有可配置的特性,比如是否启用浮点单元、是否支持压缩指令等。我曾经调试一个裸机程序,发现浮点指令执行异常,最后查出来是 CPU 默认没开 F 扩展——加上 +f 就好了。
4.3 内存配置
内存配置用 -m 选项。语法很简单:
-m 256M # 256MB 内存
-m 1G # 1GB 内存
-m 512M,maxmem=2G # 初始 512MB,最大可扩展到 2GB
你想想看,嵌入式系统里内存通常很紧张。我一般调试裸机程序用 128M 就够了,跑 Linux 内核至少 512M。如果要做图形界面或者跑大应用,建议 1G 起步。
-m 256M 但设备树里写的是 512M,导致内核启动到一半就崩了。
4.4 设备配置
设备配置是 QEMU 最强大的部分。你可以给虚拟机挂载硬盘、网卡、串口、USB 等各种外设。
-drive:存储设备
最常用的就是挂载磁盘镜像:
-drive file=rootfs.img,format=raw,if=none,id=drive0
-device virtio-blk-device,drive=drive0
这里我解释一下:-drive 定义了一个后端存储,-device 把它挂到前端总线上。这种分离设计让 QEMU 非常灵活——你可以换不同的设备类型,后端存储文件不变。
常用的存储格式:
| 格式 | 说明 | 适用场景 |
|---|---|---|
| raw | 原始镜像,无压缩 | 性能优先 |
| qcow2 | QEMU 自有格式,支持快照 | 开发调试、测试 |
| vmdk | VMware 兼容格式 | 跨平台迁移 |
我个人习惯用 qcow2 格式做开发。它支持写时复制,可以快速创建多个测试环境,而且占用的实际磁盘空间比 raw 小得多。
-netdev:网络设备
网络配置稍微复杂一点。最简单的用法是用户模式网络:
-netdev user,id=net0
-device virtio-net-device,netdev=net0
这样虚拟机就能通过宿主机的网络访问外网了。但要注意,用户模式网络下,宿主机不能主动连接虚拟机。如果需要双向通信,得用 tap 模式:
-netdev tap,id=net0,ifname=tap0
-device virtio-net-device,netdev=net0
嗯,tap 模式需要宿主机有 root 权限,而且得提前创建好 tap 接口。我在项目中调试网络协议栈时,就用的 tap 模式,配合 Wireshark 抓包,非常方便。
-serial:串口配置
嵌入式开发离不开串口。QEMU 默认会创建一个串口,映射到标准输出:
-nographic # 关闭图形界面,串口输出到终端
-serial mon:stdio # 串口和 QEMU monitor 共享终端
如果你需要多个串口,可以这样:
-serial stdio # 第一个串口
-serial tcp::4444,server,nowait # 第二个串口映射到 TCP 端口
我曾经调试一个双串口设备,一个串口打印日志,另一个串口做交互控制。用 QEMU 的 TCP 映射,可以在宿主机上开两个终端窗口分别连接,调试效率高很多。
4.5 完整命令行示例
把上面这些参数组合起来,一个典型的 RISC-V 仿真启动命令长这样:
qemu-system-riscv64 \
-machine virt \
-cpu rv64 \
-m 512M \
-smp 4 \
-nographic \
-kernel vmlinux \
-drive file=rootfs.img,format=raw,if=none,id=drive0 \
-device virtio-blk-device,drive=drive0 \
-netdev user,id=net0 \
-device virtio-net-device,netdev=net0 \
-append "root=/dev/vda console=ttyS0"
这个命令启动了一个 4 核、512MB 内存的 RISC-V 虚拟机,挂载了内核镜像和根文件系统,还配了网络。你直接复制过去,改一下文件路径就能用。
4.6 知识结构图
下面这张图把 QEMU 命令行的核心参数关系梳理清楚了:
4.7 避坑指南
最后分享几个我踩过的坑:
-device 引用的 id 必须在前面定义。比如 -device virtio-blk-device,drive=drive0 必须在 -drive ... id=drive0 之后。我刚开始写命令时经常搞反,结果 QEMU 报错说找不到设备。
-d in_asm 看指令执行日志,或者加 -d guest_errors 看设备错误。这两个选项能帮你快速定位问题。
嗯,命令行基础就讲到这里。你把这些参数练熟了,后面搭建 RISC-V 仿真环境就会得心应手。记住,多试、多查 -help,QEMU 的文档虽然啰嗦,但确实管用。