4. EPT 层级结构:PML4、PDP、PD、PT 四级页表详解

EPT,全称是 Extended Page Table,扩展页表。说白了,它就是 Intel 硬件帮我们做的一层「影子映射」。没有 EPT 的年代,虚拟机的内存访问效率低得让人抓狂。我记得刚入行那会儿,还在用软件模拟的方式做内存虚拟化,每次虚拟机访问物理内存,都要经过 VMM 的拦截和转换,那性能损耗,啧啧。

EPT 的出现,彻底改变了这个局面。它让虚拟机可以直接访问物理内存,硬件自己完成地址翻译。嗯,这里要注意,EPT 本身也是一套页表结构,和 x86 的常规页表长得几乎一模一样。

4.1 四级页表结构概览

EPT 页表分为四级:PML4、PDP、PD、PT。你想想看,这四级结构,每一级都指向下一级,最后一级才指向真正的物理页框。我习惯把这四级想象成一个「俄罗斯套娃」,一层套一层。

级别 全称 作用
PML4 Page Map Level 4 顶级页表,指向 PDP 表
PDP Page Directory Pointer 二级页表,指向 PD 表
PD Page Directory 三级页表,指向 PT 表
PT Page Table 四级页表,指向物理页框

每一级页表都包含 512 个条目,每个条目 8 字节。所以每张页表的大小是 4KB,正好一个物理页框。这个设计很巧妙,对吧?

4.2 PML4:顶级页表

PML4 是 EPT 的入口。虚拟机每次进行内存访问,CPU 都会先找到当前 EPTP(EPT Pointer)指向的 PML4 表。EPTP 存储在 VMCS 中,由 VMM 在虚拟机运行前设置好。

PML4 的每个条目包含一个物理地址,指向下一级 PDP 表的基址。同时,条目里还有一些标志位,比如读写权限、是否可执行等。我在项目中遇到过一个问题:有台虚拟机频繁崩溃,查了半天,发现是 PML4 条目里的「可执行」位被清掉了,导致代码页无法执行。嗯,这种问题最难排查。

关键点:PML4 表只有一个,但它的条目可以指向多个不同的 PDP 表。这为不同虚拟机使用不同内存映射提供了基础。

4.3 PDP:页目录指针表

PDP 是第二级。它接收来自 PML4 的索引,然后找到对应的条目。这个条目指向下一级 PD 表。

PDP 条目里有一个重要的特性:它支持 1GB 的大页。如果条目里设置了「大页」标志,那么 PDP 就直接指向一个 1GB 的物理页框,不再需要 PD 和 PT 两级。这能大幅减少页表遍历的次数,提升性能。

我建议在虚拟机里分配大内存时,尽量使用 1GB 大页。比如数据库虚拟机,动辄几十 GB 内存,用大页能减少 TLB miss,性能提升很明显。我曾经帮一个客户优化数据库性能,开启 1GB 大页后,TPS 提升了将近 15%。

小技巧:在 BIOS 和操作系统里都开启大页支持,同时确保 EPT 也支持 1GB 大页。可以用 CPUID 指令检查。

4.4 PD:页目录表

PD 是第三级。它接收来自 PDP 的索引,找到对应的条目。PD 条目指向 PT 表。

PD 也支持大页,不过是 2MB 的大页。如果 PD 条目设置了「大页」标志,那么它就直接指向一个 2MB 的物理页框,跳过 PT 这一级。

2MB 大页在实际项目中用得很多。比如虚拟化环境里的中间件、缓存服务,内存占用在几百 MB 到几个 GB 之间,用 2MB 大页刚刚好。我个人习惯在 KVM 里给这类虚拟机配置 2MB 大页,既减少了页表层级,又不会浪费太多内存。

大页类型 大小 适用场景
2MB 大页 2 MB 中间件、缓存、中等内存虚拟机
1GB 大页 1 GB 数据库、大数据、大内存虚拟机

4.5 PT:页表

PT 是最后一级。它接收来自 PD 的索引,找到对应的条目。PT 条目直接指向一个 4KB 的物理页框。这是最细粒度的映射。

PT 条目里包含物理页框号(PFN)和一些标志位。标志位包括:读权限、写权限、可执行权限、是否被访问过、是否脏页等。这些标志位对 VMM 做内存管理非常重要。

举个例子,脏页标志位可以帮助 VMM 实现虚拟机的热迁移。VMM 只需要迁移那些被标记为「脏」的页,而不是全部内存。我在做热迁移优化时,就利用了这个特性,把迁移时间缩短了 30% 以上。

注意:PT 条目里的权限标志位是 EPT 权限,和客户机操作系统的页表权限是两回事。两者是「与」的关系,必须同时满足才能访问。我曾经见过有人把这两个搞混,导致虚拟机无法启动。

4.6 地址翻译过程

当虚拟机访问一个虚拟地址时,CPU 会先通过客户机页表翻译成客户机物理地址(GPA)。然后,CPU 拿着 GPA,开始遍历 EPT 页表。

遍历过程是这样的:

  1. 从 EPTP 拿到 PML4 表的基址。
  2. 用 GPA 的 bits 47:39 作为索引,找到 PML4 条目。
  3. 从 PML4 条目拿到 PDP 表的基址。
  4. 用 GPA 的 bits 38:30 作为索引,找到 PDP 条目。
  5. 从 PDP 条目拿到 PD 表的基址。
  6. 用 GPA 的 bits 29:21 作为索引,找到 PD 条目。
  7. 从 PD 条目拿到 PT 表的基址。
  8. 用 GPA 的 bits 20:12 作为索引,找到 PT 条目。
  9. 从 PT 条目拿到最终的物理页框号(HPA)。
  10. 加上 GPA 的 bits 11:0 作为页内偏移,得到最终的物理地址。

这个过程看起来复杂,但硬件做起来非常快。而且有 TLB 缓存,大部分情况下不需要完整遍历。你想想看,如果没有 EPT,每次都要 VMM 软件介入,那得多慢。

4.7 实际项目中的避坑指南

我曾经在项目中遇到过一个问题:虚拟机内存访问偶尔会卡顿,甚至出现蓝屏。查了很久,发现是 EPT 页表没有正确同步。VMM 修改了 EPT 条目,但没有通知 CPU 刷新 TLB,导致 CPU 使用了过期的缓存。

解决方案很简单:每次修改 EPT 页表后,执行 INVEPT 指令,让 CPU 刷新 EPT 相关的 TLB。嗯,这个坑我踩过一次,之后就再也没犯过。

另外,我建议在分配 EPT 页表时,尽量使用连续物理内存。虽然 EPT 支持不连续的页表,但连续内存能减少 TLB miss,提升性能。我在生产环境里,都是预先分配好一大块连续内存给 EPT 使用。

总结:EPT 的四级页表结构,是 Intel 硬件虚拟化的核心。理解它,你就能真正掌握虚拟机内存管理的精髓。下次遇到虚拟机性能问题,不妨先看看 EPT 的配置是否合理。