棋子 · 4 天前

KVM/Arm——基于 Arm 虚拟化扩展的VMM

1. 前言

Arm 架构为了支持虚拟化做了些扩展,称为虚拟化扩展(Virtualization Extensions)。原先为 VT-x 创建的 KVM(Linux-based Kernel Virtual Machine)适配了 Arm 体系结构,引入了 KVM/Arm (the Linux Arm hypervisor)。KVM/Arm 没有在 hypervisor 中引入复杂的核心功能,而是利用了 Linux 内核中现有的功能进行适配。KVM/Arm 与 Linux 集成,在可移植性和硬件支持方面会有所收益。其实也可以开发独立的 bare metal hypervisor,这样可能会有更好地性能,但这种方法在 Arm 上不太好使。

在下面,我们将描述 KVM/Arm 的设计如何使得它可以从现有 kernel 的集成中获益,同时利用硬件虚拟化特性。

2. Split-mode 虚拟化

KVM/Arm 利用了现有的 kernel 功能,例如调度器(scheduler),但 KVM/Arm 如果直接在 EL2 中运行 Linux kernel,会存在两个问题。

第一,linux 中与底层体系结构相关的代码是为在 kernel 模式下工作而编写的,不经过修改就不能在 EL2 中运行,因为 EL2 是一个完全不同于普通 kernel 模式的 CPU 模式。Linux kernel 社区不太可能接受在 EL2 中运行 kernel 所需的重大更改。更重要的是,为了保持与没有 EL2 的硬件的兼容性并将 Linux 作为 Guest OS 运行,必须编写低级代码以在两种模式下工作,这可能会导致缓慢而复杂的代码路径。举个简单的例子,页表错误处理程序需要获取导致页表错误的虚拟地址。在 EL2 中,这个地址存储在与 kernel 模式不同的寄存器中。

第二,在 EL2 中运行整个内核会对本机性能产生不利影响。例如,EL2 有自己独立的地址空间,但是 kernel 模式使用两个页表基本寄存器(TTBR)在 user 地址空间和 kernel 地址空间之间提供熟悉的 3GB/1GB 分割,而 EL2 使用单个页表寄存器,因此不能直接访问地址空间的 user 空间部分。经常使用的访问 user 内存的函数需要 kernel 显式地将用户空间数据映射到 kernel 地址空间,然后执行必要的拆解和 TLB 维护操作,从而导致 Arm 的性能不好。

综上所述,KVM/Arm 引入了 split-mode 虚拟化,这种一种新的 hypervisor 设计方法,它将核心 hypervisor 分开,以便它可以跨不同的特权 CPU 模式运行,从而利用每种 CPU 模式提供的特定优势和功能。KVM/Arm 使用 split-mode 虚拟化来利用 EL2 支持的 Arm 硬件虚拟化,同时利用在 kernel 模式下运行的现有 Linux kernel 服务。Split-mode 虚拟化允许 KVM/Arm 与 Linux kernel 集成,而无需对现有代码库进行重大修改。

这是通过将 hypervisor 分成两个组件来实现的:lowvisorhighvisor,如图 1 所示。

image.png

图 1 KVM/Arm 系统架构

Lowvisor 的设计是利用 EL2 中提供的硬件虚拟化支持来提供三个关键功能。首先,lowvisor 通过适当的硬件配置来设置正确的执行上下文,并在不同的执行上下文之间实施保护和隔离。Lowvisor 直接与硬件保护功能交互,因此非常关键,且代码库需要保持绝对最小。其次,lowvisor 从 VM 执行上下文切到到 host 执行上下文,反之亦然。Host 执行上下文用于运行 hypervisor 和 host Linux kernel。我们将执行上下文称为一个世界,将从一个世界切换到另一个世界称为世界切换(world switch),因为系统的整个状态都发生了变化。由于 lowvisor 是唯一在 EL2 中运行的组件,因此只有它可以负责执行世界切换所需的硬件重新配置。第三,lowvisor 提供了一个虚拟化 Trap 处理程序,它处理必须 Trap 到 hypervisor 的中断和异常。所有发送到 hypervisor 的 Trap 必须首先发送到 lowvisor。在世界切换到 highvisor 完成后,lowvisor 只执行所需的最少量的处理,并将要完成的大部分工作推迟到 highvisor。

Highvisor 作为 host linux kernel 的一部分在 kernel 模式下运行。因此,它可以直接利用现有的 linux 功能,例如调度器,并且可以利用标准的 kernel 软件数据结构和机制来实现其功能,例如 locking 机制和 memory 分配功能。它使高层功能更容易在 highvisor 中实现。例如,虽然 lowvisor 提供低级 Trap 处理程序和低级机制来从一个世界切换到另一个世界,但 highvisor 处理来自 VM 和的 stage-2 页表错误并执行指令仿真。请注意,部分 VM 以 kernel 模式运行,就像 highvisor 一样,但是启用了 stage-2 转换和 Trap 到 EL2。

因为 hypervisor 在 kernel 模式和 EL2 之间是分开的,所以在 VM 和 highvisor 之间的切换涉及到多个模式转换。在运行 VM 时,向 highvisor 发出 Trap 将首先向 EL2 中 lowvisor 发出 Trap。然后 lowvisor 将发起另一个 Trap 去运行 highvisor。类似地,从 highvisor 切换到 VM 需要从 kernel 模式切换到 EL2,然后切换到 VM。因此,在切换到或切换出 highvisor 时,split-mode 虚拟化会产生双重 Trap 成本。在 Arm 上,执行这些模式转换到 EL2 或从 EL2 转换的唯一方法就是通过 Trap。不过,这个额外的 Trap 在 Arm 上并不是一个显著的性能成本。

KVM/Arm 使用内存映射接口根据需要在 highvisor 和 lowvisor 之间共享数据。由于内存管理可能很复杂,它利用了 linux 中现有内存管理子系统的功能来管理 highvisor 和 lowvisor 的内存。但是管理 lowvisor 的内存涉及到额外的挑战,它需要管理 EL2 的单独地址空间。一种简单的方法是重用 host kernel 的页表,并在 EL2 中使用它们来使地址空间相同。不过这样有问题,因为 EL2 使用和 kernel 模式不同的页表格式。因此,highvisor 显式地管理 EL2 页表,将在 EL2 中执行的任何代码以及 highvisor 和 lowvisor 之间共享的任何数据结构映射到 EL2 和 kernel 模式中的相同虚拟地址。

3. CPU 虚拟化

为了虚拟化 CPU,KVM/Arm 必须向 VM 提供一个接口,该接口本质上与底层的实际硬件相同,同时确保 hypervisor 仍然控制硬件。它包括确保在虚拟机中运行的软件必须与在物理 CPU 上运行的软件具有对相同寄存器状态的一致访问,以及确保切换 VM 运行中,与 hypervisor 及其 host kernel 相关联的物理硬件状态是一致的。为了不影响 VM 隔离,寄存器状态可以简单地通过保存 VM 状态和从 VM 切换到 host 时从内存恢复主机状态进行上下文切换,反之亦然。KVM/Arm 将对所有其它敏感状态的访问配置为 Trap 到 EL2,因此 hypervisor 可以模拟它。

表 1 为在 kernel 模式和 user 模式下运行的软件可以看到的寄存器状态,以及 KVM/Arm 对每个寄存器组的虚拟化方法。Lowvisor 有自己专用的配置寄存器,仅供 EL2 使用。只要硬件支持,在世界切换期间,KVM/Arm 上下文会切换寄存器,因为它允许 VM 直接访问硬件。例如,VM 可以直接对 stage-1 页表基本寄存器进行编程,而不需要 Trap 到 hypervisor,这在大多数 Guest OS 中是相当常见的操作。KVM/Arm 在敏感指令和访问硬件状态时执行 Trap 和模拟,这些状态可能会影响 hypervisor 或将有关硬件的信息泄漏给 VM,违反了虚拟化原理。例如。如果 VM 执行导致 CPU 断电的 WFI 指令,KVM/Arm 会 Trap,这样的操作应该只由 hypervisor 执行,以保持对硬件的控制。

表 1 VM 和 Host 的状态

Image

在 kernel 或 user 模式下运行虚拟机与在 kernel 或 user 模式下运行 hypervisor 之间的区别取决于 EL2 在世界切换期间如何配置 virtualization extensions。从 host 切换到 VM的过程如下:

  1. 将所有 host GP 寄存器存到 EL2 的堆栈中;
  2. 给 VM 配置 vGIC;
  3. 给 VM 配置 timers;
  4. 将所有 host 特定的配置寄存器存到 EL2 堆栈中;
  5. 将 VM 的配置寄存器加载到硬件,这个操作不会影响当前执行,因为 EL2 使用它自己的配置寄存器,这些寄存器从 host 状态中分离出来。
  6. 配置 EL2 以 Trap 浮点操作(VFP 寄存器的 lazy context switching),trap 中断,trap CPU halt 指令(WFI/WFE),trap SMC 指令,Trap 特定配置寄存器访问和 trao 调试寄存器访问;
  7. 将虚拟机特定的 ID 写入 shadow ID 寄存器中,这由 Arm 虚拟化扩展定义,并由虚拟机访问,以代替 ID 寄存器中的硬件值;
  8. 设置 stage-2 页表基寄存器(VTTBR)并启用 stage-2 地址转换;
  9. 恢复所有 guest GP 寄存器;
  10. 进入 user 模式或 kernel 模式;

CPU 将留在虚拟机世界,直到有 event 发生,触发进入 EL2 的 trap。这样的事件可能由上面提到的任何 Trap、stage-2 页表错误或硬件中断引起。由于 event 需要 highvisor 的服务,要么模拟虚拟机预期的硬件行为,要么服务于设备中断,KVM/Arm 必须执行另一个世界切换到 highvisor 和它的 host。这需要先到 lowvisor,然后再到 highvisor。从 VM 世界切换到 host 世界需要以下步骤:

  1. 保存所有 VM GP 寄存器;
  2. 关闭 stage-2 转换;
  3. 配置 EL2 不 Trap 任何寄存器访问和指令;
  4. 保存所有 VM 特定的配置寄存器;
  5. 加载 host 的配置寄存器到硬件;
  6. 配置 host 的 timers;
  7. 保存 VM 特定的 vGIC 状态;
  8. 恢复所有 host GP 寄存器
  9. 进入 kernel 模式;

4. 内存虚拟化

如图 2 所示,ARM 提供了 stage-2 页表来将 Guest 物理地址转换为 host 物理地址。KVM/Arm 通过启用 stage-2 转换,为在虚拟机中运行时为所有内存访问提供内存虚拟化。Stage-2 转换只能在 EL2 中配置,它的使用对 VM 是完全透明的。Highvisor 管理 stage-2 翻译页表,只允许访问专门为 VM 分配的内存。其它访问将导致 stage-2 页表错误,从而 Trap 到 hypervisor。该机制确保虚拟机不能访问 hypervisor 或其它虚拟机的内存,包括任何敏感数据。在 highvisor 和 lowvisor 中运行时禁用 stage-2 转换,因为 highvisor 完全控制整个系统并直接管理 host 物理地址。当 hypervisor 切换到 VM 时,它启用 stage-2 转换并相应地配置 stage-2 页表基寄存器。尽管 highvisor 和 VM 共享相同的 CPU 模式,但 stage-2 转换确保 highvisor 不受 VM 的任何访问。

Image

图 2 Arm 的两级页表

KVM/Arm 是一个 split-mode 虚拟化来利用现有的 kernel 内存分配、页表引用计数和页表操作代码。KVM/Arm 通过考虑出错的 gPA 来处理 stage-2 页表出错,以及该地址是否属于 VM 内存映射,KVM/Arm 通过简单地调用一个现有的 kernel 函数(如 get_user_pages)为 VM 分配一个页表,并将分配的页表映射到 stage-2 页表中的 VM。

5. I/O 虚拟化

KVM/Arm 利用现有的 QEMU 和 Virtio user 空间设备模拟来提供 I/O 虚拟化。在硬件层面上,ARM 架构上的所有 I/O 机制都是基于对 MMIO 设备区域的 load/store 操作。除了直接分配给 VM 的设备,所有硬件 MMIO 区域不能被虚拟机访问。KVM/Arm 使用 stage-2 转换来确保不能从虚拟机直接访问物理设备。在为 VM 分配的 RAM 区域之外的任何访问都将被 hypervisor Trap,hypervisor 可以根据 fault 地址将访问路由到 QEMU 中的特定模拟设备。

6. 中断虚拟化

KVM/Arm 利用其与 Linux 的集成来重用现有的设备驱动程序和相关功能,包括处理中断。当在虚拟机中运行时,KVM/Arm 配置 CPU 将所有硬件中断 Trap 到 EL2。在每个中断中,它执行到 hypervisor 的世界切换,host 处理中断,这样 hypervisor 就可以保留完全控制硬件资源。当在 host 和 highvisor 中运行时,中断直接 Trap 到 kernel 模式,避免通过 EL2 的开销。在这两种情况下,所有硬件中断处理都是通过重用 Linux 现有的中断处理功能在 host 中完成的。

但是,虚拟机必须以虚拟中断的形式接收来自模拟设备的通知,multicore Guest OSs 必须能够从一个虚拟 core 发送虚拟 IPIs 到另一个虚拟 xore。KVM/Arm 通过 vGIC 向虚拟机注入虚拟中断,减少 EL2 的 Trap 数量。通过编程 vGIC hypervisor CPU 控制接口中的 list registers,虚拟中断被提升到虚拟 CPU。KVM/Arm 配置 stage-2 页表,来防止虚拟机访问控制接口,只允许访问 vGIC 虚拟 CPU 接口,保证只有 hypervisor 可以编程控制接口,虚拟机可以直接访问 vGIC 虚拟接口。然而,Guest OS 仍然会尝试访问 GIC 分发器来配置 GIC,并讲 IPI 从一个虚拟 core 发送到另一个虚拟 core。这样的访问将 Trap 到 hypervisor,hypervisor 必须模拟分发程序。

KVM/Arm 引入了虚拟分发器,这是 GIC 分发器的一个软件模型,作为 highvisor 的一部分。虚拟分发器向 User 空间公开接口,因此 user 空间中的模拟设备可以向虚拟分发器发起虚拟中断,并向 VM 公开与物理 GIC 分发器相同的 MMIO 接口。虚拟分发器保存关于每个中断状态的内部软件状态,并在调度 VM 时使用此状态,编程 list registers 以注入虚拟中断。例如,如果虚拟 CPU0 向虚拟 CPU1 发送一个 IPI,分发程序将编程虚拟 CPU1 的 list registers 以引发一个虚拟 IPI 中断给虚拟 CPU1。

理想情况下,虚拟分发程序只在必要时访问硬件 list registers,因为设备 MMIO 操作通常比 cache 内存访问慢得多。当调度不同的 VM 在物理 core 上运行时,需要 list registers 的完整上下文切换,但当简单地在 VM 和 hypervisor 之间切换时,则不一定需要上下文切换。例如,如果没有挂起的虚拟中断,则不需要访问任何 list register。注意,一旦 hypervisor 在切换到 VM 时将一个虚拟中断写入一个 list register,那么在切换会 hypervisor 时,它还必须读回这个 list register,因为 list register 描述了虚拟中断的状态。KVM/Arm 的初始未优化版本使用了一种简单的方法,该方法完全切换所有 vGIC 状态,包括每个世界开关上的 list register。

7. 计时器虚拟化

读取计数器和编程计时器是许多操作系统中用于进程调度和定期轮询设备状态的常见操作。例如,Linux 读取一个计数器来确定进程是否已经过期,并编程计数器来确保进程没有超出其允许的时间片段。由于各种原因,应用程序工作负载也经常使用计时器。为每个这样的操作 Trap 到 hypervisor 可能会导致明显的性能开销,并且允许 VM 直接访问计时硬件通常意味着放弃对硬件资源的定时控制,因为 VM 可以禁用计时器并在较长的时间内控制 CPU。

KVM/Arm 利用 Arm 的通用定时器的硬件虚拟化特性来允许虚拟机直接访问可读计数器和编程计时器,而不会引起 Trap 到 EL2,同时确保 hypervisor 仍然控制硬件。由于使用 EL2 控制对物理计时器的访问,因此任何控制 EL2 模式的软件都可以访问物理计时器。KVM/Arm 通过使用 hypervisor 中的物理计时器和不允许从虚拟机访问物理计时器来维护硬件控制。作为 Guest OS 运行的 Linux kernel 只能访问虚拟计时器,因此可以直接访问计时器硬件,而不会被 hypervisor Trap 住。

不过由于体系结构的限制,虚拟计时器不能直接引发虚拟中断,而总是引发硬件中断,而硬件中断会 Trap 到 hypervisor。KVM/Arm 检测虚拟机的虚拟计时器是否到期,并向虚拟机注入相应的虚拟中断,在 highvisor 中执行所有硬件 ACK 和 EOI 操作。硬件只为每个物理 CPU 提供一个虚拟计时器,多个虚拟 CPU 可以再这个硬件实例上复用。为了在这种情况下支持虚拟计时器,KVM/Arm 在虚拟机进入 hypervisor 时检测未过期的计时器,并利用现有的 OS 功能在虚拟计时器本来触发的时候编程一个软件计时器,让虚拟机处于运行状态。当这样的软件计时器触发时,将执行一个回调函数,该函数使用上面描述的虚拟分发器向 VM 引发一个虚拟计时器中断。

8. Arm 体系结构的改进

根据 KVM/Arm 开发人员的经验,对 Arm 架构进行了一系列改进,以避免对 KVM/Arm 等 type-2 hypervisor 进行 split-mode 虚拟化的需要。这些改进包括Virtualization Host Extensions(VHE),它现在是 Arm 64-bit 架构新版本 ARMv8.1 的一部分。VHE 允许将设计在 EL1 中运行的 OS 运行在 EL2 中,而无需对 OS 源代码进行实质性修改。

VHE 是通过添加一个新的控制位 E2H 来提供的,该位在系统启动时安装使用 VHE 的 type-2 hypervisor 是设置的。如果该位没有设置,ARMv8.1 在硬件虚拟化方面与 ARMv8 相同,保留了与现有 hypervisor 的向后兼容性。如果该位设置了,那么 VHE 将开启三个主要特性。

首先,VHE 扩展了 EL2,向 CPU 添加额外的物理寄存器状态,这样 EL1 中可用的任何寄存器和功能也可以在 EL2 中使用。例如,EL1 有两个寄存器:TTBR0_EL1 和 TTBR1_EL1。第一个用于查找页表中较低 VA 范围内的虚拟地址,第二个用于较高 VA 范围内的虚拟地址。它提供了一种在 user 空间和 kernel 空间之间分隔虚拟空间的方便、高效地方法。但是,如果没有 VHE,EL2 只有一个页表基寄存器 TTBR0_EL2,这使得在 EL2 中运行时支持 EL1 的分隔 VA 空间存在问题。对于 VHE,EL2 将有第二个页表基寄存器 TTBR1_EL2,从而可以支持分隔 VA 空间 EL2 的方式与 EL1 相同。用于启用与 host 集成的 type-2 hypervisor 操作系统去支持在 EL2 种分割 VA 空间,这是运行 EL2 种的 host OS 所必需的,以便管理 user 空间和 kernel 之间的 VA 空间。

其次,VHE 提供了一种机制来透明地访问额外的 EL2 寄存器状态。简单地提供额外的 EL2 寄存器不足以在 EL2 中运行未修改的 OS,因为现有的操作系统被写入访问 EL1 寄存器。例如,Linux 使用了 TTBR1_EL1,不影响 EL2 种运行的 translation 系统。提供额外的寄存器 TTBR1_EL2 仍然需要修改 Linux,以便分别在 EL2 中运行时使用 TTBR1_EL2 而不是 TTBR1_EL1。为了避免迫使 OS 供应商给软件增加这种额外的复杂性,VHE 允许未经修改的软件在 EL2 种执行,并使用 EL1 寄存器访问函数指令编码透明的访问 EL2 寄存器。例如,当前的操作系统软件用指令 MRS x1,TTBR1_EL1 读取 TTBR1_EL1 寄存器。对于 VHE,软件仍然执行相同的指令,但是硬件实际访问的是 TTBR1_EL2 寄存器。只要设置了 E2H bit,访问 EL1 寄存器实际上访问了 EL2 寄存器,从而透明地重写了对 EL2 的寄存器访问。添加了一组新的特殊指令来访问 EL2 中 EL1 寄存器,hypervisor 可以使用这些指令在将 EL1 中运行的 VM 之间进行切换。例如,如果 hypervisor 希望访问 Guest 的 TTBR1_EL1,它将使用指令 MRS x1,TTBR_EL21。

第三,VHE 扩展了 EL2 的 memory translation 能力。在 ARMv8 种,EL2 和 EL1 使用不同的页表格式,因此编写在 EL1 种运行的软件必须经过修改才能在 EL2 中运行。在 ARMv8.1 中,当设置了 E2H 位时,EL2 页表格式现在与 EL1 格式兼容。因此,以前在 EL1 中运行的 OS 现在可以在 EL2 中运行而无需修改,因为它可以使用相同的 EL1 页表格式。

图 3 显示 type-1 和 type-2 hypervisor 程序如何映射到具有 VHE 的体系结构。

Image

图 3 虚拟化扩展(VHE)

Type-1 hypervisor(如 Xen)可以明确地设计为在 EL2 中运行,而无序任何额外的支持,它不设置 VHE 引入的 E2H 位,并且 EL2 的行为与 ARMv8 完全相同。Type-2 hypervisor(如 KVM/Arm)在系统启动时设置了 E2H 位,host OS kernel 只在 EL2 中运行,不会在 EL1 中运行。Type-2 hypervisor kernel 可以在 EL2 中不加修改地运行,因为 VHE 为每个 EL1 寄存器提供了一个等效的 EL2 寄存器,并透明地将 EL1 寄存器访问从 EL2 重写为 EL2 寄存器访问,而且 EL1 和 EL2 之间的页表格式现在是兼容的。从 host user 空间到 host kernel 的转换直接从 EL0 到 EL2 进行,例如处理系统调用,如图 3 的箭头所示。从 VM 到 hypervisor 的转换现在无须上下文切换 EL1 状态,因为 hypervisor 不使用 EL1。

END

文章来源:专芯致志 er

推荐阅读

更多 IC 设计干货请关注IC 设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。

推荐阅读
关注数
20643
内容数
1316
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息