时间轴
时间轴
2025-11-23
- init
本文介绍了QEMU硬件建模的核心机制与实现流程,详细阐述了虚拟机Machine创建的六个阶段、以PL011串口和edu设备为例的外设建模过程(涵盖QOM类型系统、MMIO回调、DMA传输及异步计算),并深入剖析了从设备触发到Guest OS响应的完整中断路由与处理流程。
环境
1 | wget https://download.qemu.org/qemu-10.1.2.tar.xz |
创建.clangd
1 | CompileFlags: |
gdb
1 | $ gdb -args ./build/qemu-system-riscv64 -M virt -device edu,id=edu1 -nographic |
Machine建模
QEMU 创建虚拟机的核心流程可分为 6 个阶段,每个阶段都涉及特定的组件和操作
命令行解析
入口函数:main() 关键操作:
- 解析 -M 参数指定机器类型(如 virt)
- 初始化 QOM(QEMU 对象模型)类型系统
- 创建全局状态机 MachineState
system/main.c
1 |
|
system/vl.c
1 | void qemu_init(int argc, char **argv) { |
Machine类型选择与注册
核心组件:MachineClass(机器类描述)
关键操作:
- 通过 select_machine() 匹配用户指定的机器类型
- 从全局类型表 type_table 查找对应的 TypeImpl
1 | // system/vl.c |
Machine对象实例化
核心组件:MachineState(机器运行时状态)
关键操作:
- 通过 QOM 的 object_new_with_class() 实例化机器
- 初始化内存、CPU 拓扑等基础属性
1 | // system/vl.c |
硬件设备初始化
核心阶段:machine_run_board_init()
关键操作:
- 调用机器专属的 init() 方法(如 RISC-V virt 的 virt_machine_init())
- 初始化 CPU、内存控制器、总线等核心组件
1 | // system/vl.c |
加速器与 CPU 初始化
核心组件:AccelState 和 CPUState
关键操作:
- 根据 -accel 参数选择 KVM/TCG 加速器
- 创建 vCPU 线程并绑定到物理 CPU
1 | // accel/accel-system.c |
启动虚拟机
最终阶段:qemu_main_loop()


system/runstate.c
1 | int qemu_main_loop(void) |
外设建模(PL011串口)
PL011 串口在 QEMU 中的建模完整展示了硬件外设模拟的核心流程:
- 类型系统集成:通过 QOM 实现设备类的继承体系
- 状态精确建模:寄存器、FIFO、中断状态的全周期管理
- 硬件接口实现:MMIO 回调处理 CPU 访问,字符后端对接宿主 I/O
- 中断系统集成:与平台中断控制器 (PLIC) 协同工作
- 设备生命周期:实现重置、迁移、销毁等完整生命周期

设备类型定义
核心机制:QEMU Object Model (QOM)
关键操作:
- 定义 TypeInfo 结构体描述设备类型
- 实现类初始化 (class_init) 和实例初始化 (instance_init)
- 注册到全局类型系统
1 | // hw/char/pl011.c |
设备状态结构设计
核心组件:PL011State
关键字段:
- 寄存器状态 (lcr, imsc, 等)
- FIFO 缓冲区
- 中断信号线
- 字符后端 (CharBackend)
1 | // hw/char/pl011.c |
设备类初始化
核心任务:
设置设备属性 (properties)
绑定 realize 和 reset 方法
定义设备迁移支持
1 | // hw/char/pl011.c |
设备实例化与硬件连接
核心操作:
- 初始化内存区域 (MemoryRegion)
- 注册 MMIO 回调函数
- 连接中断信号
- 绑定字符设备后端
1 | // hw/char/pl011.c |
实现设备功能逻辑
核心组件:
- MMIO 读写回调
- 中断生成逻辑
- FIFO 管理
- 字符设备通信
1 | static void pl011_write(void *opaque, hwaddr offset, |
中断处理与状态更新
1 | static void pl011_update(PL011State *s) |
一个简单 PL011 设备建模的代码示例
1 | /* hw/char/pl011.c - PL011 UART 完整实现 */ |
在 RISC-V virt 机器中实例化 PL011:
1 | /* hw/riscv/virt.c - 机器初始化 */ |
关键实现要点
寄存器精确建模:
- 实现所有 32 个寄存器的精确行为
- 处理特殊位(如 FIFO 使能位、中断屏蔽位)
- 支持波特率计算(IBRD/FBRD)
FIFO 状态机:
1 | static void fifo_push(PL011State *s, uint32_t value) { |
时钟域处理:
- 实现 pl011_clock_update 回调
- 处理波特率变化事件
- 支持动态时钟调整
迁移支持:
1 | cstatic const VMStateDescription vmstate_pl011 = { |
性能优化:
- 使用位操作代替除法计算波特率
- FIFO 操作用掩码替代取模运算
- 中断状态缓存减少计算次数
这种建模方式使 PL011 在 QEMU 中能精确模拟真实硬件行为,支持从裸机程序到 Linux 内核的全栈开发。
外设建模(edu设备)
在计算机系统中,CPU 与外设的交互本质是 地址访问 和 中断通知:
- 地址访问:CPU 通过 MMIO(内存映射 I/O)或 PIO(端口 I/O)读写设备寄存器
- 中断通知:外设通过中断控制器向 CPU 发送事件信号
1 | // 伪代码:CPU 与设备交互流程 |
edu 设备介绍
edu 是 QEMU 官方提供的教学用虚拟设备,其功能设计精简却完整:
- 基础功能:实现阶乘计算(fact 寄存器)
- 高级功能:DMA 传输、中断触发
- 典型应用:演示 PCI 设备全生命周期管理
定义设备属性
关键点:通过继承 PCIDevice 自动获得 PCI 配置空间管理能力。
1 | struct EduState { |
初始化 PCI 配置空间
1 | static void edu_class_init(ObjectClass *class, const void *data) |
此步骤让操作系统能识别设备为 “Edu Device”
映射 MMIO 区域
1 | static void pci_edu_realize(PCIDevice *pdev, Error **errp) |
地址布局:
1 | 0x00 : 阶乘输入寄存器 (可写) |
实现 MMIO 回调函数
以阶乘计算为例:
1 | static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size) |
实现后台计算线程
关键设计:避免阻塞主线程,通过条件变量实现异步计算。
1 | /* |
中断触发机制
1 | static void edu_raise_irq(EduState *edu, uint32_t val) |
DMA 传输实现
1 | static void edu_dma_timer(void *opaque) |
客户机视角的设备交互
当客户机程序的驱动访问 edu 设备时:
- 探测设备:通过 PCI ID 0x1234:11e8 识别
- 映射 MMIO:ioremap() 获取寄存器虚拟地址
- 计算阶乘
- 处理中断:在中断服务例程中清除状态标志
要点总结
edu 浓缩了外设建模的 5 大核心要素:
1 | 寄存器操作 → 中断通知 → DMA 传输 → 多线程协同 → PCI 规范 |
主要涉及:
- 状态机模型:通过寄存器位表示设备状态(如计算中/完成)
- 异步处理:耗时操作移交后台线程,避免阻塞 VCPU
- 地址隔离:每个设备拥有独立 MMIO 空间
- 中断抽象:支持传统 INTx 和现代 MSI 两种模式
- DMA 安全:地址校验防止虚拟机逃逸
中断建模

- 设备层:PL011 根据硬件状态触发中断信号
- 中断控制器层:PLIC 实现优先级仲裁和中断分发
- CPU 层:RISC-V 架构响应外部中断并切换上下文
- Guest 层:客户机程序或者操作系统中断处理程序完成设备服务
下面我们按照流程进行分析:
PL011 触发中断
1 | // hw/char/pl011.c |
s->irq 连接到 PLIC 的特定中断线(如 UART0_IRQ=10),PLIC 再将中断路由到 CPU。
PLIC 接收中断
PLIC 的 GPIO 输入处理函数更新中断状态:
1 | // hw/intc/sifive_plic.c |
PLIC 仲裁逻辑
在 sifive_plic_update() 中实现优先级仲裁:
1 | static void sifive_plic_update(SiFivePLICState *plic) |
CPU 中断注入
通过 cpu_interrupt 设置 CPU 中断请求标志:
1 | // system/cpus.c |
- 设置 CPU_INTERRUPT_HARD 标志
- 修改 icount 强制退出当前 TB
CPU 中断检查
在 vCPU 主循环中检测中断请求:
1 |
|
RISC-V 中断处理
riscv_cpu_do_interrupt 实现中断上下文切换:
1 | // target/riscv/cpu_helper.c |
Guest OS 中断处理
以 Linux 中断处理流程为例:
1 | // arch/riscv/kernel/entry.S |
调试与跟踪
开启 PLIC 调试:
1 | qemu-system-riscv64 -d int,guest_errors -D plic.log |
关键跟踪点 (trace-event)
1 | qemu-system-riscv64 --trace "trace_sifive_plic_*" |
相关函数
1 | // hw/intc/sifive_plic.c |
参考:
