CPU 虚拟化——从 trap-and-emulate 到 VT-x
CPU 虚拟化——从 trap-and-emulate 到 VT-x
本文目标:把 CPU 虚拟化的完整脉络讲透——从 Popek-Goldberg 理论、软件 trap-and-emulate、到硬件辅助 VT-x,再到 KVM 的工程实现。读完你能回答"为什么 x86 早期不能做高效虚拟化"“VT-x 的 root/non-root 是什么”“VMCS 存什么”“一次 VM-Exit 的开销和优化”“嵌套虚拟化怎么实现”。
一、要解决的问题:一个物理 CPU 怎么变成多个虚拟 CPU
CPU 虚拟化的本质是:让多个 Guest OS 以为自己在独占一个物理 CPU,实际由 hypervisor(VMM)时分复用一个或多个物理核。
核心难点在于"特权指令"。Guest OS 内核要执行 hlt(停 CPU)、cli(关中断)、改 CR3(切页表)、lgdt(加载 GDT)这些指令——这些指令一旦真在物理 CPU 上执行,会真的关掉中断、真的换掉页表、真的停下 CPU,整机就乱了。所以必须让 Guest 不能直接执行这些指令,或执行后能被截获交给 VMM 处理。
这就是 1974 年 Popek-Goldberg 虚拟化定理的核心:
一个体系结构要支持虚拟化,所有敏感指令必须是特权指令(即能被 trap 陷入 VMM 模拟)。
满足这条的架构叫"可虚拟化架构"(如 IBM PowerPC、MIPS)。x86 早期不满足,下面讲它怎么"先天不足"以及硬件怎么补回来。
二、软件方案:trap-and-emulate(陷入再模拟)
2.1 基本思想
让 Guest OS 运行在非 ring0(如 ring1 或 ring3),特权指令一执行就自动 trap(陷入)到 ring0 的 VMM,VMM 模拟这条指令的语义后返回 Guest。
这套"运行在非最高特权级、敏感指令触发陷入、VMM 模拟"的模型叫 trap-and-emulate,也叫软件虚拟化。
2.2 x86 的先天缺陷:敏感非特权指令
Popek-Goldberg 要求"敏感指令都是特权指令"。但 x86 有约 17 条敏感指令不是特权指令——它们在 ring1/ring3 执行也不会触发 trap,而是默默执行或返回错误结果。典型代表:
| 指令 | 问题 |
|---|---|
POPF/POPFD(弹标志) |
在非 ring0 执行时不恢复 IF(中断标志),Guest 关中断的语义丢失 |
PUSHF(压标志) |
读不到真实 IF |
SGDT/SIDT/SLDT/SMSW/STR |
在非 ring0 也能读 GDT/IDT/LDT/状态字地址,泄露 host 信息 |
CPUID |
直接返回真实 CPU 信息 |
CLTS(清 TS 位) |
部分场景不 trap |
这些指令"静默执行"破坏了 trap-and-emulate 的前提——Guest 跑了敏感操作 VMM 却截获不到,要么信息泄露、要么语义错误、要么直接崩溃。所以早期 x86 在理论上是"不可虚拟化"的。
2.3 三种软件绕过方案(历史)
为绕过这个缺陷,早期 VMM 用三类技术:
- 二进制翻译(Binary Translation, BT)——VMware 的看家本领。在 Guest 代码执行前,扫描并把敏感指令翻译成等价的、能 trap 或安全执行的代码块。开销大、实现复杂,但能做到"全虚拟化"(Guest 不用改)。
- 半虚拟化(Para-Virtualization, PV)——Xen 的路线。直接改 Guest 内核,让它知道自己活在虚拟机里,主动用 hypercall(类似系统调用,但陷 VMM)替代敏感指令。性能好但要改内核,Linux 早期有 Xen 的 pvops 分支。
- 限制 Guest 特权级 + 模拟:妥协方案,性能/兼容性折中。
面试金句:“软件虚拟化有 BT(全虚拟化、Guest 不改但 VMM 复杂)和 PV(半虚拟化、改 Guest 用 hypercall、性能好但侵入)两条路;它们的存在就是因为 x86 没满足 Popek-Goldberg。直到 VT-x 出现,硬件把缺口补上,才有了今天的硬件辅助虚拟化。”
三、硬件辅助:Intel VT-x / AMD-V
2005 年起 Intel 和 AMD 分别加入硬件虚拟化扩展,直接在 CPU 层面补上 Popek-Goldberg 的缺口,从此 x86 可原生高效虚拟化,软件 trap-and-emulate 和 BT 退出主流。
3.1 核心思想:两套特权级——root 与 non-root
VT-x 在原 ring0~3 之上再加一层维度,引入两种操作模式:
- VMX Root Mode(root):hypervisor/VMM 跑在这里,拥有对所有资源的完全控制。
- VMX Non-Root Mode(non-root):Guest 跑在这里,即使 Guest 在 ring0,它的特权也是"被限制的 ring0"——能管 Guest 自己,但敏感操作会触发 VM-Exit 回 root。
关键点:Guest 的 ring0 不等于物理的 ring0。Guest 内核以为自己握有最高特权,实际被困在 non-root 里,任何敏感指令/事件都会"破壳"回到 root 的 VMM。这就解决了"敏感非特权指令不 trap"的老问题——现在不管什么敏感指令,都在 non-root 下被强制陷入。
3.2 VM-Entry 与 VM-Exit
- VM-Entry:VMM 执行
VMLAUNCH/VMRESUME,从 root 切到 non-root,开始跑 Guest。CPU 状态从 VMCS 的 Guest-state 区加载。 - VM-Exit:Guest 执行敏感指令、发生异常、外部中断、IO 等"需要 VMM 介入"的事件时,CPU 自动切回 root,把控制权交还 VMM。CPU 把 Guest 现场存进 VMCS 的 Guest-state,并加载 Host-state。
这套 VMLAUNCH/VMRESUME → 跑 Guest → VM-Exit → VMM 处理 → VMRESUME 的循环就是 KVM 跑一个 vCPU 的本质。
3.3 触发 VM-Exit 的常见原因(面试常问)
| 类别 | 举例 |
|---|---|
| 敏感指令 | hlt、cli、lgdt、mov CR3、wrmsr |
| 异常 | Guest 内的 #PF、#GP 等(可设是否注入回 Guest) |
| 外部中断 | 物理设备中断到达,需 VMM 分发 |
| IO 访问 | Guest 访问 IO 端口/MMIO(默认 trap) |
| 主动 hypercall | Guest 主动 VMCALL 请求 VMM 服务 |
| EPT 相关 | EPT Violation(GPA 未映射) |
| 控制位触发 | 预设的"无条件 exit"控制位 |
VMM 可通过 VM-Exit Control 字段精细配置哪些事件触发 exit,做性能权衡——exit 越少越快,但要让 Guest 更多地"自跑"。
四、VMCS:虚拟机的"控制台"
VMCS(Virtual Machine Control Structure) 是 VT-x 的核心数据结构——每个 vCPU 一个 VMCS,是一块 4KB 对齐的内存区域,由硬件识别,存三类信息:
- Guest-state area:Guest 的寄存器现场(通用寄存器、段寄存器、CR/DR、RFLAGS、RIP 等)。VM-Exit 时 CPU 把 Guest 现场存进来,VM-Entry 时从这里恢复。
- Host-state area:VMM 的寄存器现场。VM-Exit 时 CPU 从这里恢复 Host 状态(RIP、CR3、段等),决定"exit 后跳到 VMM 哪里执行"。
- Control fields:控制 VM-Entry/VM-Exit 行为的位域:
- VM-Execution Controls:决定 non-root 下哪些指令/事件触发 exit(如是否拦截
hlt、是否用 EPT)。 - VM-Entry Controls:进入时是否加载特定 MSR、注入事件等。
- VM-Exit Controls:退出时存哪些、是否 ack 中断等。
- 还有 VM-Exit Information:本次 exit 的原因、限定信息(如哪个 IO 端口、什么异常向量),VMM 据此分派。
- VM-Execution Controls:决定 non-root 下哪些指令/事件触发 exit(如是否拦截
VMCS 字段的读写只能用专用指令 VMREAD/VMWRITE,不能直接当内存访问。这也是 VMCS 操作开销的来源之一。
面试金句:“VMCS 是硬件为每个 vCPU 维护的一块结构,存 Guest 现场、Host 现场、和控制位;VM-Exit 把现场存进 Guest 区、从 Host 区恢复,VM-Entry 反过来。VMM 靠 VMREAD/VMWRITE 改控制位和取 exit 原因。”
五、一次 VM-Exit 的开销与优化(性能核心)
VM-Exit 是 CPU 虚拟化的性能税。每次 exit/entry 都要:
- CPU 保存 Guest 现场到 VMCS(写一堆寄存器)。
- 加载 Host 现场从 VMCS(RIP、CR3、段、栈)。
- 切换到 VMM 代码,VMM 读 exit reason(VMREAD),分派处理。
- 处理完(可能要改 VMCS 控制位),再 VMRESUME 回去。
单次开销约 1000~2000 个周期(几百纳秒)。对 IO 密集或频繁陷入的负载,exit 频率高时 CPU 大量时间花在 root/ non-root 切换上,性能塌方。
优化手段(工程重点)
- 减少 exit 次数:用控制位放掉 Guest 能自处理的(如
hlt配合 VMX preemption timer、cli/sti用 interrupt-window exiting 替代每次拦截)。 - EPT 免除内存虚拟化陷入:Guest 访问内存走 EPT 硬件翻译,不必每次访存都 exit(详见内存虚拟化篇)。
- APIC-v / virtual interrupt:硬件虚拟化本地 APIC,收发中断不 exit。
- posted interrupt:VMM 把中断"投递"给 Guest,硬件直接在 non-root 注入,免 exit。
- virtio + ioeventfd:Guest 写 virtio queue 的 kick 寄存器被 ioeventfd 捕获,KVM 不必走完整 VMM 路径,IO 性能大增。
- vCPU 绑核 + 大页:减少 TLB/调度抖动。
- VMFUNC / #VE:更细粒度的功能切换,进一步少 exit。
面试区分点:“调优 KVM 的本质就是降 exit 频率、降单次 exit 代价。” 用
perf kvm stat/kvm_stat看 exit 原因分布,热点通常是hlt、PIO、MMIO、MSR read/write,对症优化。
六、KVM:把 VT-x 用起来的工程实现
KVM 是 Linux 内核里的虚拟化框架,它和 QEMU 的分工是云虚拟化的标准答案:
- KVM(内核模块
/dev/kvm):薄薄一层,负责CPU 与内存虚拟化——用 VT-x 管 vCPU(VMCS、VM-Entry/Exit、调度 vCPU 线程)、用 EPT 管内存翻译。它把"创建 VM/跑 vCPU/注册内存区"作为 ioctl 暴露给用户态。 - QEMU(用户态进程):负责设备模拟(磁盘、网卡、显卡、串口)、IO 路径(virtio/tap)、vCPU 线程的封装与调度。一个 VM = 一个 QEMU 进程,每个 vCPU = 该进程的一个线程。
KVM 核心 ioctl(呼应操作系统接口篇)
1 | fd = open("/dev/kvm"); // 拿到 KVM fd |
KVM_RUN 内部就是那个 VMLAUNCH/VMRESUME → 跑 Guest → VM-Exit 的循环——exit 后控制权回到 QEMU 线程,它按 exit reason(IO、MMIO、MMIO virtio kick、异常)分派,处理完再 KVM_RUN。
面试金句:“KVM 管芯,QEMU 管壳。KVM 用 VT-x 做 CPU/内存虚拟化并把能力以 /dev/kvm 的 ioctl 暴露,QEMU 作为用户态进程通过这些 ioctl 驱动 KVM、并负责设备模拟和 IO 路径。” 这就是为什么"一个虚拟机是宿主机上的一个进程、一个 vCPU 是一个线程"。
七、嵌套虚拟化:在 VM 里再开 VM
要在 Guest 里跑 hypervisor(如 KVM-in-KVM、容器里跑 VM),需要L0(真硬件)支持 L1(Guest hypervisor)也跑 VMX 指令。难点:L1 执行 VMLAUNCH 时它处于 non-root,不能直接真触发硬件 VM-Entry。
思路:指令 trap + 模拟
- L0 把 L1 对 VMX 指令(
VMLAUNCH/VMREAD/VMWRITE等)的执行拦截(trap),在 L0 里软件模拟这些指令对 L2 的效果——即 L0 帮 L1 维护 L2 的"虚拟 VMCS",把 L1 想对 L2 做的 entry/exit 翻译成 L0 对 L2 的真实操作。 - 现代 Intel 有 VMCS Shadowing(
VMREAD/VMWRITE在 shadow VMCS 上直接生效少陷入)和 VMCS Shadow + VMFUNC 等硬件加速,降低嵌套开销。 - AMD 有 AVIC/nVMCB 路线,性能更好。
嵌套虚拟化性能损失通常明显(exit 翻译放大),生产一般不嵌套,但开发/测试/CI、在云 VM 里跑 Kata 容器+VM 等场景会用到。
八、AMD-V 与 ARM 的对照(横向认知)
- AMD-V(AMD-V/SVM):AMD 的等价物,用
VMRUN/VMLOAD/VMSAVE和 VMCB(类似 VMCS)做 root/non-root 切换。概念对偶 VT-x。 - ARMv8 VHE / ARMv9 RME:ARM 用 EL0~EL3 异常等级,hypervisor 跑 EL2,Guest 跑 EL0/1;VHE(Virtualization Host Extension)让 host 内核直接在 EL2 跑减少切换。Stage-2 页表等价 EPT。
跨平台共识:两套特权级 + 现场容器(VMCS/VMCB)+ 陷入再恢复 是 CPU 虚拟化的通用范式,x86/AMD/ARM 都是这个骨架,只是命名和细节不同。
九、面试速答清单
Q1:为什么 x86 早期不能高效虚拟化?
Popek-Goldberg 要求"敏感指令都是特权指令",但 x86 有约 17 条"敏感非特权指令"(如
POPF、SGDT、CPUID),在非 ring0 执行也不 trap,会静默执行或泄露 host 信息,破坏 trap-and-emulate 前提。只能靠软件 BT(全虚拟化)或改 Guest 的 PV(半虚拟化)绕过,代价大。
Q2:VT-x 的 root/non-root 是什么?解决了什么?
VT-x 在 ring 之上加一层模式维度:root 跑 VMM、non-root 跑 Guest。Guest 的 ring0 不等于物理 ring0,任何敏感指令/事件在 non-root 下都会强制触发 VM-Exit 回 root。这就把"敏感非特权指令不 trap"的缺口从硬件补上,从此 x86 可原生高效虚拟化,BT/PV 退出主流。
Q3:VMCS 存什么?VMREAD/VMWRITE 为什么慢?
每个 vCPU 一个 VMCS(4KB 结构),存三类:Guest 现场、Host 现场、控制位 + exit 信息。VM-Exit 时存 Guest 现场、加载 Host 现场,VM-Entry 反之。VMCS 字段不能当内存直接访问,只能用
VMREAD/VMWRITE专用指令逐个读写,这是 VMCS 操作开销的来源。
Q4:一次 VM-Exit 多贵?怎么优化?
约 1000~2000 周期(几百 ns)。优化围绕"降 exit 频率 + 降单次代价":用 EPT 免除访存陷入、APIC-v/虚拟中断免中断 exit、posted interrupt 硬件注入中断、ioeventfd 加速 virtio、放掉能自处理的控制位、绑核大页。用
perf kvm stat/kvm_stat看 exit 原因热点对症调。
Q5:KVM 和 QEMU 各自负责什么?
KVM 是内核模块(/dev/kvm),用 VT-x 做 CPU 和内存虚拟化,把创建 VM/跑 vCPU/注册内存以 ioctl 暴露;QEMU 是用户态进程,通过这些 ioctl 驱动 KVM,并负责设备模拟、IO 路径、vCPU 线程封装。一个 VM = 一个 QEMU 进程,一个 vCPU = 一个线程。
Q6:触发 VM-Exit 的常见原因有哪些?
敏感指令(hlt/cli/lgdt/mov CR3)、Guest 异常(#PF/#GP)、外部中断、IO/MMIO 访问、Guest 主动 VMCALL、EPT Violation、以及控制位预设的无条件 exit。VMM 通过 VM-Exit Control 精细配置哪些触发,做性能权衡。
Q7:嵌套虚拟化怎么实现?
L0 把 L1(Guest hypervisor)对 VMX 指令的执行拦截,在 L0 软件模拟——帮 L1 维护 L2 的虚拟 VMCS,把 L1 想 entry/exit L2 翻译成 L0 对 L2 的真实操作。VMCS Shadowing 等硬件特性降低陷入开销。性能损失明显,一般只用于开发/测试/CI。
十、一张图收口
1 | 软件时代: Guest(ring1/3) ─敏感指令─► trap ─► VMM(ring0)模拟 ─► 返回 |
主线一句话:CPU 虚拟化 = 让 Guest 的 ring0 不等于物理 ring0,靠"两套特权级(root/non-root) + 现场容器(VMCS) + 陷入再恢复(VM-Entry/Exit)"把 Guest 关在笼子里自由跑、敏感事件随时能被 VMM 接管。 KVM 把这套硬件能力包成 /dev/kvm 的 ioctl,QEMU 在用户态驱动它并模拟设备——这就是当代云虚拟化的全部地基。
参考资料
- Intel ® 64 and IA-32 Architectures SDM, Vol.3C, Ch.23~34(VMX 架构权威)
- AMD64 Architecture Programmer’s Manual, Vol.2(SVM/AMD-V)
- KVM 源码与文档:
Documentation/virt/kvm/,arch/x86/kvm/ - KVM Forum 历年 slides(嵌套虚拟化、性能优化)
- 《系统虚拟化:原理与实现》
- Popek & Goldberg, Formal Requirements for Virtualizable Third Generation Architectures, 1974


