问题:EPT 和 VMFUNC,会产生 TLB 错误吗?
写这篇短文的原因是,最近我在尝试用 VMFUNC
指令做一些工作。VMFUNC
指令,简单来说,允许不经过 VM Exit,直接在 Guest 的 Ring 3 中切换 EPTP(Extended-Page-Table Pointer,EPT 指针);当然,为了安全,Guest Ring 3 只能在 Hypervisor 设定好的一系列 EPTP 中选择一个;利用这条指令我们可以实现高效的上下文切换,从而使 VMX 能被用到更广阔的场景中,如进程隔离,安全容器等。
然而这里有一个潜在的问题:在切换 EPTP 前后,同样的 GPA(Guest Physical Address,Guest 物理地址)可能会被映射到不同的 HPA(Host Physical Address,Host 物理地址),而且切换 EPTP 虽然不会修改 CR3 寄存器,但可能导致 CR3 指向不同的 Host 物理地址,这也就意味着 GVA(Guest Virtual Address,Guest 虚拟地址)到 GPA 之间的映射关系也可能会发生变化。这样一来,TLB 中的条目会如何呢?是会自动失效,还是需要手动失效?如果需要手动处理,是需要使用 INVEPT
指令,还是可以使用 INVLPG
指令?更关键的是,在 TLB 中,存储的到底是哪些映射,是 GVA 到 GPA,GPA 到 HPA,还是 GVA 到 HPA?VMFUNC
前后,哪些会受到影响?
能够回答这些问题的,只有一份文件:SDM(Software Developer’s Manual)。很幸运,SDM 中有这个问题的详细解答,也就是这篇文章的内容。
(基于 2025 年 3 月版 SDM,其他版本章节序号可能有出入)
探索:VMX 和 TLB
3 类 TLB
在 SDM Vol. 3C 30.4 Caching Translation Information 一节中,Intel 详细描述了 TLB 和 VMX 的交互方式。在和 VMX 有关的场景下,TLB 可以被分为 3 类:
- Linear mappings
- Guest-physical mappings
- Combined mappings
当 EPT 关闭时,不存在 GPA 和 HPA 的区别。此时,处理器只会创建和使用 Linear mappings,它存储的是 GVA 到物理地址的映射信息,关联的是 Guest 的页表。
当 EPT 开启时,处理器只会创建和使用 Guest-physical mappings 和 Combined mappings。Guest-physical mappings 存储的是 GPA 到 HPA 的映射信息,关联的是 EPT 页表;Combined mappings 则存储 GVA 到 HPA 的映射信息,关联的是 Guest 页表和 EPT 页表。
得到了这个信息,我们就可以回答上面倒数第二个问题了:TLB 中不会存储 GVA 到 GPA 的映射信息,会存储 GVA 和 GPA 到 HPA 的映射信息。由于使用 VMFUNC
时 EPT 必然开启,因此 TLB 中必然包含 Guest-physical mappings 和 Combined mappings 两类。
不过,剩下的问题还是没有得到解答,在通过 VMFUNC
切换 EPTP 时,TLB 中的条目会如何呢?SDM Vol. 3C 30.1 Virtual Processor Identifiers (VPIDS) 中有着进一步的线索。
PCID 与 VPID
熟悉 TLB 的同学应当知道,TLB 中的项是可以和 PCID(Process Context ID,进程上下文 ID)关联的,这样在切换进程时就无需失效 TLB 中的所有项了,只需要修改 PCID 即可。
同样地,VMX 也提供了 VPID(Virtual Process ID,虚拟进程 ID)。VPID 是 VMCS 中的一个字段,可以用以标识不同的虚拟 CPU。在上面说到的三类 TLB 项中:
- Linear mappings 和 PCID 以及 VPID 关联;
- Guest-physical mappings 和 EPTRTA(EPT Root Table Address,即 EPTP 中 EPT 基址部分)关联;
- Combined mappings 同时和 PCID、VPID 以及 EPTRTA 关联。
通过以上的设计可以推测,Intel 期望中的虚拟机内存结构是:
- 单个虚拟机的多个的虚拟 CPU 共享同样的虚拟物理地址空间,即拥有同样的 EPTP;这样,不同的虚拟 CPU 之间可以共享 TLB 中的 Guest-physical mappings,因为它只与 EPTRTA 关联;
- 单个虚拟机的多个的虚拟 CPU 之间通过 VPID 进行区分;虚拟机内部的进程通过 PCID 进行区分;这样,Linear mappings 和 Combined mappings 可以区分不同的虚拟 CPU 和进程,对于虚拟机中的进程和来说,TLB 的行为和物理机是一样的。
知道了 TLB 与 VPID 和 EPTRTA 的关系,上面的问题就有了答案:VMFUNC
会切换 EPTP,所以 EPTRTA 必然会发生变化;而在开启了 EPT 的时候,TLB 中可能存在的 Guest-physical mappings 和 Combined mappings 都和 EPTRTA 关联;因此在 VMFUNC
后,TLB 中的原有条目并不会被命中,也就不需要手动失效了。
剩余的细节:VMFUNC
的文档怎么说
行至此处,我们似乎漏掉了一件重要的事情:VMFUNC
指令的文档还没有出场呢。那中间会不会有着什么重要的细节呢?在 SDM Vol. 3C 27.5.6.3 EPTP Switching 中,果然有着一条和 TLB 相关的说明:
如果 VMCS 中没有开启 VPID,VMFUNC
指令会失效 TLB 中所有和 VPID 0(代表 VPID 未启用)相关的 Combined mappings,无论其 PCID 和 EPTRTA 是什么。
我推测背后的原因是,如果 VPID 未开启的话,Combined mappings 相当于只与 PCID 和 EPTRTA 关联;如果虚拟机中的进程被迁移到另一个虚拟 CPU 上,但那个虚拟 CPU 又恰巧调度到了当前的物理 CPU 上,那么 Combined mappings 中的条目就会被命中;这个行为是不符合物理机的行为的,因此 Intel 选择了失效所有的 Combined mappings。至于 Guest-physical mappings 本身就是在不同的虚拟 CPU 之间共享的,因此不需要失效。至于这个推测是否正确,尚且不得而知。
答案:无需忧心,但需要小心
经历了难忘的文档阅读后,我们终于得到了一个结论:
- 使用
VMFUNC
切换 EPTP 时,并不需要特意操心 TLB 条目的问题;- 这是因为,
VMFUNC
一定会导致 EPTRTA 的变化,而 EPT 开启时,TLB 中的条目一定是和 EPTRTA 关联的; - 因此,
VMFUNC
后,TLB 中原有的条目不会被命中;
- 这是因为,
- 但是,如果要避免
VMFUNC
指令失效 TLB 中的 Combined mappings,导致潜在的性能损失,则需要开启 VPID;