# 6.3 虚拟化的硬件支持
<!-- source: p0501 -->

> 虚拟机被视为真实机器的一个高效、隔离的副本。我们通过虚拟机监控器（VMM）这一概念来解释这些含义。VMM 有三个基本特征。第一，VMM 为程序提供的环境与原机器基本相同；第二，程序在这一环境中运行时，最坏也只会有轻微的速度下降；最后，VMM 完全控制系统资源。
>
> ——Gerald Popek 与 Robert Goldberg，“Formal requirements for virtualizable third generation architectures”，*Communications of the ACM*（1974 年 7 月）

虚拟化技术最早开发于 20 世纪 60 年代末，此后一直是大型机计算的重要组成部分。虽然在 80、90 年代的单用户计算机和服务器领域大体受到忽视，虚拟化却在 2000 年代重新流行起来。最初的成功在于，让开发者能够在单台机器上运行多个操作系统，例如多个不同版本的 Windows，从而在不同场景下测试应用代码，而无需购买多台物理机器。很快，重点转向企业机房中的机器整合，目的或是提高利用率，或是让旧软件栈运行在其并不直接支持的新硬件上。这些企业使用场景启发了 WSC 规模效用计算的复兴，而虚拟化正是实现云计算的核心技术。

虚拟化通过增加硬件层或软件层，为被虚拟化的底层资源创建一个或多个虚拟副本。从一台物理机器出发，虚拟化能够创建多个**虚拟机**（VM），其呈现的指令集与底层硬件相同。这类 VM 称为**（操作）系统虚拟机**，IBM VM/370、VMware ESX Server 和 Xen 都是例子。如果对“机器”采用不同定义，还可以构建其他类型的 VM。例如，Java VM 和 JavaScript 引擎等基于语言的虚拟机，把用户级应用二进制接口（ABI）定义为机器。我们只讨论系统 VM：它使用户感觉自己独占整台计算机，可以运行包括操作系统在内的自有软件栈。一台计算机运行多个 VM，可以支持多种不同的操作系统。没有<!-- source: p0502 -->虚拟化时，一个操作系统拥有全部硬件资源；有了虚拟化，多个操作系统共同分享这些资源。

管理 VM 的软件层称为**虚拟机监控器**（virtual machine monitor，VMM），也称 **hypervisor**。被虚拟化的底层硬件平台，即物理机器，称为**宿主机**（host），其资源由多个**客户虚拟机**（guest VM）共享。hypervisor 类似于操作系统的底层部分，负责管理物理资源到虚拟资源的映射。它结合三项关键技术来管理资源：**分时、分区和模拟**。例如，CPU 核心可以由多个 VM 分时使用，类似操作系统让多个进程分时使用一个核心。对于多核芯片，还可以把核心划分给不同 VM。物理内存也进行分区，使每个 VM 在任一时刻只能访问其中的一部分页面。I/O 硬件则经常采用模拟：VM 的 I/O 调用经过 hypervisor，由后者访问 I/O 设备，再将结果返回 VM。hypervisor 中负责核心隔离的部分大约只需 10,000 行代码。它比传统操作系统小得多，因为不包含图形与用户界面、高层 API 和库等上层功能。后文将说明，如今所有主流指令集体系结构（ISA）都为 hypervisor 提供了大量硬件支持。而且，云提供商正逐步把 hypervisor 软件卸载到 SmartNIC 等专用硬件，为客户 VM 腾出宿主 CPU 核心的计算周期。

虚拟化提供的一些功能，从根本上改变了效用计算：

- **聚合。** 虚拟化让具有不同软件栈的多个 VM 共享一台宿主机。因此，云提供商可以部署 TCO 最优的物理机器——通常是具有许多核心的大型服务器——再在每台机器上聚合多个大小不同的客户 VM。
- **更好的容错、恢复与维护。** 虚拟化用运行在云服务器上的 VM，替代客户自有的物理机器。VM 把机器及其软件栈的全部状态封装为一组比特；这些比特可以操作，也可以迁移。例如，云提供商可以定期把 VM 状态保存到持久化存储，提供用于故障恢复的 VM 快照；还可以把状态从一台宿主机复制到另一台，即 **VM 实时迁移**，以便对原宿主机进行无中断维护，或支持主备容错。
- **更好的调度。** 客户 VM 可以在宿主机之间迁移，因此云提供商能够不时尝试把运行中的 VM 集中到数量较少、利用率较高的宿主机上。腾出的宿主机可以进入低功耗状态以节能，也可以留给那些独占宿主机或使用相邻宿主机时效果最佳的大型工作负载。云提供商还可以通过迁移 VM，更好地均衡 WSC 网络与存储系统的负载，或将受到性能干扰的 VM 分开。
- **软件灵活性与隔离。** VM 提供的抽象能够运行完整软件栈，包括 DOS 等旧操作系统，或<!-- source: p0503 -->定制操作系统。典型部署可能有一些 VM 运行遗留操作系统，许多 VM 运行当前稳定版本，还有少量 VM 测试下一版本。所有这些软件栈可以共存，而不会使 WSC 运维复杂化。一个 VM 的崩溃、软件缺陷或安全事件，与同一宿主机上其他 VM 相隔离。

## 什么样的体系结构能够虚拟化？

hypervisor 在虚拟化底层硬件时，必须满足三个条件：**安全性、等价性和效率**。安全性要求 hypervisor 与客户 VM 隔离，客户 VM 彼此也隔离。客户 VM 不应能直接改变物理资源分配。等价性要求，运行在客户 VM 上的软件，其行为应与直接运行在原生硬件上完全相同。任何计算的输出及其副作用都应一致；例外是性能依赖或资源可用性差异引起的不同，例如分配给各 VM 的物理内存数量不同。效率则要求，客户 VM 上的软件性能应尽可能接近原生硬件。固定硬件资源被多个 VM 共享或分割时受到的限制，以及资源复用或模拟的开销，都可能降低性能。

先来看硬件如何支持安全性与等价性。为了虚拟化处理器，hypervisor 必须控制几乎所有方面——特权状态访问、地址转换、I/O、异常和中断——即使正在运行的客户 VM 暂时使用这些资源来执行操作系统代码或用户代码。例如，发生定时器中断时，hypervisor 必须挂起当前客户 VM，保存其状态，处理中断，决定接下来运行哪个 VM，再加载其状态。依赖定时器中断的客户 VM，会获得 hypervisor 提供的可编程虚拟定时器和模拟的定时器中断。为了掌握控制权，hypervisor 的特权级必须高于任何 VM 内的用户代码与操作系统代码，并能截获客户 VM 对核心、内存或中断等物理硬件资源的操作。

1974 年，Gerald Popek 和 Robert Goldberg 形式化地定义了 ISA 及其各种实现能够被虚拟化的要求（Popek 与 Goldberg，1974）。他们著名的定理指出，满足以下条件便可构建 hypervisor：

- ISA 至少定义两种处理器模式：系统模式和用户模式；
- 能够观察或更新系统状态的**敏感指令**集合，是只能在系统模式下使用的**特权指令**集合的子集。

<!-- source: p0504 -->

系统状态包括反映或管理处理器状态、中断、虚拟内存（MMU）、安全性和资源分配等功能的全部配置寄存器与内存结构。如果所有能够观察或更新这类状态的指令，在用户模式执行时都会产生异常，使处理器进入系统模式，那么独自在系统模式运行的 hypervisor 就完全控制了系统。如果客户 VM 中的代码，例如操作系统，尝试执行任何敏感指令，hypervisor 都有机会介入，并按需管理或虚拟化物理资源。

20 世纪 90 年代末，虚拟化重新开始流行时，x86 ISA 并不满足 Popek 和 Goldberg 定理的要求。具体而言，有 17 条指令是敏感指令，却不是特权指令（Robin 与 Irvine，2000）。例如，`push` 指令允许在 ring 1 或 ring 3——x86 中供用户代码使用的两种模式——运行的客户操作系统，观察 `%cs` 寄存器的值；该寄存器除了其他信息，还描述当前处理器模式。因此，客户操作系统可以发现自己并没有运行在内核模式，即 x86 ring 0。尽管如此，早期 hypervisor 仍通过两种方法实现了 x86 虚拟化。VMware Workstation 依靠**动态二进制翻译**（DBT），运行操作系统未经修改的客户 VM。hypervisor 为 VM 加载代码页时，会扫描有问题的指令，并将其替换为等价、安全且性能良好的代码序列（Bugnion 等，2012）。Xen Hypervisor 则采用**半虚拟化**（paravirtualization）：修改客户操作系统，消除有问题的指令，并使其高效地与底层 hypervisor 交互（Barham 等，2003）。虽然下面讨论的硬件支持使 x86 体系结构符合了 Popek 和 Goldberg 定理，DBT 与半虚拟化仍可用于解决虚拟化环境中的性能问题。

## 体系结构对虚拟化的支持

体系结构增加虚拟化支持，有三个动因。首先，体系结构设计者希望确保所有敏感指令都是特权指令，消除 hypervisor 对 DBT 和半虚拟化的需要。其次，在只有用户与系统两种模式的系统中，hypervisor 必须运行在系统模式。这会造成所谓的**模式压缩**（在 x86 中称为环压缩，ring compression）：客户操作系统和应用不得不共享用户模式，从而削弱二者之间的隔离。最后，模式压缩还会降低性能，因为未经修改的操作系统倾向于频繁通过敏感指令访问系统状态，导致客户 VM 与 hypervisor 之间过多地切换。如今，所有主要指令集都提供虚拟化的体系结构支持。下面简要介绍 x86 的做法，并指出 Arm 和 RISC-V 的主要差异。

x86 ISA 的 VT-x 扩展为 hypervisor 定义了新的 **root 模式**，与现有的 x86 模式正交，从而解决这些问题，如<!-- source: p0505 -->图 6.4 所示。客户 VM 在 **nonroot 模式**运行，其中操作系统使用 ring 0，即 x86 系统模式；用户应用使用 ring 3，即 x86 用户模式。新增的正交模式消除了模式压缩，同时不改变现有操作系统所看到的硬件。VT-x 还把虚拟机的全部状态封装在物理内存中的一个结构里，称为**虚拟机控制结构**（virtual machine control structure，VMCS）。VMCS 包含 nonroot、ring 0 模式下操作系统可用的全部配置寄存器。它使 hypervisor 能够操作 VM 状态——保存、恢复、读取和写入——并调度 VM 执行，方式类似于操作系统管理用户进程及其状态。

hypervisor 准备启动或恢复 VMCS 描述的虚拟机时，使用 `vmlaunch` 或 `vmresume` 指令。这些指令以原子方式把 VMCS 的内容从内存复制到处理器寄存器，再把处理器切换到 nonroot 模式。VM 开始执行，按需在其操作系统（ring 0）与应用（ring 3）之间切换。大多数情况下，操作系统可以通过 x86 特权指令读写自身的配置寄存器，而不把控制权交给 hypervisor。发生以下 **vmexit** 事件时，处理器会切换到 root 模式，并调用 hypervisor：

1. VM 代码尝试访问 root 模式的敏感状态，例如读取 CPU 的型号专用寄存器，或执行 root 模式特权指令，例如 `vmlaunch`；
2. 处理器收到外部中断，或者收到 VM 无法处理的中断，例如复位条件；
3. VM 使用 `vmcall` 指令调用 hypervisor。

![VT-x 的 root 与 nonroot 模式，以及 VMentry 和 VMexit](/figures/ch06/fig-6-4.png)
{#fig-6.4 .wide .expand num="6.4" caption="x86 体系结构 VT-x 扩展中的 root 与 nonroot 工作模式。客户操作系统和应用在 nonroot 模式中运行，分别使用 ring 0 与 ring 3。WSC 通常采用直接在物理硬件上启动的 1 型 hypervisor，运行于 root 模式的 ring 0。在个人计算机上使用虚拟化时，通常有一个宿主操作系统运行于 root 模式的 ring 0；2 型 hypervisor 作为宿主应用，运行于 root 模式的 ring 3。"}

图内术语：Guest OS＝客户操作系统；Guest apps＝客户应用；Host apps＝宿主应用；Hypervisor or host OS＝hypervisor 或宿主操作系统；VMentry＝进入 VM；VMexit＝退出 VM。

<!-- source: p0506 -->

`vmcall` 指令相当于用户进程用来调用操作系统的 `syscall` 指令。

VT-x 扩展满足 Popek 和 Goldberg 定理的安全性与等价性要求，虽然实现方式并非直接对应。所有能够观察或修改 root 模式状态的指令，都是 root 模式特权指令，会引发 vmexit。不过，许多操作 nonroot 模式敏感状态的指令能够执行，而不会引发 vmexit，因为硬件保证它们只操作该 VM 独有的状态副本。这样可以减少 hypervisor 与客户 VM 之间的切换，提高性能。

与 VT-x 类似，RISC-V ISA 的 H 扩展为 hypervisor 定义了 H 模式，它与 S（系统）和 U（用户）模式正交。Arm ISA 通过新增的 EL2 执行模式支持虚拟化；其特权严格高于 Arm 的用户模式 EL0 和系统模式 EL1，但并不与二者正交。Arm 还有 TrustZone 安全扩展定义的 EL3 模式。EL3 与其他所有模式正交，允许安全监控器将系统资源划分给可信与非可信执行环境。TrustZone 系统的可信一侧，可以用于实现纯软件的可信平台模块（TPM）。

## 内存虚拟化

内存的虚拟化方式，对 hypervisor 和虚拟机的安全性与性能至关重要。在讨论其实现之前，先定义几个术语，以区分并理解第 2 章所述虚拟内存与 VM 内存虚拟化之间的相互作用。系统中实际存在的半导体内存，通常由 DRAM 芯片组成，称为**宿主物理内存**（host physical memory，HPM）。hypervisor 将 HPM 的不同部分分配给不同 VM，并确保除非明确许可，VM 不能读写其他 VM 的内存。每个 VM 中的客户操作系统管理**客户物理内存**（guest physical memory，GPM）。GPM 是线性地址空间；客户操作系统无法知道 hypervisor 如何利用 HPM 的部分区域来支撑 GPM。GPM 中不常用的部分，也可能通过分页暂时存放到辅助存储。最后，VM 中每个进程都有自己的**客户虚拟内存**（guest virtual memory，GVM）地址空间，由客户操作系统管理，并在 GPM 之上实现。因此，虚拟化系统只有一个 HPM 地址空间；每个 VM 有一个 GPM；每个 VM 内的每个客户进程各有一个 GVM。

ISA 尚未支持虚拟化时，hypervisor 通过**影子页表**为未经修改的客户系统管理内存。进程的页表由客户操作系统管理，将 GVM 映射到 GPM。hypervisor 截获操作系统的所有内存管理操作，例如 MMU 与 TLB 管理指令，并构建将 GVM 映射到 HPM 的影子页表。影子页表用于重新填充 TLB，在<!-- source: p0507 -->程序执行时，为加载与存储指令访问的 GVM 地址提供转换。管理影子页表时，hypervisor 决定每个 VM 能使用多少内存，以及某个 GVM 页面由 HPM 页面还是辅助存储提供后备空间。影子页表依靠 DBT 或半虚拟化截获操作系统的全部内存管理事件，并减少调用 hypervisor 的次数。影子分页很可能是 hypervisor 中最复杂的组成部分。

x86 的 VT-x 扩展为**扩展页表**（extended page table，EPT）定义了硬件支持，从而不再需要软件影子页表。扩展后的 MMU 与 TLB 指令和逻辑，既能识别操作系统管理的 GVM→GPM 页表，也能识别 hypervisor 管理的 GPM→HPM 页表。执行加载或存储指令时，硬件利用 TLB 快速将 GVM 地址转换为 HPM 地址。若 TLB 未命中，硬件页表遍历器会使用 GVM/GPM 与 GPM/HPM 两套页表，找出正确转换并重新填充 TLB，通常无需调用客户操作系统或 hypervisor。GVM/GPM 转换中发生缺页时，调用客户操作系统；GPM/HPM 转换中发生缺页时，则调用 hypervisor。

EPT 的页表遍历很复杂，因为两套页表都是多级结构，而且 GPM 地址必须先转换为 HPM 地址才能使用。图 6.5 展示了 x86 中转换 64 位地址所需的步骤。无论系统是否虚拟化，只要 TLB 命中，地址转换就能迅速完成，不需要任何内存访问。非虚拟化系统发生 TLB 未命中时，硬件遍历器从基址寄存器 CR3 指向的地址开始，进行四级页表查找；它以虚拟地址的不同部分为索引，依次访问 PML4、PDPT、PD 和 PT。转换成本为 4 次内存访问，每查一级访问一次。虚拟化系统则需要 24 次内存访问。CR3 的值，以及 PML4、PDPT、PD、PT 四级查找的结果，都是 GPM 地址，必须再通过 hypervisor 维护的 GPM→HPM 页表转换，而这套页表也有四级。因此，还需 \(5\times4\) 次额外查找，总计 24 次内存访问。启用虚拟化后，页表遍历成本增加到 6 倍，使维持高 TLB 命中率更为重要，也推动了更大页面的使用，以扩大 TLB 覆盖范围、减少页表遍历步骤。附录 L 深入讨论多级页表、多种页面大小和页表缓存等地址转换关键优化。

Arm 与 RISC-V 对内存虚拟化提供的支持，与 x86 EPT 类似。

**例题**

假设一个非虚拟化 x86 系统运行典型负载时，基准平均访存时间（AMAT）为 2.2 个周期，计入了指令和数据缓存未命中，以及 L1 TLB 未命中的影响，但不包括 L2 TLB 未命中。假设<!-- source: p0508 --> L2 TLB 未命中率为 0.2%，每次页表内存访问耗时 50 个周期，来自大致等比例的 L2、L3 与主存访问。系统总 AMAT 是多少？如果启用虚拟化，要保持 AMAT 不变，L2 TLB 未命中率应降低到多少？如果只能把 L2 TLB 未命中率优化到 0.005%，那么为了与非虚拟化系统保持相同 AMAT，虚拟化四级页表遍历的目标延迟应是多少？

![x86 扩展页表的两级地址转换与 TLB](/figures/ch06/fig-6-5.png)
{#fig-6.5 .wide .expand num="6.5" caption="x86 利用扩展页表进行地址转换。CR3 是 GVM→GPM 页表的基址，本身也是 GPM 地址；EPTP 是 GPM→HPM 页表的基址，本身是 HPM 地址。TLB 未命中所触发的转换过程，需要 24 次主存访问。"}

图内术语：Guest virtual address＝客户虚拟地址；Host physical address＝宿主物理地址；Physical page number＝物理页号；offset＝页内偏移；index＝索引；guest page table＝客户页表；host page table＝宿主页表；main memory access＝主存访问。PML4、PDPT、PD、PT 为四级页表结构名称，保留原标识。

> **译注（图 6.5）：** 原图图例将蓝色标为客户页表、浅橙色标为宿主页表，但图中蓝色的每一行以 EPTP 为起点，承担 GPM→HPM 转换；浅橙色竖列以 CR3 为起点，承担 GVM→GPM 转换。图例颜色说明与图中转换路径相反。原图未改动，阅读时应以地址转换方向及 CR3/EPTP 起点为准。

**解答**

非虚拟化系统的总 AMAT 包括基准 AMAT 与 L2 TLB 未命中的影响，其中每次页表遍历需要 4 次内存访问：

$$
\begin{aligned}
\mathrm{AMAT}
&=\text{基准 AMAT}+\text{L2 TLB 未命中代价}\\
&=\text{基准 AMAT}+\text{L2 TLB 未命中率}\times\text{页表访问成本}\\
&=2.2+0.002\times50\times4\\
&=2.2+0.4=2.6\ \text{个周期}
\end{aligned}
$$

可见，由于页表遍历成本很高，即使 L2 TLB 未命中率很低，也会显著影响性能。正如第 2 章所述，<!-- source: p0509 -->硬件页表遍历器可以同时处理多个尚未完成的 TLB 未命中，从而减轻部分开销。

对于虚拟化系统，如图 6.5 所示，页表成本提高到 6 倍。要得到相同的 2.6 个周期 AMAT，L2 TLB 未命中率必须降至 \(0.2\%/6=0.033\%\)。

可以通过增大 L2 TLB 来降低 TLB 未命中率，并采用更大的页面，减轻虚拟化对 AMAT 的影响。例如，Intel 和 AMD 都支持 2 MiB 页面。如果 L2 TLB 未命中率降低到 0.05%，使 AMAT 达到 2.6 的页表遍历时间为：

$$
\begin{aligned}
\mathrm{AMAT}&=2.6\\
2.2+0.0005\times24\times\text{页表访问成本}&=2.6\\
\text{页表访问成本}
&=\frac{0.4}{0.0005\times24}=33.33\ \text{个周期}
\end{aligned}
$$

> **译注：** 题干给出 0.005%，解答却使用 0.05%，二者相差 10 倍。依原解公式，0.05% 对应每次页表内存访问 33.33 个周期、24 次访问合计约 800 个周期。若采用题干的 0.005%，则相应数值为每次约 333.33 个周期、合计约 8000 个周期。题干问“页表遍历”目标延迟，而原解最终列出的是单次页表内存访问成本，二者也应区分。

降低页表访问成本的一种方法，是增加专用 MMU 缓存来保存页表项。MMU 缓存不按顺序地址保存数据，而是保存多级页表项，使一次访问能够返回多个相互关联的页表项。详见附录 L。

## I/O 虚拟化

虚拟化存储、网络等 I/O 系统的基本办法，是让 hypervisor 截获 VM 与 I/O 硬件之间的全部交互。为此，可以在执行程序控制 I/O（PIO）指令、对内存映射 I/O（MMIO）地址执行加载或存储，以及发生 I/O 中断时触发 vmexit。也可以通过 DBT 或半虚拟化引入 `vmcall`，将 I/O 请求交给 hypervisor。让 hypervisor 介入全部 I/O 操作有多种优势。第一，它可以在客户操作系统假定的 I/O 接口与实际硬件接口之间进行转换。这让旧 VM 能借助模拟的 I/O 设备运行，也让未经修改的 VM 受益于旧设备驱动程序无法配置或利用的新硬件功能。第二，hypervisor 能观察并控制全部 I/O 操作的状态和进度，因此可以把 VM 从一台物理机器迁移到另一台，而无需重启 VM。它可以令全部 I/O 操作进入静止状态，暂停 VM，把描述其状态的全部比特迁移到另一台机器——包括寄存器、内存，以及 hypervisor 跟踪的状态——再恢复执行。前文已讨论，这种实时迁移为云计算带来重要收益。最后，这种介入使 hypervisor 能制定策略，管理多个 VM 如何安全地共享 I/O 设备能力，例如限制每个 VM 能产生的网络带宽。

然而，介入会增加延迟和吞吐量开销。对于依赖 SSD 或高速网络接口（NIC）的 I/O 密集型应用，这种开销可能很大。例如，200 Gbps NIC 每秒最多可以传送<!-- source: p0510 --> 4 亿个以太网分组，逐个介入分组处理的代价相当高。这推动了 I/O 虚拟化的体系结构支持，让 VM 能直接且安全地访问 I/O 设备。以发送网络分组为例，理想过程如下：首先，客户操作系统或用户进程直接使用 MMIO 指令设置 I/O 设备；接着，设备的 DMA 引擎直接从 VM 内存复制待发送数据，无需 hypervisor 协助；分组发送完成后，I/O 设备直接向客户 VM 递送中断，也无需 hypervisor 协助。

第一步所需的支持已经讨论过：EPT 可以把 I/O 设备的 HPM 地址映射到客户系统的 GPM 或 GVM 地址空间。下面通过 x86 的 VT-d（IOMMU）扩展，讨论后两步，即 DMA 与中断虚拟化。Arm 与 RISC-V 定义了类似机制。还有一个额外要求：允许多个 VM 直接且安全地访问同一 I/O 设备。6.5 节将讨论基于 PCIe 的 I/O 设备所用的 SRIOV 方案。

VT-d 定义的 IOMMU 单元包含两个组件：**DMA 重映射**（DMAR）与**中断重映射**（IR）。DMAR 允许 VM 内的软件使用 GPM 或 GVM 地址，设置 I/O 设备的 DMA 引擎。DMA 引擎发起内存读写访问时，根据设备标识查找上下文标识符。对于通过 PCIe 接口连接的设备，设备标识包括总线号（8 位）、设备号（5 位）和功能号（3 位）。上下文标识符包含指向地址转换所需页表的指针，例如用于 GPM→HPM 转换的页表。利用这些页表，地址转换按常规方式进行。IOMMU 内含 TLB，缓存近期使用过的转换以加速处理。事实上，早期 IOMMU 没有硬件页表遍历器，TLB 未命中时会调用 hypervisor，把 DMA 引擎使用的地址转换为 HPM 地址，并更新 TLB 供后续使用。IOMMU 的 TLB 设计颇具挑战：既要通过足够容量维持低未命中率，也要有足够吞吐量支持一个或多个高性能 I/O 设备，例如一个 200 Gbps 设备在每个方向上，都可能为每秒最多 4 亿个分组生成 DMA 访问。

IR 的工作方式如下。为了产生**消息信号中断**，I/O 设备向系统中某个核心的高级可编程中断控制器（APIC）发送消息。在 VT-d 下，每个 VM 都关联一个虚拟 APIC，它是由相应 VMCS 标识的内存结构。设备产生中断时，IR 根据 I/O 设备标识符查找接收中断的 VM（VMCS）信息，以及如何改写中断消息。随后更新相应 VMCS 中的虚拟 APIC，记录待处理的中断。虚拟 APIC 还会通知当前运行该 VM 的核心，让它考虑递送中断。物理<!-- source: p0511 --> APIC 根据核心状态和 VM 状态，决定中断能否立即触发，还是需要等待。例如，客户操作系统可能正在处理更高优先级的中断，必须先完成它，再触发其他中断。中断触发时，直接调用客户操作系统中对应中断向量的处理程序，无需 hypervisor 介入。这种无需退出 VM、向运行中 VM 递送中断的方式，称为 **posted interrupts**。它也可用于两个 VM 之间，或同一 VM 中两个进程之间的进程间中断（原文称 interprocess interrupts，IPI）。

## hypervisor 实例：Xen 项目

Xen hypervisor 是 2003 年首次推出的开源项目，也是 AWS 最初采用的 hypervisor。它属于**裸金属或 1 型 hypervisor**，即机器启动时最先运行的软件就是 hypervisor。相比之下，VMware Workstation 等**2 型 hypervisor**先启动一个基础操作系统；hypervisor 本身是用户级进程，依靠宿主操作系统的服务，在同一硬件上运行其他 VM。

Xen 引入半虚拟化，解决 VT-x 和 VT-d 扩展出现之前，x86 虚拟化面临的安全与性能问题（Barham 等，2003）。Xen 开发者修改了 Linux 约 3000 行代码，约占 x86 专用代码的 1%。这些修改消除了敏感但非特权指令的使用，并替换了在虚拟化下效率极差的代码。例如，为避免清空与影子页表关联的 TLB，Xen 将自身映射到每个 VM 地址空间最高的 64 MiB。Xen 允许客户操作系统分配页面，只检查其是否违反保护限制。为了使客户操作系统免受 VM 内用户程序的侵害，最初的 Xen 利用了 x86 提供的三个保护级：Xen hypervisor 运行于最高特权级 0，客户操作系统运行于次一级 1，应用运行于最低级 3。大多数 x86 操作系统只使用特权级 0 和 3。这些修改均不影响 VM 工作负载所依赖的 Linux ABI。自 2011 年起，Linux 已直接支持 Xen 半虚拟化。

Xen 将 VM 称为 **domain**（域）。一个初始的特权域 **Dom0** 最先启动，并管理称为客户域的普通 VM。Dom0 包含硬件 I/O 设备驱动，即后端驱动。客户域使用虚拟磁盘和虚拟网络设备的前端驱动，通过调用 Dom0 完成 I/O 请求。还可以利用**驱动域**分离 Dom0 的部分功能；驱动域是不具有特权的 VM，负责某个具体 I/O 设备及其后端驱动。驱动域既避免 Dom0 成为系统瓶颈，也提高其可靠性：驱动程序崩溃不会影响 Dom0 的运行。

这些年来，Xen 不断演进，开始利用体系结构对虚拟化的支持。从 2006 年起，Xen 利用 VT-x 支持 Microsoft Windows 等未经修改的客户操作系统。后来又利用 VT-d 改进 I/O 性能与<!-- source: p0512 -->安全性。例如，驱动域利用 IOMMU，保护其他域不受后端驱动缺陷和漏洞利用的影响。Xen 支持轻量级 VM，混合使用硬件辅助虚拟化与半虚拟化，提高 VM 性能并减小内存占用。VM 越小，云 WSC 中一台服务器的内存就能容纳越多计算需求较低的 VM。

## 机密计算与安全 enclave

公有云系统广泛采用之后，金融或医疗等关键应用的数据安全问题受到关注。攻击者可以利用应用、客户操作系统或 hypervisor 本身的缺陷，绕过进程之间的硬件辅助内存隔离（虚拟内存），或 VM 之间的隔离（EPT）。云计算扩大了攻击面：云基础设施对外开放，攻击者可能运行在同一 WSC，甚至同一台机器上，即 VM 共置。许多云工作负载实现互联网服务，因此恶意用户可以与其交互，寻找代码注入或其他安全漏洞利用机会。最后，一些云用户可能并不完全信任云提供商，原因或是存在不守规矩员工的可能性，或是用户与提供商的某项产品存在直接竞争。

这些挑战推动了**机密计算**平台的发展：即使在公有云上运行、实现互联网服务，平台仍对数据的机密性与完整性提供有力保证。这类平台可利用同态加密、多方计算和零知识证明等新兴密码技术构建。但这些技术的计算开销仍然很大，因此 CPU 厂商引入了可信执行的硬件支持，例如 Intel **软件防护扩展**（SGX）。更细粒度保护这一课题已有几十年历史，但过去因开销高，而且更高效、侵入性更低的其他方案已足够可用，商业进展一直有限。网络攻击的增加，以及在线机密信息数量的增长，使人们重新审视改善这类细粒度安全性的技术。

SGX 及其他 ISA 中的类似扩展，允许用户程序创建**安全 enclave**。放入 enclave 的代码与数据始终使用用户代码提供的密钥加密；只有在 CPU 芯片内部使用时才解密。程序通过狭窄的接口调用 enclave 内的函数，使用特殊指令将处理器切换到安全 enclave 模式。enclave 不处理中断、缺页或任何 vmexit 事件。发生这类事件时，处理器先把程序状态保存到加密内存，用合成值覆写寄存器，退出安全模式，再调用预先指定的中断处理程序。SGX 还有一项值得注意的功能：enclave 用代码与数据初始化时，会<!-- source: p0513 -->生成其状态的密码学散列值，外部证明服务可以检查这个值，以确认代码和数据此前均未遭修改。

enclave 使某些函数能够在只信任 CPU 厂商的条件下进行机密执行。由于 enclave 始终加密，常规 hypervisor 或操作系统的虚拟内存及 I/O 操作可以访问它，例如移动页面，但无法提取任何明文信息。要让 enclave 正常工作，函数所需的全部代码与数据都必须位于其中。与 Intel SGX 类似，IBM 和 AMD 的近期处理器也支持主存的即时加密。
