影子页表原理

影子页表(Shadow Page Table)是 KVM 在硬件不支持内存虚拟化(如 Intel EPT)时,用于实现客户机物理地址到宿主机物理地址转换的核心软件机制。其本质是 KVM 为每个客户机进程维护一套与客户机页表结构相同、但指向真实物理地址的页表,由 CPU 的 MMU 直接加载执行。

核心思想:让客户机操作系统认为自己管理着物理内存,而实际物理地址转换由 KVM 通过影子页表透明完成。客户机页表(gPT)保存客户机虚拟地址(gVA)→ 客户机物理地址(gPA)的映射;影子页表(sPT)保存 gVA → 宿主机物理地址(HPA)的映射,CPU 的 MMU 实际加载的是影子页表。

影子页表的核心思想

影子页表的核心思想可以概括为 “截获 + 模拟 + 同步” 三步:

  1. 截获:KVM 截获客户机对 CR3 寄存器的写操作,以及客户机对页表所在内存页的写操作。
  2. 模拟:KVM 根据客户机页表的结构,在宿主机内核中分配并维护一套结构相同的影子页表,将 gPA 替换为对应的 HPA。
  3. 同步:当客户机修改其页表时,KVM 捕获该修改,并同步更新对应的影子页表项,保证一致性。
关键指标:影子页表模式下,每次客户机修改页表都会触发 VM-Exit,导致性能开销。EPT 硬件加速正是为了解决这一瓶颈而设计的。
影子页表核心架构图 客户机虚拟地址 (gVA) 客户机页表 (gPT) 影子页表 (sPT) 宿主机物理地址 (HPA) CPU MMU 加载 查询 KVM 同步 映射 加载 说明:客户机页表 (gPT) 保存 gVA→gPA 映射;影子页表 (sPT) 保存 gVA→HPA 映射,由 CPU MMU 直接加载执行。

截获并模拟 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 违规(或影子页表模式下的缺页),从而捕获页表修改。
CR3 截获与模拟流程 客户机写 CR3 CPU 触发 VM-Exit KVM 读取客户机 CR3 分配影子页表 设置 VMCS CR3 VM-Entry 加载影子页表 说明:KVM 通过 VM-Exit 截获 CR3 写操作,读取客户机页表基址后,在宿主机内核中分配影子页表, 并将影子页表基址写入 VMCS。下次 VM-Entry 时,CPU 直接加载影子页表进行地址转换。

同步客户机页表与影子页表的机制

同步机制是影子页表正确性的关键。KVM 采用 “写时同步(Write-on-Sync)” 策略,即仅在客户机实际修改页表时才进行同步,避免不必要的开销。

同步流程

  1. 写保护:KVM 将客户机页表所在的内存页设置为只读(通过宿主机页表或 EPT 实现)。
  2. 捕获写操作:客户机尝试修改页表时,触发写保护异常(EPT 违规或缺页),导致 VM-Exit。
  3. 解析修改:KVM 解析客户机写操作的地址和内容,确定修改了哪个页表项。
  4. 更新影子页表:KVM 在影子页表的对应位置写入新的映射(gVA → HPA)。
  5. 恢复执行:KVM 允许客户机继续执行,并清除写保护标志(或重新设置写保护)。
性能优化:KVM 使用 “脏位(Dirty Bit)” 和 “访问位(Accessed Bit)” 来减少不必要的同步。只有被修改的页表项才需要同步,未修改的页表项可以直接复用影子页表中的缓存。

同步机制对比

同步策略 触发条件 开销 适用场景
写时同步 客户机写页表时触发 低(仅同步修改项) 大多数场景
全量同步 CR3 切换时全量重建 高(重建整个影子页表) 进程切换频繁时
惰性同步 缺页时按需同步 中等(按需填充) 内存访问稀疏时
客户机页表与影子页表同步机制 客户机页表 (gPT) gVA → gPA 页表项 A: 0x1000 → 0xA000 页表项 B: 0x2000 → 0xB000 写保护 (Write Protect) 捕获写操作 → VM-Exit 影子页表 (sPT) gVA → HPA 页表项 A: 0x1000 → 0x7000 页表项 B: 0x2000 → 0x8000 KVM 同步更新影子页表 说明:KVM 将客户机页表所在页设置为只读。当客户机修改页表时,触发写保护异常,导致 VM-Exit。 KVM 解析修改内容后,在影子页表中同步更新对应的映射项(gVA → HPA),然后恢复客户机执行。 同步粒度可以是单个页表项(写时同步)或整个页表(全量同步)。

同步过程中的关键数据结构

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),建议优先启用硬件加速内存虚拟化,避免影子页表的软件开销。