第八篇讨论了 SBI 在 RISC-V Guest 中如何被虚拟化。本篇回到 QEMU:
-machine virt到底如何把 RAM、CPU、UART、virtio、interrupt controller 和 device tree 组织成 Guest Linux 眼中的一台 RISC-V 机器?
1. 本篇要回答的问题
启动 RISC-V Linux guest 时,我们经常写:
qemu-system-riscv64\-machinevirt\-m1G\-smp4\-nographic\-kernelImage\-append"console=ttyS0"其中-machine virt不是一句泛泛的“虚拟机器”,而是 QEMU 里一个具体的 RISC-V machine model。
它要回答的问题是:
Guest Linux 启动时看到的那台 RISC-V 机器,是 QEMU 如何描述、摆放和交付的?
2. machine model 是什么
QEMU 不是只模拟 CPU。
它要提供一整台机器的外观:
- RAM
- CPU hart
- interrupt controller
- timer
- UART
- virtio-mmio 设备
- flash/ROM
- firmware
- kernel/initrd 放置位置
- device tree
这些对象加起来,才构成 Guest Linux 能启动的“平台”。
可以把 QEMU machine model 理解成一张虚拟硬件原理图。
真实板卡靠硬件手册描述设备,QEMUvirtmachine 靠代码和 device tree 描述设备。
3.virtmachine 的基本布局
QEMU RISC-Vvirtmachine 通常会定义一套固定或可配置的地址布局。
简化模型:
Guest physical address space 0x00000000 +-------------------+ | ROM / firmware | +-------------------+ | CLINT / AIA | +-------------------+ | PLIC / interrupt | +-------------------+ | UART | +-------------------+ | virtio-mmio | +-------------------+ 0x80000000 | RAM | | kernel / initrd | | device tree | +-------------------+实际地址会随 QEMU 版本和配置变化,但思路不变:
QEMU 把一组设备放进 guest physical address space,再告诉 Guest Linux 这些设备在哪里。
4. CPU hart 如何创建
RISC-V 多核里常说 hart。
-smp 4会让 QEMU 创建多个 guest hart。
每个 hart 对 Guest Linux 来说像一个 CPU。
在 KVM 模式下,hart 通常对应 vCPU。
Guest hart 0 <-> KVM vCPU 0 <-> QEMU vCPU thread 0 Guest hart 1 <-> KVM vCPU 1 <-> QEMU vCPU thread 1 Guest hart 2 <-> KVM vCPU 2 <-> QEMU vCPU thread 2 Guest hart 3 <-> KVM vCPU 3 <-> QEMU vCPU thread 3Device tree 中的 CPU 节点会告诉 Guest Linux 有哪些 hart、hart id 是什么、支持哪些 ISA 扩展。
5. RAM 如何交给 Guest
-m 1G告诉 QEMU 给 guest 准备 1GB RAM。
QEMU 会创建对应 memory region,并把它放到virtmachine 的 RAM 地址范围中。
在 KVM 模式下,QEMU 还会把这段内存注册给 KVM,成为 memory slot。
QEMU memory region | | if KVM: KVM_SET_USER_MEMORY_REGION v KVM memory slot | v Guest RAM in GPA spaceGuest Linux 最终通过 device tree 的memory节点知道 RAM 起点和大小。
6. Firmware、kernel 和 initrd
RISC-V Linux 启动通常涉及 firmware/SBI,例如 OpenSBI。
QEMU 需要安排:
- firmware 从哪里开始执行
- kernel Image 放在哪里
- initrd 放在哪里
- device tree 放在哪里
- 启动参数如何传给 kernel
简化启动交接:
reset vector | v firmware / SBI | | pass hart id and DTB address v Linux kernel entryGuest Linux 不需要知道 QEMU 内部如何加载文件。
它只需要在启动入口拿到符合 RISC-V boot protocol 的参数。
7. UART 与-nographic
-nographic常常和console=ttyS0一起使用。
QEMUvirtmachine 会提供一个 UART 设备。
Guest Linux 通过 device tree 发现 UART,然后把 console 输出到这个设备。
Guest printk | v Guest UART driver | | MMIO access v QEMU UART device model | v host terminal这就是为什么串口配置正确时,guest kernel log 会出现在当前终端中。
如果早期日志依赖earlycon=sbi,则可能走 SBI console 路径,而不是完整 UART driver。
8. virtio-mmio 设备窗口
QEMUvirtmachine 常使用 virtio-mmio 给 guest 提供虚拟设备。
每个 virtio-mmio 设备会占用一段 MMIO 地址。
Guest Linux 通过 device tree 发现这些设备,然后加载 virtio driver。
例如 virtio block:
DTB virtio-mmio node | v Guest virtio-mmio driver | v Guest virtio-blk driver | v QEMU virtio-blk device model因此 device tree 里的 virtio-mmio 节点是 guest driver 和 QEMU device model 对上的关键。
9. interrupt controller
Guest 设备需要中断。
QEMUvirtmachine 会为 guest 提供中断控制器模型。
不同配置可能涉及:
- PLIC
- AIA
- IMSIC
- ACLINT/CLINT 相关组件
Device tree 会告诉 Guest Linux 中断控制器是什么、设备 interrupt line 如何连接。
virtio device completes request | v virtual interrupt controller | v Guest Linux interrupt handler中断控制器是 QEMU machine model、KVM interrupt injection 和 guest driver 之间的交汇点。
10. Device Tree 是硬件契约
RISC-V Linux 依赖 DTB 认识硬件。
QEMUvirtmachine 创建完虚拟硬件后,会生成一份 device tree,交给 guest。
DTB 通常包含:
/memory/cpus/chosen- interrupt controller 节点
- UART 节点
- virtio-mmio 节点
- reserved-memory
- initrd 起止地址
- bootargs
可以把 DTB 理解成 QEMU 写给 Guest Linux 的硬件说明书。
11.chosen节点
chosen节点常包含启动参数。
例如:
chosen { bootargs = "console=ttyS0 earlycon=sbi"; linux,initrd-start = <...>; linux,initrd-end = <...>; };-append里的内容通常会进入 bootargs。
-initrd对应的地址范围也会通过 DTB 告诉 guest。
Guest Linux 早期启动解析 DTB 后,就能知道 command line 和 initrd 位置。
12. dumpdtb:观察 QEMU 生成了什么
理解virtmachine 最实用的方法之一是 dump DTB。
qemu-system-riscv64\-machinevirt,dumpdtb=virt.dtb\-nographic\-kernelImage然后反编译:
dtc-Idtb-Odts virt.dtb>virt.dts通过virt.dts可以直接看到 QEMU 告诉 guest 的硬件布局。
这比只读文字描述更可靠。
13. QEMU 源码阅读入口
本篇对应的 QEMU 源码入口:
hw/riscv/virt.c:RISC-V virt machine 主体target/riscv/:CPU model、ISA、CSR、TCG/KVM 相关架构代码target/riscv/kvm/:RISC-V KVM accelerator 相关hw/intc/:中断控制器模型hw/char/:UART 等字符设备hw/virtio/:virtio 框架hw/block/virtio-blk.c:virtio block
阅读问题:
virtmachine 在哪里注册?- RAM base 和 device base 在哪里定义?
- CPU hart 在哪里创建?
- DTB 在哪里生成和填充?
- virtio-mmio 设备如何挂到地址空间?
- interrupt line 如何连接?
14. 本篇小结
这一篇把 QEMUvirtmachine 和 Device Tree 放在一起看。
第一,-machine virt是一个具体的 RISC-V 虚拟平台模型。
第二,QEMU 负责创建 RAM、CPU hart、UART、virtio、中断控制器等虚拟硬件。
第三,Device Tree 是 QEMU 和 Guest Linux 之间的硬件契约。
第四,Guest Linux 通过 DTB 发现内存、CPU、中断控制器和设备。
第五,读hw/riscv/virt.c时应该同时观察设备创建和 DTB 生成。
可以把本篇压缩成一句话:
QEMU
virtmachine 负责把虚拟硬件拼出来,Device Tree 负责把这台 RISC-V 机器说明白,Guest Linux 则根据这份说明书完成启动和驱动绑定。
15. 下一篇预告:virtio 与 vhost
下一篇进入 I/O 路径。
会讨论:
- virtio 为什么适合虚拟化
- virtqueue 的基本结构
- virtio-mmio 和 virtio-pci
- virtio-blk、virtio-net 的请求路径
- QEMU device model 与 host backend
- vhost-kernel 和 vhost-user 如何减少 QEMU 数据面开销