1.前言
Arm 架构虚拟化扩展(Virtualization Extension)是在 2010 年作为 ARMv7 架构的一部分引入的,它为虚拟化提供了架构支持。在此之前,ARM 系统上的虚拟化解决方案都是基于半虚拟化(paravirtualization)的,并没有被广泛使用。不过随着 Arm CPU 的性能不断提高,并从智能手机和平板电脑等移动设备向传统服务器进军,人们对 Arm 虚拟化的兴趣也在增长,因为对 Arm 来说,支持虚拟化对它的生态建设起到很重要的作用。于是,ARM 虚拟化扩展就出现了,它的核心设计目标是构建 Arm hypervisor,且它可以运行未经修改的 Guest OS,而不增加显著的硬件复杂性,同时保持高水平的性能,而且也符合 Popek 和 Goldberg 的定理要求,关于这个定理,大家可以看下《一文读懂虚拟化原理》文章。
2.CPU 虚拟化
虚拟化扩展侧重于在虚拟化上下文中支持现有的 ISA,因此 Arm 没打算改变 ISA 单个指令语义,而不单独处理架构中限制虚拟化的指令。相反,虚拟化扩展引入了一种新的更高特权处理器执行的模式,这个模式在 ARMv7 架构首次引入时称为HYP(hypervisor)模式,在 ARMv8 架构以及之后称为EL2。
Arm 架构上的 CPU 模式如图 1 所示,包括 TrustZone(安全扩展)。TrustZone 将模式分为安全和非安全两个世界,这两个世界与 CPU 模式是正交的。而且在 EL3 也提供了一种特殊模式:Monitor mode(监控器模式),用于在安全和非安全世界之间切换。如果实现了 TrustZone,尽管 Arm CPU 在安全模式下启动,但 Arm 的引导加载程序(bootloaders)通常在早期阶段过渡到非安全世界。安全世界用于可信计算场景,如数字版权管理。不过需要注意的是,最新的 Arm 架构已经支持 secure 状态下的 EL2 Hypervisor 了。
图 1 Arm 处理器模式
不像以前的 Arm 架构支持多种 kernel 模式,ARMv8 只有一个 kernel 模式:EL1。图 1 中为支持虚拟机扩展而引入的名为 EL2 的新 CPU 特权级别(也成为异常级别,Exception Level, EL),它构建于现有的 user(EL0)和 kernel(EL1)级别上,是虚拟化拓展的中心。EL2 以 trap-and-emulate 机制来支持虚拟化,它是严格地比其它 CPU 模式(EL0 和 EL1)拥有更多特权的 CPU 模式。
为了支持虚拟机 VM,运行在 EL2 中的软件可以配置捕获(Trap)来自 EL0 或 EL1 的各种敏感指令和硬件中断去 EL2,这样系统使用起来更灵活了。比如,不支持资源复用的分区 hypervisor 可能不会捕获正常 hypervisor 会捕获的某些指令。允许 VM 支持使用 Arm 调试寄存器而不是对它们进行 Trap 在某些情况下是可取的。如果想要模拟与正在使用的 CPU 不同的 CPU,则有必要捕获对 CPU 标识符寄存器的访问,但没有必要捕获运行完整 hypervisor 所需的其它访问。
为了允许 VM 与物理机相同的接口进行交互,同时将它们与系统的其余部分隔离,并防止它们获得对硬件的完全访问权,hypervisor 在切换到 VM 之前要使能 EL2 中的虚拟化特性。然后 VM 将在 EL0 和 EL1 中正常执行,直到达到需要 hypervisor 干预的某些条件。此时,硬件进入 EL2,将控制权交接给 hypervisor,然后 hypervisor 可以管理硬件并提供跨 VM 所需的隔离。一旦系统 hypervisor 处理了该条件,CPU 就可以切换回 EL0 和 EL1,VM 就可以恢复执行了。当在 EL2 中禁用所有虚拟化特性时,在 EL1 和 EL0 中运行的软件就像在没有虚拟化扩展的系统上运行一样,在 EL1 中运行的软件具有对硬件的完全访问权限。
Arm 架构允许将每个 Trap 配置为直接路由给在 EL1 的 VM,而不是 EL2 的 hypervisor。例如,由 EL0 引起的系统调用或页表错误的 Trap 可以配置为直接路由到 EL1,以便由 Guest OS 处理它们,而不需要 hypervisor 的干预。它避免在每次系统调用或页表错误时转到 EL2,从而减少了虚拟化开销。此外,所有进入 EL2 的 Trap 都可以被禁用,单个非虚拟化 kernel 可以在 EL1 中运行并完全控制系统。与 EL1 相比,EL2 中可用的控制寄存器数量减少了。例如,EL2 只有一个页表基寄存器,而 EL1 可以使用两个。可以说,通过减少 EL2 中可用的控制寄存器的数量,因此也相应减少需要操作的状态数量,这样可以简化 hypervisor 的实现。
Arm 的 Virtualization Extensions 提供了以下特性:
- 处理器在任何时间点都只能处于一种 CPU 模式,即 EL0,EL1 或 EL2。从一种模式到另一种模式的实际转换是原子的,但是从一种模式到另一种模式时根据需要保存和恢复状态的过程不是原子的。
- CPU 模式可以通过执行 MRS 指令来确定,MRS 指令在任何模式下都可用。最开始的期望是无论有没有虚拟机,应用程序和操作系统仍然使用相同的 CPU 模式运行。但在这种假设下,EL2 执行本身不能被虚拟化,并且不支持递归虚拟机,因为嵌套的 hypervisor 可以确定它不是运行在 EL2。
- EL2 被设计用于虚拟化,但它只是一种更有特权的 CPU 模式,也可以用于其它目的。
- 每种模式由不同的页表定义,都有自己完整线性地址空间。EL2 有自己的 translation regime,它定义了给定模式中使用的寄存器和页表格式。然而,EL1/EL0 共享一个 translation regime,因此它们的地址空间和页表都可以在 EL1 和 EL0 中访问。因为每个 TLB 条目都会被标记上 translation regime,因此只有在当前活跃地址空间的 translation regime 匹配上 TLB 条目的标记,才能算作命中了。由于 TLB 条目标记,因此在 EL2 和其它 CPU 模式之间转换时不需要刷新 TLB 内容。
- Arm 有两种类型的中断:正常中断(IRQ)和快速中断(FIQ),以及对应处理器当前模式的两个中断标志。EL1 中的软件可以自由操作每种中断类型的中断标记而不会引发 Trap。EL2 可以配置每个中断类型要么直接传递到 EL1,要么 Trap 到 EL2。当中断直接传递给 EL1 时,由 EL1 直接配置的中断标志控制真正的物理中断。当中断 Trap 到 EL2 时,由 EL1 直接配置的中断标志控制虚拟中断。
3.内存虚拟化
Arm 引入了一组额外的页表来将 Guest physical address 转换成 Host physical address,并将此作为虚拟化支持的一部分。在运行 VM 时,使用 Arm 的硬件支持使用虚拟化物理内存,由 VM 管理的物理地址实际上是中间物理地址(Intermediate Physical Addresses, IPAs),也称为来宾物理地址(Guest physical addresses, gPAs),需要转换为主机物理地址(host physical addresses, hPAs)。因此,ARM 提供了第二组页表,stage-2 tables,用于将 guest 物理地址(gPAs)和 host 物理地址(hPAs)。EL2 可以完全禁用或启动 Stage-2 转换。Stage-2 页表使用 Arm 新的 LPAE(Large Physical Address Extension)页表格式,与 kernel 模式使用的页表有细微的区别。
图 2 为 Arm 中完整的地址转换机制。在 stage-1 阶段,从 virtual physical address 到 guest physical address 转换使用了三层页表;在 stage-2 阶段,从 guest physical address 到 host physical address 的转换使用了四层页表。可以使用 HCR(Hyp Configuration Register)中的一个域段来启用或禁用 stage-2 转换。Stage-2 第一层次(L1)页表的基寄存器由 Virtualization Translation Table Base Register(VTTBR)指定的,这两个寄存器只能从 EL2 配置。
图 2 Arm 上使用 LPAE 内存长格式描述符的 stage-1 核 stage-2 页表遍历过程。虚拟地址(VA)首先转换为 guest PA,最后转换为 host PA。
4.中断虚拟化
Arm 定义了通用中断控制器(Generic Interrupt Controller, GIC)架构。GIC 分配从设备到 CPU 的中断,CPU 查询 GIC 来发现中断的来源。在多核配置中,GIC 尤其重要,因为它用于生成处理器间从一个 CPU 内核到另一个 CPU 内核的中断(Inter-Processor Interrupts, IPIs)。GIC 分为两部分:distributor(分发器)和 CPU interfaces(CPU 接口)。系统中只有一个 distributor,但每个 CPU 核心都有一个 GIC CPU interface。CPU interface 和 distributor 都通过 Memory-Mapped interface(MMIO)访问 GIC。Distributor 用于配置 GIC,例如,设置中断的 CPU core affinity、完全启用或禁用系统上的中断、或将 IPI 发送到另一个 CPU 核心。CPU interface 用于确认(ACK)和发送中断结束(End-of-Interrupt, EOI)信号。例如,当一个 CPU 内核接收到一个中断时,它将在 GIC CPU 接口上读取一个特殊寄存器,该寄存器对中断进行 ACK 处理并返回中断的编号。在 CPU 将从 ACK 寄存器中检索到的值写入 CPU 接口的 EOI 寄存器之前,将不会再次向 CPU 发出中断。
可以将中断配置为 Trap 到 EL2 或 EL1。将所有中断 Trap 到 EL1 并让运行在 EL1 中的操作系统软件直接处理它们是有效地,但在虚拟机环境中不起作用,因为 hypervisor 失去了对硬件的控制。将所有中断 Trao 到 EL2 可确保 hypervisor 保留控制权,但需要在软件中模拟虚拟中断以向 VM 发送信号事件。由于中断和虚拟中断处理的每个步骤(如 ACK 和 EOI)都必须经过 hypervisor,因此管理起来很麻烦,而且成本很高。
GIC 以虚拟 GIC(vGIC)的形式提供硬件虚拟化支持,因此不需要在软件中由 hypervisor 模拟接收虚拟中断。vGIC 为每个 CPU 引入了一个 vGIC CPU 接口以及相应的 hypervisor 控制接口。虚拟机被配置为 vGIC CPU 接口,而不是 GIC CPU 接口。在 vGIC hypervisor 控制接口上,虚拟中断是通过写入特殊寄存器(list registers)来生成。vGIC CPU 接口将虚拟中断直接送到 VM 的 kernel 模式。因为 vGIC CPU 接口支持 ACK 和 EOI,这些操作不再需要 Trap 到 hypervisor 去用软件模拟了,从而减少了在 CPU 上接收中断的开销。例如,模拟的虚拟设备通常通过软件 API 向 hypervisor 发起虚拟中断,hypervisor 可以通过将模拟设备的虚拟中断号写入 list registers 来利用 vGIC。它使 vGIC 直接将虚拟机中断送到 kernel 模式,并允许 Guest OS 的 ACK 和 EOI 虚拟中断,而不会 Trap 到 hypervisor。请注意,distributor 仍然需要在软件中模拟,并且 VM 对 distributor 的所有访问必须 Trap 到 hypervisor。例如,当一个虚拟 CPU 向另一个虚拟 CPU 发送一个虚拟 IPI 时,这个将 Trap 到 hypervisor,hypervisor 在软件中模拟分发器的访问,并在接收 CPU 的 GIC hypervisor 控制接口上编程 list registers。
5.计时器虚拟化
Arm 定义了通用计时器架构(Generic Timer Architecture),其中包括对计时器虚拟化的支持。通用计时器提供一个计数器来实时测量时间的流逝,并为每个 CPU 提供一个计时器,它被编程为在一定时间后向 CPU 发起中断。Hypervisor 和 Guest OS 都可能使用计时器,但是为了提供隔离和保持控制,Guest OS 不能直接配置和操作 hypervisor 使用的计时器。来自 Guest OS 的这种计时器访问需要 Trap 到 EL2,对于某些工作负载来说,相对频繁的操作会带来额外的开销。Hypervisor 也可能希望虚拟化 VM 时间,因为 VM 可以直接访问计数器硬件,那就不符合虚拟化要求了。
Arm 通过引入一个新的计数器(虚拟计数器)和一个新的计时器(虚拟计时器),为计时器提供虚拟化支持。可以将 hypervisor 配置为使用物理计时器,而将虚拟机配置为使用虚拟计时器。然后,虚拟机可以访问、编程和取消虚拟计时器,而不会对 EL2 产生 Trap。Kernel 模式访问物理计时器和计数器是由 EL2 控制的,但是在 kernel 模式下运行的软件总是可以访问虚拟计时器和虚拟计数器。此外,EL2 配置一个偏移寄存器,它从物理计数器中减去,并在读取虚拟计数器时作为返回值。请注意,在使用通用计时器之前,读取计数器时内存映射操作,因此频繁操作读取的话,通常会 Trap 到 EL2 并产生额外的开销。
END
作者:谷公子
文章来源:专芯致志er
推荐阅读
更多 IC 设计干货请关注IC 设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。