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 页表。
遍历过程是这样的:
- 从 EPTP 拿到 PML4 表的基址。
- 用 GPA 的 bits 47:39 作为索引,找到 PML4 条目。
- 从 PML4 条目拿到 PDP 表的基址。
- 用 GPA 的 bits 38:30 作为索引,找到 PDP 条目。
- 从 PDP 条目拿到 PD 表的基址。
- 用 GPA 的 bits 29:21 作为索引,找到 PD 条目。
- 从 PD 条目拿到 PT 表的基址。
- 用 GPA 的 bits 20:12 作为索引,找到 PT 条目。
- 从 PT 条目拿到最终的物理页框号(HPA)。
- 加上 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 的配置是否合理。