CPU 虚拟化:执行方式、隔离与调度
一、CPU 虚拟化的执行环境与权限边界
CPU 虚拟化为客户机提供独立的处理器状态和指令执行环境,同时保留宿主对真实资源的控制。一段程序做整数加法,只需 CPU 按指令修改寄存器;操作系统内核还会切换页表、管理中断、访问设备。如果让客户机内核直接控制宿主的这些资源,它就可能覆盖宿主地址空间,或影响其他虚拟机;若把它简单当作普通用户程序运行,原本合法的内核操作又会因权限不足而失败。
一个 vCPU 表示客户机可见的一套寄存器、指令位置、特权状态和异常行为;物理 CPU 则在调度时实际执行它。客户机可以看见 4 个 vCPU,宿主仍能把这 4 个执行线程与其他任务共享调度。指令语义、资源隔离和执行机会因此是三个要分别解决的问题。
本文以 x86 系统虚拟化为主,比较软件执行与硬件辅助的实现,再跟踪一段程序中的普通计算、能力查询、设备访问和空闲操作,说明它们的处理位置与开销。
源码版本:QEMU 10.2.0、Linux 6.18。
二、解释执行、动态翻译与硬件辅助
Mermaid 源码
flowchart TB
accTitle: 解释执行、动态翻译与硬件辅助
accDescr: 客户机代码可以由解释器逐条执行语义,可以经 TCG 形成缓存的宿主代码,也可以在 KVM 配置的硬件环境中直接执行。三种方法都必须保持寄存器、权限和异常的客户机语义。
G["客户机指令与 CPU 状态"] --> I["解释执行<br/>取指 → 解码 → 更新模拟状态"]
G --> T["TCG<br/>首次翻译 → 缓存 TB → 执行宿主代码"]
G --> K["KVM + 硬件辅助<br/>执行客户机 → 需要时 VM exit"]
I --> S["保持客户机可见的寄存器、权限与异常"]
T --> S
K --> S
classDef guest fill:#e4f1ec,stroke:#438575,color:#173e34;
classDef method fill:#eaf0f6,stroke:#8198ac,color:#263c4c;
classDef semantic fill:#fff4db,stroke:#b89a55,color:#59491e;
class G guest;
class I,T,K method;
class S semantic;
1. 解释执行:逐条读懂并实现指令
最直接的方案是取出客户机指令,解码,再用宿主程序更新模拟寄存器和内存。例如客户机加法由解释器完成运算并更新客户机标志位;客户机修改页表基址,也只修改模拟状态。访问都经过软件控制,便于隔离和跨架构实现,但热点循环每次都要重新取指、解码和分派,管理成本很高。
2. TCG:把重复解码变为可复用代码
QEMU 的 Tiny Code Generator(TCG)将一段客户机指令翻译为中间表示,再生成宿主机器码,缓存为 Translation Block(TB,翻译块)。普通运算成为宿主代码,复杂操作调用实现相应语义的 helper。下一次进入相同代码和匹配的 CPU 状态时,可以复用 TB;满足条件的块还能直接连接,减少返回主循环查找的次数。
翻译要付出首次生成和缓存管理成本。客户机修改代码后,旧 TB 必须失效;中途发生异常时,还需恢复准确的客户机指令位置。TCG 因而适合不同指令集之间的执行、开发调试及缺少硬件辅助的环境,它与 KVM 在当前 QEMU 中并存。TB 状态匹配与直接连接
3. 硬件辅助:保留客户机权限,分开宿主控制
Intel VT-x、AMD-V 提供受宿主配置控制的客户机执行环境。客户机内核仍可处于自己的高权限级;处理器按虚拟化控制决定哪些行为直接完成,哪些触发 VM exit(客户机退出),把控制权交回宿主。客户机从应用进入自身内核,与离开客户机环境进入宿主,是两类不同切换。
以 Intel VT-x 为例,VMX root / non-root 区分宿主与客户机执行环境,ring 0 / ring 3 区分各自环境内的权限级。这是两个维度:
| 权限级 | VMX root:宿主环境 | VMX non-root:客户机环境 |
|---|---|---|
| ring 3 | QEMU 等宿主用户程序 | 客户机应用 |
| ring 0 | Linux 内核与 KVM | 客户机操作系统内核 |
客户机应用执行系统调用,可以在 non-root 内进入自己的 ring 0;是否同时触发退出取决于配置和具体执行事件。VM exit 则把控制权交给宿主内核。客户机 ring 0 的权限只适用于它的执行环境,不允许它自行改写宿主配置的虚拟化控制。
Mermaid 源码
flowchart TB
accTitle: VMCS 的执行现场与控制条件
accDescr: KVM 配置 VMCS。进入客户机时装入客户机状态,发生需要退出的事件后保存客户机状态并装入宿主状态,KVM 再处理退出原因。图不表示 VMCS 保存所有寄存器。
K["KVM 配置 VMCS"] --> C["执行控制<br/>拦截条件与事件控制"]
K --> G["客户机状态<br/>RIP / RSP / CR3 / 段状态"]
K --> H["宿主状态<br/>退出入口 / 宿主栈 / 地址空间"]
G -->|VM entry| V["客户机执行<br/>自身 ring 0 或 ring 3"]
C -. "决定哪些事件退出" .-> V
V -->|VM exit:保存相应现场| G
V -->|装入宿主状态| H
H --> R["KVM 处理退出原因"]
VMCS(虚拟机控制结构)把这条边界具体化:客户机区域记录进入时的执行现场,宿主区域指定退出后的执行环境,控制字段决定哪些事件需要接管。硬件负责规定的状态交接,其余通用寄存器等仍由 KVM 保存。AMD 的 VMCB 承担相应职责。理解总流程时,可以先把它们看作“执行现场+拦截规则”;具体交接见KVM 执行循环及进入与退出汇编。
KVM 在 Linux 内核中建立并维护这个执行环境,QEMU 的 vCPU 线程通过 KVM_RUN 进入它。硬件退出后,KVM 能处理的事件在内核中完成,再次进入客户机;只有需要用户态设备模型或管理参与的情况才返回 QEMU。因此 VM exit 次数与 KVM_RUN 返回次数通常不同。
三、计算、CPUID、设备读取与等待的执行示例
假设客户机先计算 3 + 4,查询 CPUID,再读取一个由 QEMU 模拟的设备寄存器,最后执行 HLT 等待工作。设备应返回 0x12345678。客户机看到同一段程序,执行这些步骤的位置则随加速器改变:
| 客户机操作 | TCG 路径 | Intel VMX / KVM 路径 |
|---|---|---|
| 计算 3 + 4 | QEMU 用户态执行翻译后的宿主代码,更新客户机状态 | 物理 CPU 在客户机环境直接执行,结果为 7 |
| 查询 CPUID | QEMU 按虚拟 CPU 配置模拟能力 | 退出到宿主内核,由 KVM 填写结果后继续客户机 |
| 读取用户态模拟的设备 | 访存辅助路径进入 QEMU 设备回调 | KVM_RUN 返回 QEMU;设备回调填写 kvm_run.mmio,下一次进入 KVM 完成读取 |
| HLT 等待 | QEMU 记录客户机停止状态,由执行循环处理等待 | 常见内核 LAPIC 路径由 KVM 轮询或阻塞,Linux 调度宿主线程 |
本例的加法结果是 7,设备读取结果是 0x12345678;选用 TCG 或 KVM 都应保持这些结果。区别在于执行每一步时,宿主做了多少翻译、退出、设备处理与调度工作。优化需要减少这些额外工作,同时保持客户机可观察的行为。QEMU 的 KVM 执行循环给出了用户态处理分支,退出总数本身无法说明哪一步最慢。
四、代码复用、退出处理与并行执行的演进
动态翻译把逐条解释中的重复工作移到首次翻译,后续通过缓存与块连接降低分派成本。硬件辅助让兼容架构的指令直接执行,并把权限隔离交给明确的硬件边界。两条路线解决的条件不同:跨架构执行仍需要翻译,硬件辅助也仍需要设备模型和异常处理。
继续优化时,TCG 用多线程执行不同 vCPU,需要同时维护客户机内存顺序、共享设备锁和翻译失效。KVM 则可用共享队列与事件通知减少设备往返,或由硬件加速部分中断处理。这些改进把工作移动到不同层次,无法消除后端处理与状态同步。
虚拟 CPU 型号还有兼容性取舍。向客户机暴露更多宿主特性,可能改善本机计算性能,却减少能接收该虚拟机的迁移目标。固定兼容的 CPU 能力集合有利于跨宿主调度,代价是不能使用其中较新机器的全部能力。
五、按执行成本评价方案
| 主要成本 | 改进着力点 | 需要一起检查的代价 |
|---|---|---|
| 取指、解码与翻译 | 复用 TB、连接热块,条件允许时采用 KVM | 代码失效、首次执行成本与架构兼容性 |
| 设备访问和频繁退出 | 批处理、virtio 队列、内核或独立后端 | 后端 CPU、通知延迟与设备状态协调 |
| 空闲与唤醒等待 | 平衡短暂轮询和阻塞,调整调度与 CPU 放置 | 尾延迟、物理 CPU 占用与其他虚拟机的公平性 |
扩展 vCPU 数量的效果取决于业务并行度与宿主可提供的运行时间。客户机内观察任务和锁等待,宿主侧观察 vCPU 线程调度、KVM 退出及设备后端;两侧数据对应同一段业务负载后,再比较吞吐、尾延迟和 CPU 消耗。客户机中的 CPU 利用率反映其自身调度视图,宿主线程消耗还可能包含设备处理和轮询。
六、进一步阅读
- QEMU TCG 的翻译块与执行循环:首次翻译、热块连接与代码失效。
- KVM vCPU 执行与退出处理:CPUID、MMIO 及 ioeventfd/irqfd 两个方向。
- KVM 嵌套虚拟化:VMCS 合成与退出转发:L1 启动 L2,以及两级虚拟机监控器对退出的处理。
- KVM vCPU 的阻塞、轮询与唤醒:短等待延迟与 CPU 成本。
- 虚拟 CPU 模型与迁移兼容性:能力暴露、运行状态与目标主机检查。
- vCPU 与 NUMA 内存放置:线程、物理页及后端的局部性。
- KVM 虚拟时钟与定时器:TSC、共享时钟页、到期通知与迁移后的时间衔接。
- 中断虚拟化:设备事件如何进入客户机。