学习体系结构 - AArch64 虚拟化
Learn the architecture - AArch64 virtualization
Version 1.0
借助 Deepl 翻译文档 + 个人对文档补充的一部分解释, 仅供学习参考
前 3 章为了解内容,引入虚拟化
第 4-7 章为虚拟化比较核心的内容
第 4 章为第二阶段地址翻译,第 5 章为陷入和指令模拟,第 6 章为虚拟化异常,第 7 章为虚拟化通用计时器
省略了第 9 章 嵌套虚拟化,第 10 章 安全虚拟化
1、Overview
本指南介绍 Armv8-A AArch64 中的虚拟化支持。涉及的主题包括 第 2 阶段转换、虚拟异常和捕获。
本指南包括一些基本的虚拟化理论作为介绍,并举例说明管理程序如何使用本指南介绍的功能。它并不涵盖特定管理程序的操作,也不试图解释如何从头开始编写自己的管理程序。这两个主题都超出了本指南的范围。
在本指南结束时,你可以检查自己的知识。你将了解两种类型的管理程序,以及它们如何映射到 Arm 异常级别。您将能够解释陷阱的操作以及如何使用它们来模拟操作。您还将能够列出管理程序可以生成哪些虚拟异常,并描述生成异常的机制。
Before you begin
我们假设你对虚拟化有基本的了解,包括什么是虚拟机(VM)以及管理程序的作用。熟悉异常模型和内存管理中的地址转换。
2、Introduction to virtualization
下面我们将介绍一些管理程序和虚拟化的入门理论。如果你已经熟悉这些概念,可以跳过这部分内容。
我们在本指南中使用的管理程序(hypervisor)一词是指负责**创建、管理和调度虚拟机(VM)**的软件。
Why is virtualization important
**虚拟化是一项应用广泛的技术,几乎是所有现代云计算和企业基础设施的基础。**开发人员使用虚拟化技术在一台机器上运行多个操作系统(OS),并测试软件,而不会有破坏主计算环境的风险。
虚拟化在服务器系统中非常流行,大多数服务器级处理器都要求支持虚拟化。这是因为虚拟化为数据中心提供了非常理想的功能,包括:
- 隔离: 虚拟化的核心是在单个物理系统上运行的虚拟机之间进行隔离。**这种隔离允许互不信任的计算环境共享物理系统。**例如,两个竞争对手可以在数据中心共享同一台物理机,而无法访问对方的数据。
- 高可用性: 虚拟化允许在物理机之间无缝、透明地迁移工作负载。这种技术通常用于将工作负载从可能需要维护和更换的故障硬件平台上迁移开来。
- 工作负载均衡: 为了优化数据中心的硬件和电力预算,必须尽可能多地使用每个硬件平台。同样,这可以通过虚拟机迁移或在物理机上共同托管合适的工作负载来实现。这意味着要尽可能多地使用物理机的容量。这为数据中心提供商提供了最佳的电力预算,也为租户提供了最佳的性能。
- 沙箱: **虚拟机可用于为可能会干扰机器其他部分运行的应用程序提供沙箱。**此类应用程序的例子包括传统应用程序或正在开发的软件。在虚拟机中运行这些应用程序,可以防止应用程序中的错误或恶意部分干扰物理机上的其他应用程序或数据。
Standalone and hosted hypervisors
管理程序可分为两大类:独立式管理程序(或称 Type-1 hypervisor)和托管式管理程序(或称 Type-2 hypervisor)。
我们首先来看看托管型或 Type-2 hypervisor。**在 Type-2 hypervisor 配置中,主机操作系统完全控制硬件平台及其所有资源,包括 CPU 和物理内存。**下图展示了托管型或 Type-2 hypervisor:
如果你以前使用过 Virtual Box 或 VMware Workstation 等软件,这就是你运行的管理程序类型。平台上安装了一个操作系统(称为主机操作系统),管理程序在主机操作系统内运行,利用现有功能管理硬件。然后,管理程序可以托管虚拟机,而虚拟机本身也运行操作系统。我们称之为客机操作系统。
接下来,我们先来看看独立的或 Type-1 hypervisor:
可以看到,在这种管理程序设计中没有主机操作系统。**管理程序直接在硬件上运行,可以完全控制硬件平台及其所有资源,包括 CPU 和物理内存。**与托管管理程序一样,独立管理程序也可以托管虚拟机。这些虚拟机可以运行一个或多个完整的客户操作系统。
Arm 平台上最常用的两个开源管理程序是 Xen(独立,Type-1)和 KVM(托管,Type-2)。我们将使用这些管理程序来说明本指南中的某些要点。不过,还有许多其他管理程序可用,既有开源的,也有专有的。
Full virtualization and para-virtualization
**虚拟机的经典定义是一个独立、隔离的计算环境,与真实的物理机器没有区别。**尽管可以在基于 Arm 的系统上完全仿真真实机器,但这样做往往效率不高。因此,这种仿真并不常见。例如,仿真真实以太网设备的速度很慢,因为客户操作系统对仿真寄存器的每次访问都必须由管理程序在软件中处理。这种处理方式比访问物理设备上的寄存器要昂贵得多。
严格来说,全系统虚拟化模拟的是一台真正的物理机。另一方面,Xen(开源项目)普及了准虚拟化(paravirtualization)这一术语。**在这种情况下,客户操作系统的核心部分被修改为在虚拟硬件平台而非物理机上运行。**这种修改是为了提高性能。
如今,**在包括 Arm 在内的大多数具有虚拟化硬件支持的架构上,客户操作系统的运行大多未经修改。客户操作系统认为自己是在真实硬件上运行,但块存储和网络等 I/O 外围设备的驱动程序除外,这些设备使用准虚拟化设备和设备驱动程序。**这种准虚拟化 I/O 设备的例子有 Virtio 和 Xen PV 总线。
Virtual machines and virtual CPUs
了解虚拟机(VM)和虚拟 CPU(vCPU)之间的区别非常重要。虚拟机将包含一个或多个 vCPU,如下图所示:
当我们查看本指南中的其他主题时,VM 和 vCPU 之间的区别将变得非常重要。例如,内存页面可能分配给某个虚拟机,因此该虚拟机中的所有 vCPU 都可以访问。但是,虚拟中断是针对特定 vCPU 的,因此只能访问该 vCPU。
严格来说,我们应该指的是虚拟处理单元(vPE),而不是 vCPU。请记住,处理元件(PE)是实现 Arm 架构的机器的统称。本指南使用 vCPU 而不是 vPE,因为 vCPU 是大多数人熟悉的术语。不过,在体系结构规范中,使用的是 vPE 术语。
3、Virtualization in AArch64
运行于 EL2 或更高版本的软件可使用若干虚拟化控制功能:
- 第 2 阶段转换
- EL1/0 指令和寄存器访问陷阱
- 虚拟异常生成
非安全和安全状态下的异常级别 (EL) 如下所示:
图中,安全 EL2 显示为灰色。这是因为安全状态下的 EL2 并不总能获得支持。安全虚拟化部分将对此进行讨论。
架构中也有支持以下功能的功能:
- 安全虚拟化
- 托管或第 2 类管理程序
- 嵌套虚拟化
4、Stage 2 translation
本章介绍第 2 阶段翻译和控制内存访问的方法。
What is stage 2 translation
第 2 阶段转换允许管理程序控制虚拟机(VM)中的内存视图。具体来说,它允许管理程序控制虚拟机可以访问哪些内存映射系统资源,以及这些资源在虚拟机地址空间中的位置。
这种控制内存访问的能力对于隔离和沙盒非常重要。第 2 阶段转换可用于确保虚拟机只能查看分配给它的资源,而不能查看分配给其他虚拟机或管理程序的资源。
对于内存地址转换,第 2 阶段转换是转换的第二阶段。如图所示,为支持这种转换,需要一组新的转换表(称为第 2 阶段表):
操作系统(OS)控制着一组转换表,将虚拟地址空间映射到它认为的物理地址空间。不过,这个过程还要经过第二次转换,才能进入真正的物理地址空间。第二阶段由管理程序控制。
操作系统控制的转换称为第 1 阶段转换,管理程序控制的转换称为第 2 阶段转换。操作系统认为是物理内存的地址空间称为中间物理地址(IPA)空间。
第 2 阶段使用的转换表格式与第 1 阶段非常相似。
不过,第 2 阶段对某些属性的处理方式有所不同,“类型”、"正常 "或 "设备 "直接编码到表项中,而不是通过 MAIR_ELx 寄存器。
MAIR_ELx 寄存器:
在第1阶段(Stage 1)的内存管理中,
MAIR_ELx
寄存器的作用是将虚拟内存地址(VA)通过页表项(Page Table Entry,PTE)映射到物理内存地址(PA),并定义这些映射的内存属性。这个寄存器包含了一系列的字段,每个字段都代表了一种内存属性配置。每个页表项通常包含一个属性字段(Attribute Index),它指向
MAIR_ELx
寄存器中的一个特定条目。MAIR_ELx
寄存器中的每个条目定义了一组内存属性,如设备内存、正常内存、是否可缓存等。页表项使用这个属性索引来选择适当的内存属性,从而确定访问该内存时的行为方式。
MAIR_ELx
寄存器包含多个字段,每个字段都可以被配置为代表一种特定的内存属性。这些属性包括:
- 缓存策略:如非缓存、写通(Write-Through)缓存、写回(Write-Back)缓存等。
- 访问类型:如设备内存(Device memory)、正常内存(Normal memory)等。
- 内存排序特性:如强排序或弱排序等。
VMIDs
每个虚拟机都有一个虚拟机标识符(VMID)。VMID 用于标记转换查找缓冲区(TLB)条目,以确定每个条目属于哪个虚拟机。这种标记允许多个不同虚拟机的翻译同时出现在 TLB 中。
VMID 保存在 VTTBR_EL2 中,可以是 8 位或 16 位。VMID 由 VTCR_EL2.VS 位控制。对 16 位 VMID 的支持是可选的,是在 Armv8.1-A 中添加的。
VTTBR_EL2
寄存器在 ARM 架构中的作用是关键的,尤其是在虚拟化环境中。全称为 Virtualization Translation Table Base Register at Exception Level 2,这个寄存器在 ARMv8 架构中用于支持二级地址转换,即 Stage 2 虚拟化地址转换。**
VTTBR_EL2
指向用于 Stage 2 转换的页表的基础。**当虚拟机监视器(Hypervisor)设置了VTTBR_EL2
后,硬件可以使用这个页表来将来自虚拟机的中间物理地址(IPA)映射到宿主机的物理地址(PA)。
VTTBR_EL2
还包含一些其他控制信息,如 VMID(Virtual Machine Identifier)。VMID 是一个用于区分不同虚拟机的标识符,在虚拟化环境中非常重要,因为它允许系统区分来自不同虚拟机的地址转换请求,确保内存隔离和安全性。
VTTBR_EL2
是实现 ARM 架构下硬件辅助虚拟化的关键组件。由
VTCR_EL2.VS
位控制:VTCR_EL2
是 Stage 2 虚拟地址转换控制寄存器(Virtualization Translation Control Register),用于控制第二级地址转换的各种参数。VTCR_EL2
中的VS
位(Virtualization Support bit)是用来控制 VMID 的长度。设置这个位可以启用对更长 VMID(即 16 位)的支持。VTTBR_EL2存放的是 IPA -> PA的转换,Stage 2 转换表的基地址。
EL2 和 EL3 翻译制度的译文不标记 VMID,因为它们不受第 2 阶段翻译的限制。
VMID interaction with ASIDs
TLB 条目还可以标记地址空间标识符 (ASID)。操作系统会为一个应用程序分配一个 ASID,该应用程序中的所有 TLB 条目都会被标记为 ASID。这意味着不同应用程序的 TLB 条目可以在 TLB 中共存,而不会出现一个应用程序使用属于另一个应用程序的 TLB 条目的情况。
每个虚拟机都有自己的 ASID 命名空间。例如,两个虚拟机可能都使用 ASID 5,但它们用于不同的用途。ASID 和 VMID 的组合才是最重要的。
Attribute combining and overriding
阶段 1 和阶段 2 映射都包括属性,如类型和访问权限。内存管理单元(MMU)会将两个阶段的属性结合起来,给出最终的有效值。如图所示,MMU 会选择限制性更强的阶段:
在本例中,"设备 "类型比 "正常 "类型的限制性更强。因此,结果类型是 Device。如果我们将示例反过来,第 1 阶段 = Normal,第 2 阶段 = Device,结果也是一样的。
这种组合属性的方法适用于大多数用例,但有时管理程序可能希望覆盖这种行为。例如,在虚拟机的早期启动过程中。在这种情况下,有一些控制位可以覆盖正常行为:
- HCR_EL2.CD. 这使得所有第 1 阶段属性都不可缓存。(Cache Disable)
- HCR_EL2.DC. 强制第 1 阶段属性为正常、可回写缓存。(Default Cacheable)
- HCR_EL2.FWB. 允许第 2 阶段覆盖第 1 阶段属性,而不是常规属性组合。(Force Write-Back)
HCR_EL2
寄存器用于配置虚拟化环境中的多种行为和策略。
HCR_EL2.E2H
位是在 ARMv8.1 架构中引入的,特别是为了支持虚拟化主机扩展(Virtualization Host Extensions,VHE)。该功能使得操作系统可以在异常级别 2(EL2)上运行,而不是通常的 EL1。
0b0
:**当E2H
位设置为0b0
时,表示禁用了在 EL2 运行主机操作系统的支持。**这是传统的工作模式,其中 EL2 用于 Hypervisor,而 EL1 用于操作系统。0b1
:**当E2H
位设置为0b1
时,表示启用了在 EL2 运行主机操作系统的支持。**在这种模式下,操作系统可以在 EL2 运行,而其应用程序则在 EL0 运行。
HCR_EL2.FWB was introduced in Armv8.4-A.
Emulating Memory-mapped Input/Output (MMIO)
与物理机上的物理地址空间一样,虚拟机中的 IPA 空间也包含用于访问内存和外设的区域,如图所示:
虚拟机可以使用外设区域访问真实物理外设(通常称为直接分配的外设)和虚拟外设。
如图所示,虚拟外设完全由管理程序在软件中模拟:
分配的外设是已分配给虚拟机 并 映射到其 IPA 空间的真实物理设备。这样,在虚拟机中运行的软件就可以直接与外设交互。
虚拟外设是管理程序将在软件中模拟的外设。相应的第 2 阶段表项将被标记为故障。虚拟机中的软件认为它可以直接与外设对话,但每次访问都会触发第 2 阶段故障,管理程序会在异常处理程序中模拟外设访问。
要模拟外设,管理程序不仅需要知道访问了哪个外设,还需要知道访问了该外设中的哪个寄存器、访问是读还是写、访问的大小以及用于传输数据的寄存器。
异常模型指南介绍了 FAR_ELx 寄存器。在处理第 1 阶段故障时,这些寄存器会报告触发异常的虚拟地址。虚拟地址对管理程序没有帮助,因为管理程序通常不知道客户操作系统是如何配置其虚拟地址空间的。对于第 2 阶段故障,还有一个额外的寄存器 HPFAR_EL2,用于报告中止的地址的 IPA。由于 IPA 空间由管理程序控制,因此管理程序可以使用该信息确定需要仿真的寄存器。
FAR_ELx(Fault Address Register at Exception Level x):
- 这些寄存器用于存储触发异常的虚拟地址(VA)。当一个内存访问操作(如读取或写入)由于各种原因(如权限问题、不存在的地址等)失败并导致异常时,相应的虚拟地址会被存储在
FAR_ELx
寄存器中。- 对于第1阶段(Stage 1)的地址转换异常,
FAR_ELx
提供了出现问题的虚拟地址。这对于操作系统来说是有用的信息,因为操作系统可以使用这个地址来确定异常的原因,并采取相应的措施,如修复页表、发送信号到进程等。HPFAR_EL2(Hypervisor IPA Fault Address Register):
- 这个寄存器专门用于虚拟化环境中,特别是处理第2阶段(Stage 2)的地址转换异常。当一个虚拟机(客户操作系统)的内存访问请求在通过第2阶段地址转换时失败,导致异常,
HPFAR_EL2
寄存器会存储相关的中间物理地址(IPA)。- **这个寄存器对于虚拟机监视器(Hypervisor)来说非常有用,因为它提供了发生异常的物理地址信息。**由于 IPA 空间由 Hypervisor 控制,所以 Hypervisor 可以使用这个信息来确定故障的原因,例如缺少的页表映射、权限问题等,并据此来模拟或处理异常。
对于触发第 2 阶段故障的单个通用寄存器加载或存储,会提供额外的综合征信息。这些信息包括访问大小、源寄存器或目标寄存器,并允许管理程序确定虚拟外设的访问类型。
该图说明了先诱捕再模拟访问的过程:
该过程按以下步骤描述:
- 虚拟机中的软件尝试访问虚拟外设。在本例中,这是虚拟 UART 的接收 FIFO。
- 该访问在第 2 阶段转换时受阻,导致路由至 EL2 的终止。
- 终止将向 ESR_EL2 中填充有关异常的信息,包括访问的字节数、目标寄存器以及是加载还是存储。
- 终止还将向 HPFAR_EL2 填充终止访问的 IPA。
- 虚拟机管理程序使用 ESR_EL2 和 HPFAR_EL2 中的信息来识别所访问的虚拟外设寄存器。通过这些信息,管理程序可以模拟操作。然后通过 ERET 返回 vCPU。
- 在 LDR 之后的指令上重新开始执行。
System Memory Management Units (SMMUs)
到目前为止,我们已经考虑了来自处理器的不同类型的访问。**系统中的其他主控设备(如 DMA 控制器)可能会分配给虚拟机使用。**我们需要某种方法将第 2 阶段的保护措施也扩展到这些主控程序。
考虑一个不使用虚拟化的 DMA 控制器系统,如下图所示:
DMA 控制器将通过一个驱动程序进行编程,通常位于内核空间。**内核空间驱动程序可确保操作系统级内存保护不被破坏。**这意味着,一个应用程序无法使用 DMA 访问它本不应访问的内存。
DMA 控制器允许硬件设备(如网络卡、磁盘驱动器等)直接读写系统内存,绕过 CPU,从而提高数据传输效率。
让我们来看看同样的系统,但操作系统是在虚拟机中运行的,如下图所示:
在这个系统中,管理程序使用第 2 阶段来提供虚拟机之间的隔离。软件查看内存的能力受到管理程序控制的第 2 阶段表的限制。
允许虚拟机中的驱动程序直接与 DMA 控制器交互会产生两个问题:
- 隔离: DMA 控制器不受第 2 阶段表的限制,可被用于破坏虚拟机的沙盒。
- 地址空间: 通过两个阶段的转换,内核认为的 PA 其实是 IPA。DMA 控制器看到的仍是 PA,因此内核和 DMA 控制器对内存有不同的看法。为了解决这个问题,管理程序可以捕获虚拟机与 DMA 控制器之间的每次交互,提供必要的转换。
除了对驱动程序访问进行捕获和仿真外,另一种方法是将第 2 阶段机制扩展到其他主控程序,例如我们的 DMA 控制器。在这种情况下,这些主控也需要一个 MMU。这被称为系统内存管理单元(SMMU,有时也称为IOMMU):
管理程序将负责对 SMMU 进行编程,以便上游主控(即我们示例中的 DMA)能看到与分配给它的虚拟机相同的内存视图。
这一过程解决了我们发现的两个问题。SMMU 可以强制执行虚拟机之间的隔离,确保外部主控无法用来破坏沙箱。SMMU 还能为虚拟机中的软件和分配给虚拟机的外部主控提供一致的内存视图。
虚拟化并不是 SMMU 的唯一用例。还有许多其他情况没有包括在本指南的范围内。
5、Trapping and emulation of instructions
有时,管理程序需要模拟虚拟机(VM)内的操作。例如,虚拟机中的软件可能会尝试配置与电源管理或高速缓存一致性有关的低级处理器控制。通常情况下,您不会让虚拟机直接访问这些控制,因为它们可能被用来破坏隔离或影响系统中的其他虚拟机。
当执行特定操作(如读取寄存器)时,陷阱会导致异常。管理程序需要具备在虚拟机中捕获操作(如配置低级控制的操作)并对其进行仿真的能力,而不会影响其他虚拟机。
该架构包含陷阱控制功能,可在虚拟机中设置陷阱操作并对其进行仿真。设置陷阱后,执行通常允许的特定操作会导致更高异常级别的异常。管理程序可以使用这些陷阱来模拟虚拟机中的操作。
例如,执行等待中断(WFI)指令通常会使 CPU 进入低功耗状态。通过断言 TWI 位,如果 HCR_EL2.TWI==1 ,那么在 EL0 或 EL1 执行 WFI 将导致 EL2 异常。
在 ARM 架构中,
WFI
(Wait For Interrupt)指令用于将 CPU 置入低功耗状态,直到发生中断。这是一种常见的节能机制,用于减少 CPU 在空闲期间的功耗。然而,当涉及到虚拟化和异常级别(EL)时,如何处理WFI
指令会有所不同,这就是HCR_EL2.TWI
位的作用。HCR_EL2.TWI 位:
HCR_EL2
是 Hypervisor Configuration Register,用于在 EL2(即 Hypervisor 级别)配置各种虚拟化相关的行为。TWI
(Trappable WFI)是HCR_EL2
中的一个位。当设置为1
时,它允许 Hypervisor 拦截在 EL0 或 EL1 执行的WFI
指令。这意味着当一个在 EL0 或 EL1 运行的程序执行WFI
指令时,而不是让 CPU 直接进入低功耗状态,这个操作会触发一个到 EL2 的异常。在虚拟化环境中,直接让虚拟机控制 CPU 的低功耗状态可能不安全或不高效,特别是在多个虚拟机共享同一硬件资源的情况下。
- 当
HCR_EL2.TWI
设置为1
,并且在 EL0 或 EL1 执行WFI
指令时,这会触发一个异常跳转到 EL2,允许 Hypervisor 接管控制。- Hypervisor 可以决定如何处理这个
WFI
请求,例如,它可以选择忽略此指令,模拟低功耗状态,或者在适当的时候让 CPU 真正进入低功耗状态。
陷阱不仅适用于虚拟化。还有 EL3 和 EL1 受控陷阱。不过,陷阱对虚拟化软件特别有用。本指南只讨论通常与虚拟化相关的陷阱。
在我们的 WFI 示例中,操作系统通常会在空闲循环中执行 WFI。对于虚拟机中的客户操作系统,管理程序可以捕获这一操作,并调度不同的 vCPU 代替,如图所示:
Presenting virtual values of registers
使用陷阱的另一个例子是显示寄存器的虚拟值。例如,ID_AA64MMFR0_EL1 报告处理器对内存系统相关功能的支持。操作系统可能会在启动过程中读取该寄存器,以确定要启用内核中的哪些功能。管理程序可能希望向客户操作系统提供一个不同的值,即虚拟值。
ID_AA64MMFR0_EL1 寄存器的功能包括:
- 内存管理特性:它报告了处理器对不同内存管理功能的支持程度,例如页表格式、硬件页表管理等。
- 地址大小支持:此寄存器提供关于处理器支持的最大物理和虚拟地址大小的信息。
- 硬件支持的特性:它还报告了处理器对特定内存管理功能的硬件支持情况,如硬件访问标志、页表属性等。
在 ARM 架构中,
ID_AA64MMFR0_EL1
通常在启动过程中被操作系统读取,以确定系统支持哪些内存管理功能。
为此,虚拟机管理程序会启用涵盖读取寄存器的陷阱。出现陷阱异常时,管理程序会确定触发了哪个陷阱,然后模拟操作。在此示例中,管理程序会用 ID_AA64MMFR0_EL1 的虚拟值填充目标寄存器,如图所示:
陷阱还可用作懒惰上下文切换的一部分。例如,操作系统通常会在启动过程中初始化内存管理单元(MMU)配置寄存器(TTBR<n>_EL1、TCR_EL1 和 MAIR_EL1),然后不再对其重新编程。管理程序可以利用这一点优化其上下文切换例程,只在上下文切换时恢复寄存器,而不保存它们。
- MMU 配置寄存器:
- 在 ARM 架构中,寄存器如
TTBR<n>_EL1
(Translation Table Base Register),TCR_EL1
(Translation Control Register),和MAIR_EL1
(Memory Attribute Indirection Register)用于配置内存管理单元(MMU)。- 这些寄存器负责定义地址转换的规则(如页表的基地址、大小和属性),以及内存访问的属性。
- 操作系统的初始化行为:
- 通常,在操作系统启动过程中,这些寄存器会被初始化,以设置系统的内存管理和地址转换策略。在正常运行期间,这些寄存器的值通常不会改变。
- 懒惰上下文切换:
- 在虚拟化环境中,当从一个虚拟机切换到另一个虚拟机时,Hypervisor 需要保存和恢复各种寄存器的状态,以确保每个虚拟机的独立和正确运行。这包括 MMU 配置寄存器。
- **由于操作系统在正常运行时很少修改这些 MMU 配置寄存器,Hypervisor 可以采用一种懒惰上下文切换的策略。**这意味着 Hypervisor 可以选择在切换上下文时不立即保存这些寄存器的状态。相反,它只在这些寄存器的值真正需要恢复时才进行保存和恢复操作。
- 这样做的优势在于减少了上下文切换的开销,因为保存和恢复寄存器状态是一个耗时的操作。通过减少这些操作,可以提高虚拟机的性能。
但是,操作系统可能会在启动后对寄存器进行不寻常的重新编程。为避免这种情况造成任何问题,管理程序可以设置 HCR_EL2.TVM 陷阱。这一设置会导致对 MMU 相关寄存器的任何写入都会向 EL2 生成一个陷阱,从而允许管理程序检测是否需要更新这些寄存器的保存副本。
在虚拟化环境中,
HCR_EL2
用于控制 Hypervisor 对各种操作和事件的拦截和管理。TVM
位专门用于控制对内存管理单元(MMU)配置寄存器的访问。当
HCR_EL2.TVM
位被设置(通常设置为1
),**任何试图从较低异常级别(如 EL1,通常是操作系统级别)对关键的 MMU 寄存器进行写入的操作都会被拦截。**这些寄存器包括TTBR0_EL1
,TTBR1_EL1
,TCR_EL1
, 和MAIR_EL1
。
TTBR0_EL1
存储页表的基地址,用于短地址空间或低虚拟地址空间的转换。在典型的配置中,它用于用户空间地址的转换。TTBR1_EL1
同样存储页表的基地址,但它用于长地址空间或高虚拟地址空间的转换。通常,它用于内核空间地址的转换。TCR_EL1
用于配置内存地址转换的各种参数。这包括 TTBR0 和 TTBR1 的大小(即它们各自覆盖的地址范围),页表的类型(例如页大小),以及其他控制选项,如硬件访问标志的使用等。MAIR_EL1
定义了内存区域的属性。这些属性包括是否可缓存、缓存的类型(如写回或写通)、内存的访问权限等。页表项中的属性字段会引用其中的一个属性。
MIDR and MPIDR
使用陷阱虚拟化操作需要大量计算。操作会向 EL2 生成一个陷阱异常,然后管理程序会确定所需的操作,对其进行仿真,然后返回客户机。ID_AA64MMFR0_EL1 等功能寄存器不常被操作系统访问。这意味着,在将对这些寄存器的访问捕获到管理程序以模拟读取时,计算是可以接受的。
对于访问频率较高的寄存器,或在性能关键代码中,要避免这种计算负载。这种计算负载。这些寄存器及其值的例子包括:
- MIDR_EL1 处理器类型,例如 Cortex-A53
- MPIDR_EL1 亲和性,例如处理器 2 的内核 1
虚拟机管理程序可能希望客户操作系统能看到这些寄存器的虚拟值,而无需捕获每个单独的访问。对于这些寄存器,体系结构提供了陷阱之外的另一种选择:
- VPIDR_EL2 ,这是 EL1 读取 MIDR_EL1 时返回的值。
- VMPIDR_EL2 , 这是 EL1 读取 MPIDR_EL1 时返回的值。
管理程序可以在进入虚拟机之前设置这些寄存器。如果在虚拟机中运行的软件读取了 MIDR_EL1 或 MPIDR_EL1,硬件将自动返回虚拟值,而不需要触发陷阱。
VMPIDR_EL2 和 VPIDR_EL2 没有定义重置值。**它们必须在首次进入 EL1 之前由启动代码初始化。**这一点在裸机环境中尤为重要。
6、Virtualizing exceptions
中断被系统中的硬件用来向软件发送事件信号。例如,图形处理器可能会发送中断信号,表示它已完成一帧图像的渲染。
**使用虚拟化的系统则更为复杂。有些中断可能由管理程序本身处理。其他中断可能来自分配给虚拟机(VM)的设备,需要由虚拟机内的软件处理。**此外,在收到中断时,中断所针对的虚拟机可能并不在运行。
这意味着需要有机制来支持管理程序处理 EL2 中的某些中断。此外,还需要将其他中断转发到特定虚拟机或虚拟机中的特定虚拟 CPU (vCPU) 的机制。
为了启用这些机制,该架构支持虚拟中断:vIRQs、vFIQs 和 vSErrors。这些虚拟中断的行为与物理中断(IRQ、FIQ 和 SErrors)类似,但只能在 EL0 和 EL1 中执行时发出信号。在 EL2 或 EL3 中执行时,无法接收虚拟中断。
概括而言,Armv8.4-A 引入了对安全状态下虚拟化的支持。要在安全 EL0/1 中发出虚拟中断信号,必须支持并启用安全 EL2。否则,虚拟中断不会在安全状态下发出信号。
Enabling virtual interrupts
**要向 EL0/1 发送虚拟中断信号,管理程序必须设置 HCR_EL2 中的相应路由位。**例如,要启用 vIRQ 信号,管理程序必须设置 HCR_EL2.IMO。该设置将物理 IRQ 异常路由到 EL2,并启用向 EL1 发送虚拟异常信号。
虚拟中断按中断类型进行控制。理论上,虚拟机可以配置为接收物理 FIQ 和虚拟 IRQ。实际上,这种情况并不常见。虚拟机通常只配置为接收虚拟中断。
HCR_EL2
(Hypervisor Configuration Register at Exception Level 2)是一个配置寄存器,用于控制虚拟化环境下的各种行为。- 在中断管理方面,
HCR_EL2
中的某些位,如IMO
(IRQ Mask Override),用于控制中断信号的路由。例如,当HCR_EL2.IMO
被设置时,物理 IRQ(Interrupt Request)中断会被路由到 EL2(Hypervisor层),而不是直接发送到 EL1(操作系统层)。- 在虚拟化环境中,Hypervisor 可以拦截物理中断,并根据需要将它们转换为虚拟中断(vIRQ)。这允许 Hypervisor 控制和管理虚拟机接收的中断信号。
Example of a physical interrupt being forwarded to a vPE:
Generating virtual interrupts
生成虚拟中断有两种机制:
- 由内核在内部使用 HCR_EL2 中的控制。
- 使用 GICv2 或更高版本的中断控制器。
让我们从机制 1 开始。HCR_EL2 中有三个位控制虚拟中断的产生:
- VI = 设置该位可注册 vIRQ。
- VF = 设置该位寄存 vFIQ。
- VSE = 设置该位将寄存一个 vSError。
设置其中一个位相当于中断控制器向 vCPU 发出中断信号。生成的虚拟中断会受到 PSTATE 屏蔽,就像普通中断一样。
这种机制使用简单,但缺点是只能提供生成中断的方法。然后,管理程序需要在虚拟机中模拟中断控制器的运行。总而言之,在软件中捕获和模拟操作涉及开销,对于中断等频繁操作最好避免使用。
第二种选择是使用 Arm 的通用中断控制器 (GIC) 生成虚拟中断。在 Arm GICv2 中,GIC 通过提供物理 CPU 接口和虚拟 CPU 接口,可以同时发出物理和虚拟中断信号,如下图所示:
这两个接口完全相同,只是一个发出物理中断信号,另一个发出虚拟中断信号。**管理程序可以将虚拟 CPU 接口映射到虚拟机中,让虚拟机中的软件直接与 GIC 通信。**这种方法的优势在于,管理程序只需设置虚拟接口,而无需对其进行仿真。这种方法减少了执行过程中被困到 EL2 的次数,从而降低了中断虚拟化的开销。
虽然 Arm GICv2 可用于 Armv8-A 设计,但更常见的是使用 GICv3 或 GICv4。
Example of forwarding an interrupt to a vCPU
到目前为止,我们已经了解了虚拟中断是如何启用和生成的。下面我们来看一个示例,说明如何将虚拟中断转发给 vCPU。在这个示例中,我们将考虑一个已分配给虚拟机的物理外设,如下图所示:
下图说明了这些步骤:
- 物理外设向 GIC 发出中断信号。
- GIC 生成 IRQ 或 FIQ 物理中断异常,并通过 HCR_EL2.IMO/FMO 的配置路由到 EL2。管理程序会识别外设,并确定它已分配给某个虚拟机。它会检查中断应转发给哪个 vCPU。
- 管理程序配置 GIC,将物理中断作为虚拟中断转发给 vCPU。然后,GIC 将发出 vIRQ 或 vFIQ 信号,但处理器在 EL2 中执行时将忽略该信号。
- 管理程序将控制权交还给 vCPU。
- 现在处理器处于 vCPU(EL0 或 EL1)中,可以从 GIC 捕捉虚拟中断。该虚拟中断受 PSTATE 异常掩码的限制。
更详细的解释:
- 物理外设向 GIC 发出中断信号:当物理设备需要处理器注意时,它会向 GIC 发送中断信号。
- GIC 生成 IRQ 或 FIQ 物理中断异常,并通过 HCR_EL2.IMO/FMO 的配置路由到 EL2:GIC 接收到中断后,会根据配置生成相应的中断异常(IRQ 或 FIQ),并通过 Hypervisor Configuration Register (HCR_EL2) 中的 IMO/FMO 设置来决定是否将这些中断发送到 EL2(hypervisor 层)。管理程序在 EL2 识别到这个中断,并确定其对应的虚拟机。
- 管理程序配置 GIC,将物理中断转为虚拟中断发送给 vCPU:一旦确定了中断属于哪个虚拟机,hypervisor 就会配置 GIC,将这个物理中断转换为一个虚拟中断(vIRQ 或 vFIQ),并指定给相应的虚拟CPU (vCPU)。但是,如果处理器当前在 EL2 执行,它会忽略这个虚拟中断信号。(暂时忽略,由hypervisor 管理调度,在合适的时机会拿出来处理)
- 管理程序将控制权交还给 vCPU:hypervisor 完成中断处理后,会将控制权返回给对应的 vCPU。
- vCPU 处理虚拟中断:此时,处理器在执行 vCPU 的代码(通常在 EL0 或 EL1),可以接收并处理从 GIC 发送过来的虚拟中断。这个虚拟中断的处理受到程序状态寄存器(PSTATE)中的异常掩码限制。
示例显示物理中断被转发为虚拟中断。该示例与第 2 阶段转换部分所述的指定外设模型相匹配。对于虚拟外设,管理程序可以创建虚拟中断,而无需将其链接到物理中断。
Interrupt masking and virtual interrupts
在 AArch64 异常模型指南中,我们介绍了 PSTATE 中的中断掩码位,PSTATE.I 用于 IRQ,PSTATE.F 用于 FIQ,PSTATE.A 用于 SErrors。在虚拟化环境中运行时,这些掩码的工作方式略有不同。
例如,对于 IRQ,我们已经看到,设置 HCR_EL2.IMO 有两个作用:
- 将物理 IRQ 路由到 EL2
- 启用 EL0 和 EL1 中的 vIRQ 信号
该设置还改变了 PSTATE.I 掩码的应用方式。在 EL0 和 EL1 中,如果 HCR_EL2.IMO==1,PSTATE.I 将对 vIRQ 而非 pIRQ 执行操作。
7、Virtualizing the generic timers
Arm 架构包括通用定时器,这是一套标准化的定时器,每个处理器都有。通用定时器由一组比较器组成,比较器与通用系统计数进行比较。当比较器的值等于或小于系统计数时,比较器就会产生中断。在下图中,我们可以看到系统中的通用定时器(橙色),以及由比较器和计数器模块组成的组件。
下图显示了一个带有管理程序的示例系统,该管理程序托管两个虚拟 CPU(vCPU):
在示例中,我们忽略了运行管理程序在 vCPU 之间进行上下文切换的开销。
在 4 毫秒的物理时间或挂钟时间之后,每个 vCPU 运行了 2 毫秒。如果 vCPU0 在 T=0 时设置了比较器,以便在 3 毫秒后产生中断,那么中断是否会触发?
或者,您是希望在虚拟时间(即 vCPU 经历的时间)2 毫秒后触发中断,还是希望在壁钟时间 2 毫秒后触发中断?
根据虚拟化的用途,Arm 架构可以同时实现这两种功能。让我们看看它是如何做到这一点的。
在 vCPU 上运行的软件可以访问两个计时器:
- EL1 物理定时器
- EL1 虚拟计时器
EL1 物理定时器与系统计数器模块生成的计数进行比较。使用该定时器可获得挂钟时间。
EL1 虚拟计时器与虚拟计数进行比较。虚拟计数是物理计数减去一个偏移量。管理程序会在寄存器中指定当前调度的 vCPU 的偏移量。这样就可以在 vCPU 未计划运行时隐藏时间的流逝:
为了说明这个概念,我们可以扩展之前的示例,如下图所示:
在一个持续 6 毫秒的时间段内,每个虚拟 CPU(vCPU)运行 3 毫秒。**Hypervisor 可以使用偏移寄存器来展示一个只显示 vCPU 运行时间的虚拟计数。**或者,Hypervisor 也可以将偏移保持为 0,这意味着虚拟时间与物理时间相同。
示例显示系统计数器的频率为 1 毫秒。实际上,这个频率值是非常不可能的。我们建议将系统计数器的频率设置在 1MHz 到 50MHz 之间。
8、Virtualization host extensions
下图显示了我们在虚拟化异常部分中探讨的软件堆栈和异常级别的简化版本:
你可以看到独立 Hypervisor 是如何映射到 ARM 异常级别的。Hypervisor 运行在 EL2,而虚拟机(VMs)运行在 EL0/1。对于托管 Hypervisor 来说,这种情况更为复杂,如下图所示:
传统上,内核运行在 EL1,但虚拟化控制在 EL2。这意味着主机操作系统的大部分都在 EL1 中运行,而一些存根代码则在 EL2 中运行,以访问虚拟化控制。这种安排可能效率不高,因为它可能涉及额外的上下文切换。
内核需要处理 EL1 和 EL2 运行时的一些差异,但这些差异仅限于少数子系统,例如早期启动。
DynamIQ 处理器(Cortex-A55、Cortex-A75 和 Cortex-A76)支持虚拟化主机扩展(VHE)。
Running the Host OS at EL2
VHE 由 HCR_EL2 中的两个位控制。这些位可以概括为
- E2H:控制是否启用 VHE。
- TGE: 启用 VHE 时,控制 EL0 是访客还是主机。
下表总结了典型的设置:
*软件必须在运行主机内核的主要部分之前设置该位。
你可以从下图中看到这些典型的设置:
Virtual address space
下图显示了 EL0/EL1 的虚拟地址空间在引入 VHE 之前的样子:
正如内存管理中所讨论的,**EL0/1 有两个区域。按照惯例,上面的区域称为内核空间,下面的区域称为用户空间。**然而,**EL2 在地址范围的底部只有一个区域。**造成这种差异的原因是,传统上,管理程序不会托管应用程序。这意味着管理程序不需要划分内核空间和用户空间。
**将内核空间分配到上层区域、将用户空间分配到下层区域只是一种惯例。**这不是 Arm 架构的强制规定。
EL0/1 虚拟地址空间也支持地址空间标识符(ASID),但 EL2 不支持。这是因为管理程序通常不会托管应用程序。
为了让主机操作系统在 EL2 中高效执行,我们需要添加第二个区域和 ASID 支持。如下图所示,设置 HCR_EL2.E2H 可以解决这些问题:
在 EL0 中,HCR_EL2.TGE 控制使用哪个虚拟地址空间:或EL1 空间或 EL2 空间。使用哪个空间取决于应用程序是在主机操作系统(TGE==1)下运行,还是在访客操作系统(TGE==0)下运行。
Re-directing register accesses
我们在 "虚拟化通用定时器 "一节中看到,启用 VHE 会改变 EL2 虚拟地址空间的布局。这是因为内核会尝试访问 _EL1 寄存器,如 TTBR0_EL1,而不是 _EL2 寄存器,如 TTBR0_EL2。
**要在 EL2 运行相同的二进制文件,我们需要将 EL1 寄存器的访问重定向到 EL2 的对应寄存器。**设置 E2H 就可以做到这一点,从而将_EL1 系统寄存器的访问重定向到 EL2 的对应寄存器。这种重定向如下图所示:
然而,这种重定向给我们带来了一个新问题。管理程序仍然需要访问真正的 _EL1 寄存器,以便实现任务切换。为了解决这个问题,我们引入了一组新的寄存器别名,其后缀为 _EL12 或 _EL02。在 EL2 使用时,当 E2H==1 时,这些别名可以访问 EL1 寄存器进行上下文切换。如下图所示:
Exceptions
通常,HCR_EL2.IMO/FMO/AMO 位控制物理异常是路由到 EL1 还是 EL2。在 TGE==1 的 EL0 中执行时,所有物理异常都会路由到 EL2,除非它们被 SCR_EL3 路由到 EL3。无论 HCR_EL2 路由位的实际值如何,情况都是如此。这是因为应用程序是作为主机操作系统的子操作系统而不是客户操作系统执行的。因此,任何异常都应路由到在 EL2 中运行的主机操作系统。
11、Costs of virtualization
虚拟化的成本取决于每当管理程序需要为虚拟机提供服务时,在虚拟机(VM)和管理程序之间切换所需的时间。在 Arm 系统上,这种成本的下限是
- 31x 64 位通用寄存器(X0…X30)
- 32 个 128 位浮点/SIMD 寄存器(V0…V31)
- 两个堆栈指针(SP_EL0、SP_EL1)
使用 LDP 和 STP 指令,管理程序需要 33 条指令来保存或恢复这些寄存器。
项目虚拟化的确切成本取决于平台和管理程序的设计。