4. x86地址转换机制

理解 KVM 内存虚拟化与 Stage-2 映射之前,必须先回到 x86 架构本身的地址转换机制。因为 KVM 的 EPT/NPT 并不是凭空出现的,它是在传统 x86 分页机制之上叠加的一层“第二维”转换。只有把实模式、保护模式、分页、CR3、多级页表这条主线理清,才能看懂 GVA→GPA→HPA 的两级翻译到底发生在哪一步。

一、从实模式到保护模式:地址观念的两次跃迁

x86 处理器上电复位后首先进入实模式。此时 CPU 采用“段基址 + 偏移”的方式寻址,物理地址 = 段寄存器值 × 16 + 偏移量,可寻址空间只有 1MB。实模式没有权限级别、没有分页、没有保护,任何程序都能访问任意物理内存,这也是早期 DOS 时代的运行环境。

当操作系统加载后,会通过设置 CR0 寄存器的 PE 位切换到保护模式。保护模式引入了段描述符表(GDT/LDT)、特权级(Ring 0~3)和段选择子机制。此时段寄存器不再直接存放基址,而是作为选择子索引描述符表,从中取出段基址、段限长和访问权限。保护模式解决了隔离与权限问题,但地址仍然是“段基址 + 偏移”得到的线性地址,尚未与物理地址解耦。

关键区分:实模式与保护模式的差别不在“有没有段”,而在段的含义——实模式的段是真实基址,保护模式的段是描述符索引,且受权限与限长约束。

二、分页机制:线性地址到物理地址的解耦

真正让虚拟内存成为可能的是分页机制。开启分页后,保护模式产生的线性地址(虚拟地址)不再等于物理地址,而是需要经过页表逐级翻译。x86 通过 CR0 的 PG 位开启分页,通过 CR4 的 PAE 位决定是否启用物理地址扩展(32 位下扩展到 36 位物理地址,并引入三级页表)。

分页的核心思想是把地址空间切成固定大小的页(通常 4KB),用页表记录“虚拟页 → 物理页帧”的映射。这样每个进程都能拥有独立的虚拟地址空间,物理内存可以离散分配,还能实现按需调页、写时复制、内存共享等能力。

三、CR3 寄存器与多级页表

CR3 寄存器是分页机制的“根指针”,它保存当前页表基址(更准确地说,是顶级页表所在的物理页帧号)。进程切换时,操作系统把新进程的页表基址写入 CR3,MMU 便从新的页表树开始翻译。因此 CR3 是进程地址空间的身份标识之一。

x86-64 采用四级页表:PML4 → PDPT → PD → PT。每一级表项都指向下一级表的物理基址,最后一级 PTE 指向真正的物理页帧。一个 48 位虚拟地址被拆成 5 段:9 位 PML4 索引、9 位 PDPT 索引、9 位 PD 索引、9 位 PT 索引、12 位页内偏移。这种多级结构既节省页表内存,又支持稀疏地址空间。

层级名称索引位作用
第 1 级PML447:39顶级页表,CR3 指向它
第 2 级PDPT38:30页目录指针表
第 3 级PD29:21页目录,可映射 2MB 大页
第 4 级PT20:12页表,指向 4KB 物理页
偏移Offset11:0页内偏移

四、地址转换流程与 KVM 的衔接

一次完整的地址转换流程如下:CPU 发出虚拟地址 → MMU 读取 CR3 得到 PML4 基址 → 用 PML4 索引取表项 → 逐级下探 PDPT、PD、PT → 得到物理页帧号 → 加上页内偏移得到物理地址。若某级表项无效或权限不符,则触发缺页异常(#PF),由操作系统补页或报错。

在 KVM 场景中,Guest 内部这套 CR3 + 四级页表完成的是 GVA → GPA 的转换;而 GPA 到 HPA 还需要 Stage-2(EPT/NPT)再翻译一次。也就是说,Guest 的 CR3 指向的是 Guest 物理地址空间中的页表,而 EPT 的根指针(EPTP)由 VMM 维护,指向宿主物理地址空间中的嵌套页表。两者叠加,才构成完整的 GVA→GPA→HPA 路径。

x86-64 地址转换流程与 KVM 两级翻译衔接 48 位虚拟地址(GVA) PML4 47:39 PDPT 38:30 PD 29:21 PT 20:12 Offset 11:0 CR3 → PML4 物理基址 PML4 表 PDPT 表 PD 表 PT 表 物理页帧 阶段一:Guest 内 MMU 用 Guest CR3 遍历 Guest 页表 → 得到 GPA(Guest 物理地址) 阶段二:EPT/NPT 用 EPTP 遍历嵌套页表 → 将 GPA 翻译为 HPA(宿主物理地址) 最终访问 HPA 物理内存 表项无效/权限不符 → #PF 缺页异常 GVA → GPA(Guest 页表,CR3)→ HPA(EPT 嵌套页表,EPTP)

五、常见误区与复盘要点

  • 误区一:把 CR3 当成物理地址。CR3 保存的是页表物理基址,但在 KVM 中 Guest 的 CR3 指向的是 GPA,需要 EPT 再翻译,不能直接当 HPA 使用。
  • 误区二:认为分页一定从四级开始。32 位非 PAE 是两级页表,PAE 是三级,x86-64 通常是四级,开启 5 级分页后为五级,层级取决于模式与配置。
  • 误区三:忽略大页。PD 层可直接映射 2MB 大页,PDPT 层可映射 1GB 大页,减少页表层级访问,对 KVM 性能影响显著。
  • 误区四:混淆缺页与 EPT violation。Guest 页表缺项触发 Guest #PF,由 Guest OS 处理;EPT 缺项触发 EPT violation,由 VMM 处理,两者归属不同。
排查地址转换问题时,可按“先确认当前模式(实/保护/长模式)→ 再看 CR0.PG、CR4.PAE → 检查 CR3 指向 → 逐级验证页表项 → 最后确认是否经过 EPT”的顺序推进,能快速定位是 Guest 页表问题还是 Stage-2 映射问题。
# 查看当前 CR3 与分页相关控制位(宿主机示例)
cr3 = 0x000000012a4b5000   # 顶级页表物理基址
cr0 = 0x80050033           # PG=1, PE=1
cr4 = 0x00000000001406e0   # PAE=1, PGE=1, OSXSAVE=1

# 地址拆分(48 位)
GVA = 0x00007f3c2a5b8c40
PML4_idx = (GVA >> 39) & 0x1ff
PDPT_idx = (GVA >> 30) & 0x1ff
PD_idx   = (GVA >> 21) & 0x1ff
PT_idx   = (GVA >> 12) & 0x1ff
Offset   =  GVA        & 0xfff

把这条链路记牢:实模式给的是物理地址,保护模式给的是线性地址,分页把线性地址变成物理地址,CR3 是页表树根,多级页表负责逐级下探,而 KVM 的 EPT 在 GPA 与 HPA 之间再插一层。后续理解 Stage-2 映射、影子页表、EPT 缺页处理时,都会反复回到这张地址转换全景图上。