try ai
科普
编辑
分享
反馈
  • 扩展页表

扩展页表

SciencePedia玻尔百科
关键要点
  • EPT 是一项硬件特性,通过执行从客户机虚拟地址 (GVA) 到主机物理地址 (HPA) 的两阶段地址转换来加速内存虚拟化。
  • 虽然 EPT 消除了频繁且缓慢的 hypervisor 陷阱,但它为页表遍历引入了显著的延迟,这一成本在很大程度上由转译后备缓冲器 (TLB) 所缓解。
  • EPT 提供了强大的、由硬件强制执行的安全性,允许 hypervisor 设置可覆盖客户机操作系统所设权限的内存权限,从而防止未经授权的访问。
  • 该机制是现代云计算的基础,支持了虚拟机实时迁移、即时虚拟机克隆(写时复制)和细粒度资源管理等关键功能。

引言

在一台计算机中运行另一个完整、独立的操作系统是现代计算的支柱之一,从大型数据中心到本地桌面开发皆是如此。然而,这种虚拟化技术带来了一个深远的挑战:系统如何为多个隔离的客户机环境高效且安全地管理内存?早期的解决方案依赖于复杂的软件技巧,但这通常伴随着显著的性能开销。对更优方法的探索催生了硬件创新,彻底改变了这一领域,而扩展页表 (EPT) 正是其中的佼佼者。这一关键的处理器特性为内存虚拟化问题提供了一个强大的、基于硬件的解决方案。本文将深入探讨 EPT 的世界,首先在“原理与机制”部分解释其核心内容,以揭开两阶段地址转换过程、其性能影响以及它如何解决隔离难题的神秘面纱。然后,在“应用与跨学科联系”部分,我们将看到这一机制如何成为构建驱动现代云的动态、安全且可扩展系统的基础工具。

原理与机制

要真正领会现代计算机处理器的精妙之处,我们需要像魔术师一样思考。最伟大的魔术是那些能创造出无缝幻象的魔术,而在计算世界里,最宏大的幻象之一就是虚拟机——一个在另一台计算机内部运行的完整、独立的计算机。这种“戏法”是通过一系列巧妙的硬件特性实现的,而位于内存虚拟化核心的,正是一种被称为​​扩展页表​​ (EPT) 的机制,在 AMD 的术语中也称为嵌套页表 (NPT)。

内存的双体问题

想象你置身于一个巨大的图书馆。要找到一本书,你不会使用它的物理书架位置,而是使用索引卡上的目录号。这就像一个普通程序在内存中查找数据的方式。程序使用一个“虚拟”地址(目录号),处理器的内存管理单元 (MMU) 在一组称为​​页表​​的表格中查找它,以找到计算机 RAM 芯片中的实际物理地址。这是经典的单阶段转换:​​虚拟地址​​ →\to→ ​​物理地址​​。

现在,让我们增加一点复杂性。总图书管理员——我们的 ​​hypervisor​​ 或虚拟机监控器 (VMM)——同时管理着几个独立的图书馆访客(虚拟机),并且需要让他们严格隔离。管理员不能信任任何一个访客,把图书馆的主布局图交给他们。

于是,一项新规则应运而生。每个访客(一个客户机操作系统)都有自己的一套索引卡,它认为这些卡片上包含了真实的书架位置。当访客的程序使用​​客户机虚拟地址​​ (GVA) 请求数据时,访客自己的系统会查找它,并找到它认为的物理位置,我们称之为​​客户机物理地址​​ (GPA)。

然而,这个 GPA 并非最终答案。访客必须将此 GPA 交给管理员。然后,管理员会在一本主账本——扩展页表——中查找这个 GPA,以找到真正的​​主机物理地址​​ (HPA),即主板上内存芯片的实际位置。硬件会自动且无形地完成这整个两步过程,GVA→GPA→HPAGVA \to GPA \to HPAGVA→GPA→HPA。这种二维转换正是 EPT 的精髓所在。

漫漫长路:性能成本

这种两阶段过程提供了优雅的隔离,但其潜在成本可能高得惊人。在页表中查找地址的过程,称为​​页表遍历​​ (page walk),并非单一步骤。现代系统使用多级页表,它就像一棵树。为了找到一个转换关系,处理器必须沿着这棵树“遍历”,在每一级从内存中读取一个条目。如果一个客户机系统有一个 4 级页表 (Lg=4L_g=4Lg​=4),一次 GVA 查找就需要 4 次内存访问。

有了 EPT,情况变得更加戏剧化。请记住,客户机页表本身——即客户机 CPU 为执行其遍历所需读取的数据结构——位于客户机物理内存中。但管理员 (hypervisor) 控制着对真实内存的所有访问。因此,每当硬件需要从客户机页表中读取某个 GPA 处的条目时,它必须首先通过执行一次 EPT 的完整遍历来将该 GPA 转换为 HPA。

让我们来追溯这个过程。假设客户机和 EPT 都使用 4 级页表 (Lg=4L_g=4Lg​=4, Le=4L_e=4Le​=4)。客户机中的一个程序请求数据,但该转换并未被缓存。硬件必须找到答案:

  1. 要读取第一级客户机页表项 (PTE),其地址必须被转换。这需要一次完整的 4 步 EPT 遍历(4 次内存访问),然后是 1 次访问以读取客户机 PTE 本身。总计:5 次访问。
  2. 该客户机 PTE 指向第二级客户机页表。要读取它,硬件必须执行另一次 4 步 EPT 遍历,然后进行 1 次读取。总计:5 次访问。
  3. 对于客户机页表的第三和第四级,此过程会重复。每一步都耗费 5 次内存访问。
  4. 经过这样的四步(4×5=204 \times 5 = 204×5=20 次访问),硬件终于完成了 GVA→GPAGVA \to GPAGVA→GPA 的转换。现在它知道了程序想要的实际数据的 GPA。
  5. 但还没完!现在它必须将这个最终的 GPA 转换为 HPA,这需要最后一次 4 步 EPT 遍历(4 次访问)。
  6. 最后,拿到真正的 HPA 后,它才能执行实际的数据读取(1 次访问)。

加载单个数据的最坏情况总成本是令人难以置信的 (Lg×(Le+1))+(Le+1)=(Lg+1)(Le+1)(L_g \times (L_e+1)) + (L_e+1) = (L_g+1)(L_e+1)(Lg​×(Le​+1))+(Le​+1)=(Lg​+1)(Le​+1) 次内存访问。在我们的例子中,这相当于 (4+1)(4+1)=25(4+1)(4+1) = 25(4+1)(4+1)=25 次内存访问,而本应只需一次!这揭示了一个深层次的权衡。在 EPT 出现之前,hypervisor 使用一种称为​​影子页表​​ (shadow page tables) 的软件技术,即 hypervisor 创建一个特殊的页表,将 GVA 直接映射到 HPA。这使得页表遍历快得多(只需 LgL_gLg​ 步),但它要求 hypervisor 捕获并模拟客户机操作系统对其自身页表所做的任何更改——这是一个频繁且缓慢的操作。EPT 以更高的页表遍历代价消除了这些陷阱 (​​VMEXIT​​)。

终极捷径:TLB 及其虚拟化近亲

如果每次内存访问的成本都增加 25 倍,虚拟化将慢到无法使用。幸运的是,有一种称为​​转译后备缓冲器​​ (Translation Lookaside Buffer, TLB) 的硬件可以挽救局面。TLB 是 CPU 上的一个小型、极快的缓存,它存储着来之不易的最终 GVA→HPAGVA \to HPAGVA→HPA 转换结果。当下次访问同一内存页时,CPU 会在 TLB 中找到转换结果,从而绕过整个嵌套页表遍历过程。由于程序表现出引用局部性——它们倾向于重复访问相同的内存区域——TLB 的命中率非常高,平均访问时间更接近于单次内存查找。

然而,这引入了一个新问题。如果你同时运行多个虚拟机,如何将它们的 TLB 条目分开?一种天真的方法是在每次虚拟机之间的上下文切换时刷新整个 TLB,这是一个代价高昂的操作。解决方案是增加另一项魔法:​​虚拟处理器标识符​​ (VPID)。硬件会用所属虚拟机的 VPID 来标记每个 TLB 条目。在查找转换时,CPU 只考虑那些 VPID 标签与当前运行的虚拟机相匹配的条目,从而允许多个虚拟机的条目和平共存于 TLB 中。

无形的守护者:作为安全机制的 EPT

EPT 的真正美妙之处在于它不仅仅是一种转换机制,更是一种强大的安全强制执行工具。Hypervisor 不仅用地址映射填充 EPT,它还为每个页面指定了权限——读、写和执行。任何内存访问要成功,都必须同时获得客户机自己的页表和 hypervisor 的 EPT 的许可。最终的权限实际上是这两层权限的逻辑与。

想象一个行为不端或被攻破的客户机操作系统。它可能会尝试将其虚拟内存的一部分映射到一个它知道或猜测对应于 hypervisor 自身内存或其他虚拟机内存的客户机物理地址。从客户机的角度看,它创建的 GVA→GPAGVA \to GPAGVA→GPA 转换是完全有效的。然而,当硬件尝试第二阶段的转换(GPA→HPAGPA \to HPAGPA→HPA)时,它会查阅 EPT。配置了 EPT 的 hypervisor 只为它实际分配给该客户机的内存范围创建了有效的映射。当硬件查找那个被禁止的 GPA 时,它会发现一个 EPT 条目,其权限位(读、写、执行)全部设置为零。

这种不匹配不会导致普通的页错误。相反,它会触发一次 ​​EPT 违例​​ (EPT Violation),这是一个特殊的事件,会立即停止客户机并将控制权转移给 hypervisor。Hypervisor 会立即收到通知,得知客户机试图进行非法操作,并可以采取行动,例如终止该虚拟机。这种由硬件强制执行的隔离是现代云计算安全的基础。它提供了一道即使是被攻破的客户机操作系统也无法逾越的屏障。此外,这种控制的粒度非常精细。例如,hypervisor 可以使用 EPT 将某个页面标记为用户代码不可执行,即使客户机操作系统将其标记为可执行。更严格的 EPT 权限总是优先。

两种错误的故事:责任之舞

两层页表之间的相互作用,在出现问题时,引出了一场优雅的责任之舞。思考一下当一个客户机程序试图访问一个尚未加载到内存中的页面时会发生什么。从客户机的角度来看,其页表中相应的条目被标记为“不存在”。

接下来发生的事情简单而优美:

  1. 硬件开始 GVA→GPAGVA \to GPAGVA→GPA 的转换。它遍历客户机的页表,并立即发现“不存在”的条目。此时,硬件的行为与在非虚拟化系统上完全相同:它生成一个​​页错误异常​​,并将其传递给客户机操作系统。Hypervisor 不参与其中,也完全不知情。

  2. 客户机操作系统的页错误处理程序运行。它执行其正常工作:找到一帧它认为是物理内存(一个 GPA)的空闲空间,将所需数据加载进去,更新自己的页表以将该条目标记为“存在”,然后从异常中返回。

  3. 硬件自动重试原始指令。这一次,GVA→GPAGVA \to GPAGVA→GPA 的转换成功了,因为客户机 PTE 现在是存在的。这产生了一个 GPA。

  4. 现在,硬件尝试第二阶段:GPA→HPAGPA \to HPAGPA→HPA。但这是一个 hypervisor 从未见过的全新 GPA!自然,EPT 中没有它的映射。硬件对 EPT 的遍历失败。这次失败会触发一次 ​​EPT 违例​​,控制权被转移给 hypervisor。

  5. Hypervisor 的 EPT 违例处理程序被调用。它看到客户机需要一个新的物理内存页面。它分配一个真实的 HPA 帧,更新其 EPT 以将客户机选择的 GPA 映射到这个新的 HPA,然后恢复客户机的运行。

只有在第三次尝试时,内存访问才最终成功。这个序列完美地展示了关注点分离。客户机操作系统管理着它自己的虚拟世界,处理它自己的页错误。Hypervisor 管理着“真实”的物理世界,响应 EPT 违例以按需提供资源。

从分页之前遗留的段检查,到存储 EPT 本身所需的内存开销,这整个错综复杂的系统构成了一幅分层的计算机体系结构杰作。扩展页表为虚拟化内存这个难题提供了一个强大而又出人意料地优雅的解决方案,将一个潜在的性能和安全噩梦转变为现代计算的基石。

应用与跨学科联系

理解了扩展页表 (EPT) 的原理和机制后,我们可能会倾向于将其仅仅看作一种架构上的改进——一种用于加速虚拟化内存访问的技术细节。但这样做就像只看到一个齿轮,而没有看到它能构建的钟表宇宙。EPT 及其对应的嵌套页表 (NPT) 不仅仅是一项优化,它们是一个基础性的构建模块,一个强大的工具,解锁了定义现代计算的广阔能力。通过赋予 hypervisor 对客户机物理地址空间的细粒度、透明控制,EPT 将其从一个简单的管理者转变为一位总建筑师,能够动态地重塑、保护甚至迁移整个虚拟世界。在本节中,我们将探索这些应用,从巧妙的操作系统技巧到硬件安全的前沿,以见证这一机制的真正力量与美妙。

作为增强型操作系统的 Hypervisor

欣赏 EPT 最优雅的方式之一,是不仅仅将 hypervisor 视为主机,而是看作一种“元”操作系统,它执行着我们熟悉的操作系统功能,但其规模是作用于整个机器,而非单个进程。

想象一下,你想要克隆一个正在运行的虚拟机,瞬间创建一个完全相同的副本,就像 [fork()](/sciencepedia/feynman/keyword/fork()|lang=zh-CN|style=Feynman) 系统调用可以近乎即时地创建进程副本一样。复制整个内存足迹(可能多达数 GB)会慢得令人望而却步。取而代之,hypervisor 可以施展一个技巧。它为子虚拟机创建一个新的 EPT,但不指向父虚拟机内存的副本,而是指向完全相同的主机物理页面。为了防止父子虚拟机相互干扰内存,hypervisor 使用 EPT 的权限位。它在父子双方的 EPT 中都将所有共享页面标记为只读。现在,当任一虚拟机试图写入共享页面时,CPU 硬件会检测到权限违例并触发 EPT 违例,陷入到 hypervisor 中。Hypervisor 此时便知是时候行动了:它为该特定页面创建一个私有副本,更新导致错误的虚拟机的 EPT,使其指向这个新的、具有写权限的私有副本,然后恢复执行。这种被称为写时复制 (copy-on-write) 的技术,意味着页面只在绝对必要时才被复制,从而实现了近乎瞬时克隆虚拟机的神奇壮举。

这种控制也延伸到更常规的内存管理中。当客户机操作系统需要更多内存时——例如,为了增长线程的栈——它会分配其视为连续的客户机物理页面。Hypervisor 的工作是通过寻找可用的主机物理页面(这些页面可能根本不连续)来满足这一请求,并更新 EPT,为客户机制造一个连续内存块的假象。客户机分配的每一个新页面,都要求 hypervisor 创建一个相应的新的 EPT 叶子条目来建立映射关系。

反之,在云环境中,hypervisor 可能需要从一个虚拟机回收内存以分配给另一个。一个在客户机内部运行的“气球驱动程序”可以“膨胀”,即获取客户机物理页面,然后将它们返回给 hypervisor。从 hypervisor 的角度看,这意味着它必须更新 EPT 来取消映射这些被回收的页面。如果这些页面是某个由单个、高效的 2MB “大页”条目映射的大区域的一部分,hypervisor 必须进行一次精细的手术:它拆分这个大页映射,创建一个新的、包含 512 个条目(用于 4KB 页面)的低级页表,然后填充它,小心地将被回收的页面标记为不存在,同时保留所有其他页面的映射。这个操作当然需要仔细地使所有虚拟 CPU 上的转译后备缓冲器 (TLB) 失效,以确保它们不会使用过时的转换。在这场舞蹈中,hypervisor 扮演着整个系统的动态资源管理器。

性能工程的艺术

这种不可思议的灵活性并非没有代价。嵌套分页固有的二维页表遍历——先遍历客户机的页表,再遍历 EPT——为每一次未命中 TLB 的内存访问都增加了显著的延迟。在一个内存压力巨大的系统中,当客户机操作系统不断地将页面换出到磁盘时,这种开销变得尤为明显。每一次页错误都需要一次嵌套页表遍历,这为本已缓慢的从磁盘取数据的过程增加了宝贵的 CPU 时间(微秒级)。这种转换开销会显著增加客户机观察到的换入延迟,甚至可能改变物理磁盘所见的 I/O 流的时间和突发性。

然而,同样的机制也可以成为性能优化的伙伴。现代操作系统使用“透明大页” (Transparent Huge Pages, THP) 来通过其页表中的单个条目映射大至 2MB 的内存区域,从而减少页表遍历的深度。当客户机操作系统使用 THP 时,它缩短了二维遍历的第一阶段。虽然 hypervisor 仍然需要使用 512 个独立的 4KB EPT 条目来映射这个 2MB 的客户机页面,但嵌套遍历的总步数减少了。对于一次未命中 TLB 的访问,即使只从页表遍历中减少一步,也能带来显著的性能提升,尤其是在数十亿次内存访问的累积效应下。这展示了一种美妙的协同作用:客户机级别的优化和 hypervisor 的虚拟化层可以共同努力,提升整个系统的性能。

赋能现代云

也许 EPT 最显而易见的影响在于它赋能了现代云计算的核心特性:移动性和安全性。

云的一个决定性特征是​​实时迁移​​ (live migration),即能够在几乎没有可察觉的停机时间的情况下,将正在运行的虚拟机从一台物理服务器移动到另一台。这一壮举背后的魔法,再次是 EPT。使用一种迭代的“预复制” (pre-copy) 算法,hypervisor 在虚拟机仍在运行时,开始将其内存复制到目标服务器。但虚拟机在复制期间修改的页面怎么办?Hypervisor 使用了与写时复制相同的技巧:它在 EPT 中将所有已复制的页面标记为只读。客户机的任何写操作都会陷入 hypervisor,后者会注意到该页面现在是“脏”的,并将其加入一个列表,以便在下一轮中重新复制。由于网络速度通常比虚拟机弄脏内存的速度快,每一轮复制的脏页集合会越来越小。最后,当剩余的脏页集变得非常小时,hypervisor 将虚拟机暂停几十毫秒,复制最后少数几个页面和 CPU 状态,然后在目标主机上恢复它。这个完全依赖 EPT 透明跟踪写操作能力的过程,使得云服务提供商能够在不中断用户服务的情况下进行硬件维护或负载均衡。

EPT 也催生了强大的新安全模型。传统上,客户机操作系统内核是一个单一、庞大的安全域。内核中任何地方的缺陷都可能危及整个系统。有了 EPT,hypervisor 可以在单个客户机内部强制实施隔离。想象一个敏感的网络驱动程序,其内存映射 I/O (MMIO) 寄存器只应由该驱动程序本身访问。Hypervisor 可以配置 EPT,在大部分时间内拒绝读写这些 MMIO 寄存器的 GPA 范围。只有当受信任的驱动程序被调度运行时,hypervisor 才会切换到一个允许访问的不同 EPT 上下文。如果客户机内核中的恶意组件试图通过将其自己的虚拟地址重新映射到受保护的 MMIO 区域来篡改设备,该尝试将会失败。客户机的重映射会成功,但随后的内存访问将被转换为受保护的 GPA,在那里 EPT 硬件会检查权限,发现被拒绝,并陷入 hypervisor,从而挫败攻击。这将 hypervisor 变成了一名安全警卫,在客户机自己的城堡内部筑起了高墙。

作为安全哨兵的 Hypervisor

这种安全警卫的角色可以更进一步,创造出从根本上更值得信赖的系统。

通过利用 EPT 的执行权限,hypervisor 可以实现一个强大的、带外的​​入侵检测系统 (IDS)​​。想象一下,hypervisor 有启发式方法来识别注入到客户机内核内存中的潜在恶意代码。它可以悄悄地在 EPT 中将这些可疑的客户机物理页面标记为不可执行。客户机和恶意软件对此变化完全不知情。然而,如果恶意软件试图执行其代码,CPU 的指令提取将触发一次 EPT 执行违例,陷入 hypervisor。通过统计这些陷阱,hypervisor 可以以极高的置信度检测到 rootkit 的活动,而这一切都发生在一个被攻破的客户机视角之外的特权位置。

然而,EPT 的安全保护伞只覆盖 CPU。那么外围设备呢?一个具有直接内存访问 (DMA)能力的恶意设备,原则上可以写入主机物理内存的任何位置,完全绕过 CPU 及其 EPT 保护。这时,一个关键的伙伴关系就发挥作用了:​​输入输出内存管理单元 (IOMMU)​​。IOMMU 对于设备而言,就如同 EPT 对于 CPU。它位于设备和主内存之间,拦截所有 DMA 请求,并执行自己的两阶段地址转换 (IOVA→GPA→HPAIOVA \to GPA \to HPAIOVA→GPA→HPA)。Hypervisor 控制着第二阶段 (GPA→HPAGPA \to HPAGPA→HPA),确保分配给特定虚拟机的设备只能访问合法属于该虚拟机的内存。EPT 和 IOMMU 共同提供了全面的隔离,保护系统免受恶意客户机代码和恶意设备的侵害。

前沿:机密性与微架构

探索的旅程并未在此结束。EPT 是硬件安全研究最前沿的关键参与者,它催生了全新的信任范式。

​​机密计算​​ (Confidential Computing) 的兴起旨在保护客户机数据,使其免受被攻破或恶意的 hypervisor 的侵害。诸如 AMD 的安全加密虚拟化 (SEV) 和 Intel 的可信域扩展 (TDX) 等技术,使用硬件内存加密引擎来透明地加密虚拟机的私有内存。Hypervisor 没有密钥。在这里,EPT 的角色发生了演变。当客户机将某个页面标记为私有时,硬件会将其客户机物理地址与一个加密属性关联起来。EPT 机制被设计为在 GPA 到 HPA 的转换过程中保留此属性。当 hypervisor(缺少密钥)试图读取该内存时,内存控制器只向其提供原始的、加密的密文。相反,当 CPU 在客户机上下文中执行时,内存控制器会自动动态解密数据。EPT 和加密引擎之间这种优雅的相互作用为虚拟机创建了一个安全的保险库,其中 EPT 充当了执行边界但无法窥探内部的守门人。

最后,即使是像 EPT 权限这样看似完美的架构保证也可能存在微妙的裂缝。在深奥而奇特的微架构世界里,处理器会执行​​推测执行​​ (speculative execution),即为了提高性能而在预测的路径上提前运行。这导致了一类“瞬态执行” (transient execution) 攻击。研究人员已经表明,在某些易受攻击的 CPU 上,即使一次内存访问最终会被 EPT 权限检查所阻止,处理器也可能将禁用的数据从本地缓存中推测性地转发给瞬态指令。这些指令虽然永远不会在架构上提交,但它们可以在缓存的状态中留下痕迹,从而创建一个恶意客户机可用来泄露数据的侧信道。这揭示了一个深刻的真理:安全是一个跨层属性。虽然 EPT 提供了强大的架构屏障,但确保真正的安全需要理解其与复杂、几乎不可见的微架构行为之间的相互作用。

从一个简单的查找机制,EPT 已经发展成为数字世界的基石。它是云的赋能者,是性能工匠的工具,也是安全架构师的哨兵。它的故事证明了一种简单、优雅的抽象概念能够创造一个充满复杂而奇妙可能性的宇宙。