影子页表原理
影子页表(Shadow Page Table)是 KVM 在硬件不支持内存虚拟化(如 Intel EPT)时,用于实现客户机物理地址到宿主机物理地址转换的核心软件机制。其本质是 KVM 为每个客户机进程维护一套与客户机页表结构相同、但指向真实物理地址的页表,由 CPU 的 MMU 直接加载执行。
核心思想:让客户机操作系统认为自己管理着物理内存,而实际物理地址转换由 KVM 通过影子页表透明完成。客户机页表(gPT)保存客户机虚拟地址(gVA)→ 客户机物理地址(gPA)的映射;影子页表(sPT)保存 gVA → 宿主机物理地址(HPA)的映射,CPU 的 MMU 实际加载的是影子页表。
影子页表的核心思想
影子页表的核心思想可以概括为 “截获 + 模拟 + 同步” 三步:
- 截获:KVM 截获客户机对 CR3 寄存器的写操作,以及客户机对页表所在内存页的写操作。
- 模拟:KVM 根据客户机页表的结构,在宿主机内核中分配并维护一套结构相同的影子页表,将 gPA 替换为对应的 HPA。
- 同步:当客户机修改其页表时,KVM 捕获该修改,并同步更新对应的影子页表项,保证一致性。
关键指标:影子页表模式下,每次客户机修改页表都会触发 VM-Exit,导致性能开销。EPT 硬件加速正是为了解决这一瓶颈而设计的。
截获并模拟 CR3 / 页表基址
KVM 通过 VM-Exit 机制 截获客户机对 CR3 寄存器的所有写操作。当客户机执行 MOV CR3, reg 指令时,CPU 自动触发 VM-Exit,将控制权交给 KVM。
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 客户机写 CR3 | 客户机切换进程或修改页表基址 |
| 2 | CPU 触发 VM-Exit | VMCS 中记录退出原因(CR3 访问) |
| 3 | KVM 读取客户机 CR3 | 获取客户机页表基址(gPA) |
| 4 | KVM 分配影子页表 | 在宿主机内核中分配影子页表(HPA) |
| 5 | KVM 设置 VMCS 中的 CR3 | 将影子页表基址写入 VMCS,下次 VM-Entry 时 CPU 加载影子页表 |
注意:KVM 不仅截获 CR3 写操作,还会通过 写保护(Write Protection) 机制截获客户机对页表所在内存页的写操作。KVM 将客户机页表所在页设置为只读,任何写操作都会触发 EPT 违规(或影子页表模式下的缺页),从而捕获页表修改。
同步客户机页表与影子页表的机制
同步机制是影子页表正确性的关键。KVM 采用 “写时同步(Write-on-Sync)” 策略,即仅在客户机实际修改页表时才进行同步,避免不必要的开销。
同步流程
- 写保护:KVM 将客户机页表所在的内存页设置为只读(通过宿主机页表或 EPT 实现)。
- 捕获写操作:客户机尝试修改页表时,触发写保护异常(EPT 违规或缺页),导致 VM-Exit。
- 解析修改:KVM 解析客户机写操作的地址和内容,确定修改了哪个页表项。
- 更新影子页表:KVM 在影子页表的对应位置写入新的映射(gVA → HPA)。
- 恢复执行:KVM 允许客户机继续执行,并清除写保护标志(或重新设置写保护)。
性能优化:KVM 使用 “脏位(Dirty Bit)” 和 “访问位(Accessed Bit)” 来减少不必要的同步。只有被修改的页表项才需要同步,未修改的页表项可以直接复用影子页表中的缓存。
同步机制对比
| 同步策略 | 触发条件 | 开销 | 适用场景 |
|---|---|---|---|
| 写时同步 | 客户机写页表时触发 | 低(仅同步修改项) | 大多数场景 |
| 全量同步 | CR3 切换时全量重建 | 高(重建整个影子页表) | 进程切换频繁时 |
| 惰性同步 | 缺页时按需同步 | 中等(按需填充) | 内存访问稀疏时 |
同步过程中的关键数据结构
KVM 使用以下数据结构管理影子页表与客户机页表的对应关系:
- kvm_mmu_page:描述一个影子页表页,包含对应的客户机页表页的 gPA、影子页表的 HPA、以及同步状态。
- kvm_mmu_spte:影子页表项(Shadow PTE),保存 gVA → HPA 的映射,以及访问权限和脏位。
- gfn_to_pfn_cache:缓存客户机页框号(GFN)到宿主机物理页框号(PFN)的映射,加速地址转换。
实践建议:在影子页表模式下,频繁的进程切换会导致大量的 CR3 写操作和页表同步,性能下降明显。如果宿主机 CPU 支持 EPT(Intel)或 NPT(AMD),建议优先启用硬件加速内存虚拟化,避免影子页表的软件开销。