QEMU 内部机制:机器模型、请求处理与状态协调
一、QEMU 系统模拟器的职责
QEMU 系统模拟器建立客户机可见的机器,包括 CPU、内存、总线、设备和固件,并将它们连接到执行引擎及实际 I/O 后端。操作系统除了执行指令,还要发现设备、访问磁盘并接收中断;这些操作依赖确定的地址布局、设备寄存器和复位状态。采用 KVM 加速时,兼容架构的客户机指令可以在硬件上执行,QEMU 继续维护机器模型并处理需要用户态参与的设备操作。
本文以 KVM 加速、现代 virtio PCI 接口、split virtqueue 和配置了 IOThread 的 virtio-blk 为例,通过一次 4 KiB 读取介绍对象组织、设备请求、执行上下文与生命周期管理。
源码版本:QEMU 10.2.0。
二、机器接口、执行引擎与后端的分工
客户机驱动认识的是设备接口,并不需要知道数据最终存在宿主文件还是远端存储。CPU 执行引擎也不应承担每种磁盘格式的处理。QEMU 因此把三项责任分开:
| 组件 | 运行位置 | 本例中的职责 |
|---|---|---|
| 机器与 virtio-blk 设备模型 | 宿主 QEMU 进程的用户态对象 | 提供客户机看到的总线、寄存器、队列及中断接口 |
| vCPU 线程与 KVM | 线程属于 QEMU,进入宿主内核 KVM 后运行客户机 | 执行客户机 Linux 及其块设备驱动;退出后处理需用户态参与的操作 |
| raw 与文件块节点 | QEMU 进程内的块层,向宿主内核提交文件 I/O | 将磁盘偏移转换为后端操作,返回数据或错误 |
本例的一台虚拟机由一个 QEMU 进程承载,其中可以有多个 vCPU 线程和 IOThread;KVM 是宿主内核模块。客户机驱动在虚拟 CPU 上运行,QEMU 的设备模型与块层在宿主用户态执行,两者通过客户机 RAM 中的队列交换请求。
Mermaid 源码
flowchart TB
accTitle: QEMU 的机器接口、执行引擎与 I/O 后端
accDescr: QEMU 用机器与设备模型组织客户机接口,通过 KVM 或 TCG 运行客户机代码,通过块或网络后端访问实际资源。QMP 是单独的管理路径,数据请求无需逐次经过管理命令。
G["客户机<br/>内核、驱动与应用"]
subgraph QEMU["QEMU 进程"]
E["CPU 执行接口<br/>KVM 调用 / TCG 翻译"]
D["机器与设备模型<br/>地址、队列、寄存器、中断"]
B["I/O 后端<br/>块设备图 / 网络后端"]
M["QMP 管理入口<br/>配置与运行状态"]
D -->|"解释请求,提交实际工作"| B
D -. "地址与中断关系" .-> E
M -. "配置与生命周期" .-> D
M -. "启动、暂停" .-> E
end
G -->|"客户机代码"| E
G -->|"设备请求"| D
E --> H["宿主内核与物理资源<br/>CPU、内存、文件、网络"]
B --> H
classDef guest fill:#e4f1ec,stroke:#438575,color:#173e34;
classDef component fill:#eaf0f6,stroke:#8198ac,color:#263c4c;
classDef control fill:#fff4db,stroke:#b89a55,color:#59491e;
class G,H guest;
class E,D,B component;
class M control;
这种分工允许保留同一种 virtio-blk 接口,改变后端镜像格式;也允许同一机器模型选择 KVM 或 TCG。变化仍有兼容条件:后端容量、错误处理和缓存语义需要正确反映给设备,执行引擎也必须遵守机器提供的地址与中断规则。
三、一次 4 KiB 读取的请求与完成路径
设客户机从磁盘偏移 1 MiB 读取 4 KiB。驱动准备三部分共享内存:说明读哪个磁盘位置的请求头、接收 4 KiB 数据的缓冲区,以及接收成功或失败的状态字节。描述符将这些缓冲区连成一条请求,再通过 available ring 把它交给设备。磁盘偏移选择读取内容,缓冲区地址决定将结果放回客户机内存的什么位置。
Mermaid 源码
sequenceDiagram
accTitle: 从 4 KiB 读取请求到缓冲区归还
accDescr: 客户机发布头、数据和状态组成的描述符链,经 ioeventfd 通知 IOThread。QEMU 解析请求后提交块后端,等待数据完成,写回状态和 used,再按需通知客户机回收。
participant G as 客户机驱动
participant Q as QEMU 队列线程
participant B as 块层
participant S as 宿主存储
G->>G: 描述符 5 → 9 → 12<br/>发布 available 链头 5
G-->>Q: 按需 kick:ioeventfd
Q->>Q: 校验 IN、sector 2048<br/>映射 4096 B 可写缓冲区
Q->>B: 读取 1 MiB 偏移的 4 KiB
B->>S: 格式 / 协议节点提交操作
Note over Q,B: 等待 I/O 时协程让出<br/>事件循环可继续处理其他工作
S-->>B: 数据与完成结果
B-->>Q: 完成回调
Q->>Q: 写 status = OK<br/>发布 used:id 5、len 4097
Q-->>G: 按需完成通知
G->>G: 消费 used,回收描述符与缓冲区
- 接收请求。队列通知经 ioeventfd 到达对应 IOThread。QEMU 读取描述符并解析客户机缓冲区,检查命令、长度、磁盘范围和访问方向。
- 转换为后端操作。设备取得数据缓冲区列表,通过 BlockBackend 这一设备侧入口提交读取。raw 示例继续到文件节点;若是 qcow2,还需按格式元数据解析磁盘偏移,不能直接拿客户机偏移读取镜像文件。
- 等待实际数据。块层可异步执行或挂起协程,存储完成后恢复。等待期间事件循环可以处理其他工作;客户机其他 vCPU 也可继续执行。
- 归还结果。成功后数据缓冲区已填入 4 KiB,状态写为 OK,设备在 used ring 发布完成记录,并按需通知客户机。驱动据此找到原请求、使用数据并回收缓冲区。
这条路径串起了 QEMU 的四项工作:解释设备协议、访问客户机内存、调用实际后端、发布完成状态。一次通知可以对应多个完成,后端也可能返回错误。描述符字段与完成长度的计算放在virtio-blk 专篇;源码入口分别是请求解析与完成发布。
四、类型、对象与资源连接关系
前面的读取发生时,设备队列、BlockBackend 和镜像节点已经连好。创建阶段把磁盘后端关联到 virtio-blk 实例,运行阶段的处理函数再通过该实例找到后端。于是磁盘偏移 1 MiB 的读取始终落到这块磁盘自己的块图,即使另一块磁盘使用相同的设备代码,也会走它自己的资源连接。
同一台机器可以有两块 virtio 磁盘。它们共享寄存器与请求格式的实现,却各有容量、队列、后端和未完成请求。QEMU 用“类型”保存共同的方法与属性规则,用“对象实例”保存各自状态;类型继承还能复用通用设备能力。这套机制称为 QOM,设备框架 qdev 在其上管理设备与总线生命周期。
对象创建后先有默认状态,再设置属性、关联后端,通过 realize 检查配置并建立可工作的设备。因此“分配对象”和“设备准备完成”是两个阶段,错误必须在合适阶段回收已取得的资源。
连接关系也不止一类:QOM 对象树表达组成和引用,PCI 总线表达客户机拓扑;MemoryRegion 描述 RAM 或设备地址区域,AddressSpace 提供访问视图;块设备图则表达格式节点、文件或协议节点之间的数据关系。一次设备访问会跨过这些关系,单看对象树无法还原完整 I/O 路径。QOM 类型与设备生命周期
五、线程分工、执行上下文与状态同步
常见 KVM 配置中,每个 vCPU 由对应线程运行;主循环处理 QMP 和许多设备事件;IOThread 为支持的设备队列提供独立事件循环。多个设备可以共享一个 IOThread,同一设备的多个队列也可以分给不同 IOThread。设备配置与数据请求由此可能在不同线程访问,需要各自的同步规则。
AioContext 记录某个事件循环要关注的文件描述符、定时器和延迟回调。前面的 4 KiB 读取等待存储时,请求对象保留设备、队列与缓冲区的关联,协程保留尚未执行完的调用栈。完成后沿保存的关联找回原请求,再把结果写入原来的客户机缓冲区。协程让出所在宿主线程后,该线程可以推进别的请求;跨线程完成则通过事件通知、调度回调及相应同步交接。
历史设备代码常由主循环与 BQL(全局互斥锁)保护;具备 IOThread 支持的数据路径采用自己的线程约束和同步方式。增加 IOThread 不会自动使任意设备回调线程安全。配置变更尤其需要防止主线程释放对象时,另一线程仍在使用请求缓冲区或块节点。执行上下文与同步约定
六、暂停、设备移除与异步请求收尾
假设上面的读请求已交给存储,此时管理程序要求修改块设备关系。仅停止 vCPU 只能阻止客户机继续发指令,已经提交的 I/O 仍可能完成。相关路径需要停止接收新请求、等待在途请求到达安全边界,再修改状态;块层的 drain 为这类同步提供机制,完成后再恢复处理。
移除整块设备还涉及客户机驱动停止使用它。对需要客户机配合的 PCIe 设备,device_del 的成功响应表示请求被受理,DEVICE_DELETED 才标志相应移除完成。随后才能清理已无引用的后端;设备 ID、对象引用与异步请求都要按生命周期管理,不能用“命令返回了”代替完成条件。
七、从集中模拟到多线程与独立后端
QEMU 的软件翻译与设备模型让一套程序可以模拟完整机器。接入 KVM 后,兼容架构的大量指令由硬件执行,设备请求却仍可能集中于主循环。CPU 变快之后,高频 I/O 的锁等待和串行处理更容易成为瓶颈。
virtio 以共享队列减少传统寄存器模拟;IOThread 把工作从主循环分开,多队列映射进一步让一个设备使用多个执行线程。vhost、vhost-user 又把部分队列处理移入内核或外部进程,QEMU 继续组织设备配置和控制。TCG 仍服务于跨架构及模拟需求,这些路线在不同环境中并存。
每次拆分都需要新的交接规则:线程间处理同步,外部后端处理连接与共享内存生命周期,设备状态还要支持暂停、迁移和恢复。低负载或存储已饱和时,更多线程可能只增加调度与缓存竞争;必须结合实际请求深度和后端能力判断。
八、按请求经过的边界定位问题
排查读取变慢,依次检查 available 是否推进、处理线程何时消费、块后端耗时、used 何时发布,以及客户机何时获得运行机会。对比方案时保持镜像格式、缓存与持久化要求一致,同时记录吞吐、尾延迟、CPU 占用和锁等待。
QEMU 的组织方式可以概括为:设备模型维持接口语义,对象与连接关系组织资源,执行上下文推进工作,生命周期规则保证变更安全。性能改进可以改变工作的位置,但请求与状态交接仍要完整。
九、沿请求路径深入阅读
- QEMU 虚拟机启动 → QOM 与设备生命周期:机器如何建立。
- MemoryRegion 与地址空间:地址如何找到 RAM 或设备。
- virtio-blk 请求处理 → qcow2 地址映射:读写、完成与持久化边界。
- 外部快照与在线块操作:backing 链、stream/commit 方向与 mirror 切换。
- AioContext 与 IOThread:线程、协程和完成回调。
- virtqueue 数据结构与交互图:描述符、环槽和缓冲区的关系。