本文主要是介绍ARM 虚拟化介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
0.目录
文章目录
- 0.目录
- 1.概述
-
- 1.1 Before you begin
- 2.虚拟化介绍
-
- 2.1 虚拟化为什么重要
- 2.2 hypervisors的两种类型
- 2.3 全虚拟化和半虚拟化
- 2.4 虚拟机和虚拟CPUs
- 3.AArch64中的虚拟化
- 4.stage 2 转换
-
- 4.1 什么是stage 2 转换
- 4.2 VMIDs
- 4.3 VMID vs ASID
- 4.4 属性整合和覆盖
- 4.5模拟MMIO(Memory-mapped Input/Output)
- 4.6 系统内存管理单元System Memory Management Units (SMMUs)
- 5.指令的陷入和模拟
-
- 5.1 显示寄存器的虚拟值
- 5.2 MIDR和MPIDR
- 6.异常虚拟化
-
- 6.1 开启虚拟中断
- 6.2 产生虚拟中断
- 6.3 中断转发给vCPU的例子
- 6.4 中断屏蔽和虚拟中断
- 7.通用定时器虚拟化
- 8.虚拟化主机扩展(Virtualization Host Extensions, VHE)
-
- 8.1 将主机操作系统运行在EL2
- 8.2 虚拟地址空间
-
- 8.3 重定向寄存器访问
- 8.4 异常
- 9. 嵌套虚拟化
-
- 9.1 Guest Hypervisor访问虚拟化控制接口
- 10.安全世界虚拟化
-
- 10.1 安全EL2与两个IPA空间
- 11.虚拟化的损耗
- _参考_
1.概述
看完之后你将学会:
- 两种类型的hypervisor,以及他们如何映射到Arm异常等级。
- 解释陷入的操作以及他们怎么用来进行各种模拟操作
- 能够列出hypervisor能够产生的虚拟异常以及产生这些异常的机制
1.1 Before you begin
知识储备需求:对虚拟化、虚拟机、hypervisor的作用的基本理解。熟悉异常等级模型和内存管理的地址变换。
2.虚拟化介绍
下文的hypervisor泛指:用于创建、管理、调度虚拟机的软件。
2.1 虚拟化为什么重要
虚拟化的特性:
- 隔离性
- 高可用性:虚拟机迁移
- 负载均衡:
- 沙箱
2.2 hypervisors的两种类型
type 2:寄生
宿主OS对硬件平台和资源具有全部的控制权,包括CPU和物理内存。如Virtual Box和VMware Workstation就寄生在宿主OS上。
type 1:独立
Hypervisor对硬件平台和资源拥有全部的控制权。
2.3 全虚拟化和半虚拟化
2.4 虚拟机和虚拟CPUs
举个例子体会两者的区别:页面分配给虚拟机,而中断只有单个的vcpu能够收到。
注意:arm中的通用术语应为vPE,只是vcpu更广为人知
3.AArch64中的虚拟化
运行在EL2之上的软件可以访问并控制虚拟化功能:
- Stage 2 转换
- EL1/0指令和寄存器访问
- 产生虚拟化异常
非安全状态和安全状态的异常等级如下所示:
这个架构支持的特性:
- 安全虚拟化
- 寄生型hypervisor的支持
- 嵌套虚拟化
4.stage 2 转换
4.1 什么是stage 2 转换
stage 2 转换允许hypervisor控制虚拟机的内存视图,即hypervisor可以控制哪一块内存中的系统资源VM可以访问,以及在VM中的哪一块地址空间出现了这些资源,
能够控制 内存访问 对于隔离性和沙盒特性是很重要的。stage 2 转换用于确保VM只能访问到被分配的资源。
地址转换:VA ->IPA(Intermediate Physical Address)->PA
stage 2 和stage 1 的转换表格式很像,但是,部分属性的处理不同。并且,判断内存类型是正常还是设备的信息被编码到了表中,而不是通过MAIR_ELx 寄存器
4.2 VMIDs
作用:用于标记某个特定的TLB项属于哪一个VM。VMID使得不同的VM可以共享同一块TLB缓存。
储存位置:寄存器VTTBR_EL2,可以是8或16比特,由VTCR_EL2.vs比特位控制。
注意,EL2和EL3的地址转换不需要VMID标记,因为它们不需要stage 2转换。
4.3 VMID vs ASID
ASID:
- 每个应用都被操作系统分配有一个ASID,所有属于同一个应用的TLB项都有相同的ASID。
- 每一个VM有它自己的ASID空间。
- VMID+ASID确定属于哪一个应用
4.4 属性整合和覆盖
stage 1 和 stage 2映射都包含属性,例如存储类型,访问权限等。内存管理单元(MMU)会将两个阶段的属性整合成一个最终属性,整合的原则是选择更有限制的属性。
在上面的例子中,Device属性比起Normal属性更具限制性,因此最终结果是Device属性。同样的原理,如果你将顺序调换一下也不会改变最终的属性。
如果hypervisor希望改变默认的行为,可以通过改变如下的寄存器比特来实现:
- HCR_EL2.CD: 控制所有stage 1属性为Non-cacheable。
- HCR_EL2.DC:强制所有stage 1属性为Normal,Write-Back Cacheable。
- HCR_EL2.FWB (Armv8.4-A引入):使用stage 2属性覆盖stage 1属性,而不是使用默认的限制性整合原则。
4.5模拟MMIO(Memory-mapped Input/Output)
与物理机器的物理地址空间类似,VM的IPA地址空间包含了内存与外围设备两种区域。如下图所示
VM使用外围设备区域来访问其看到的物理外围设备,这其中包含了直通设备和虚拟外围设备。虚拟设备完全由Hypervisor模拟,如下图所示
可以看到:
- 直通设备的stage 2 转换对应的就是相应的PA
- 虚拟设备的stage2的相关的表项设置为fault,从而进入相应的异常处理程序由Hypervisor进行模拟
- 为了模拟一个外围设备,Hypervisor需要知道哪一个外围设备被访问,外围设备的哪一个寄存器被访问,是读访问还是写访问,访问长度是多少,以及使用哪些寄存器来传送数据。
stage 1 faults和stage2 faults的区别:
- 当处理stage 1 faults时,FAR_ELx寄存器包含了触发异常的虚拟地址。但虚拟地址不是给Hypervisor用的,Hypervisor通常不会知道客户操作系统如何配置虚拟地址空间的映射。
- 对于stage 2 faults,有一个专门的寄存器HPFAR_EL2,该寄存器会报告发生错误的IPA地址。IPA地址空间由Hypervisor控制,因此可用利用此寄存器里的信息来进行必要的模拟。
ESR_ELx寄存器用于报告发生异常的相关信息。当loads或stores一个通用寄存器触发stage 2 fault时,相关异常信息由这些寄存器提供。这些信息包含了,访问的长度,访问的原地址或目的地址。Hypervisor可以以此来判断对虚拟外围设备访问的权限。
下图展示了一个 陷入(trapping) – 模拟(emulating) 的访问过程。
- VM里的软件尝试访问虚拟外围设备,这个例子当中是虚拟UART的接收FIFO。
- 该访问被stage 2转换block住,导致一个abort异常被路由到EL2。
- 异常处理程序查询ESR_EL2关于异常的信息,如访问长度,目的寄存器,是load还是store操作。
- 异常处理程序查询HPFAR_EL2,取得发生abort的IPA地址。
- Hypervisor通过ESR_EL2和HPFAR_EL2里的相关信息对相关虚拟外围设备作模拟,模拟完成后通过ERET指令返回vCPU,并从发生异常的下一条指令继续执行。
4.6 系统内存管理单元System Memory Management Units (SMMUs)
扩展stage 2映射以保护主设备的地址空间。个DMA控制器没有使用虚拟化,那它看起来应该如下图所示
在不采取扩展措施的情况下,操作系统运行在虚拟机中的场景如下
驱动则直接与DMA控制器交互,这会产生两个问题:
- 隔离:DMA控制器访问在虚拟机之间没有了隔离,这破坏了虚拟机的沙箱功能。
- 地址空间: 利用两级映射转换,使内核看到的PAs实际上是IPAs。但DMA控制器看到的仍然是PAs。因此DMA控制器和内核看到的是不同的地址空间,为了解决这个问题,每当VM与DMA控制器交互时就需要陷入到Hypervisor中做必要的转换。这种处理方式是极其没有效率的,且容易出错。
解决方法:这些主设备控制器也需要一个MMU,Armv8称之为SMMU(通常也称为IOMMU)。
Hypervisor负责配置SMMU,以使DMA控制器看到的物理地址空间与kenrel看到的物理地址空间相同。这样就能解决上述两个问题。
5.指令的陷入和模拟
Armv8包含一些陷入控制来帮助实现 陷入(trapping) – 模拟(emulating)。
- 举个例子,执行等待中断指令WFI通过会使CPU进入低功耗状态。然而,当配置HCR_EL2.TWI==1时,如果在EL0/EL1执行WFI则会导致EL2的异常。
- 对于 WFI的例子里, 操作系统通过在一个idle loop里执行 WFI指令,但虚拟机中的操作系统执行该指令时,会陷入到Hypervisor里模拟,这时Hypervisor通常会调度另一个vCPU执行。
5.1 显示寄存器的虚拟值
陷入 – 模拟的另一个用途是用来呈现虚拟寄存器的值。
- 例如寄存器ID_AA64MMFR0_EL1是用来报告处理器内存相关特性的,操作系统可能会读取该寄存器来决定在内核中开启或关闭某些特性。Hypervisor可能会给VM呈现一个与实际物理寄存器不同的值。如下图中的例子。
- 陷入,ESR_EL2保存相关信息→hypervisor进行处理→ERET返回
陷入也可用作惰性上下文切换的一部分。举个例子,OS通常会在boot期间初始化MMU配置寄存器(TTBR_EL1, TCR_EL1 and MAIR_EL1),之后就不会对他们重新编程。hypervisor可以利用这个特性优化上下文切换,通过不保存这些寄存器。
但是OS也可能重新编程这些寄存器。为了避免引起麻烦,hypervisor可以设置HCR_EL2.TVM陷入。这个设置使得任何改写MMU相关寄存器的操作都会陷入到EL2异常。
陷入和异常路由的区别:
- 陷入:给定操作之后,会陷入异常
- 异常路由:路由是指一旦异常产生,它就会被带到的异常级别
5.2 MIDR和MPIDR
陷入 – 模拟 的开销是很大的。这种操作需要先陷入到EL2,然后由Hypervisor做相应模拟再返回客户操作系统。对于某些寄存器如 ID_AA64MMFR0_EL1,操作系统并不经常访问,陷入 – 模拟的开销还是可以接受的。但对于某些经常访问的寄存器以及性能敏感的代码,陷入太频繁会对系统性能造成很大影响。对于这些情况,我们需要尽可能地优化 陷入。
- MIDR_EL1: 存有处理器类型信息
- MPIDR_EL1:亲和性配置
Hypervisor可能希望在访问上述两个寄存器时不要总是陷入。对这些寄存器,Armv8提供了与其对应的不需要陷入的版本。Hypervisor可以在进入VM 时先配置好这些寄存器的值。当VM中读到 MIDR_EL1 / MPIDR_EL1时会自动返回VPIDR_EL2 / VMPIDR_EL2的值而不发生陷入。
- VPIDR_EL2:读取 MIDR_EL1返回 VPIDR_EL2的值避免陷入
- VMPIDR_EL2:读取 MPIDR_EL1返回 VMPIDR_EL2的值避免陷入
注意:VPIDR_EL2 / VMPIDR_EL2 在硬件reset后没有初始化的值,它们必须由软件启动代码初始化一个合理的值。
6.异常虚拟化
需要支持两种处理中断的方法:
- 在EL2中直接处理中断
- 将收到的中断转发给相应VM的vCPU
Armv8提供了vIRQs, vFIQs, 和vSErrors来支持虚拟中断。这些中断的行为和物理中断(IRQs, FIQs, 和 SErrors)类似,只不过只有当系统运行在EL0/1是才会收到,运行在EL2/3是收不到虚拟中断的。
6.1 开启虚拟中断
为了发送虚拟中断到EL0/1, Hypervisor需要设置 HCR_EL2中相应的中断路由比特位。例如,开启vIRQ,你需要设置 HCR_EL2.IMO, 这意味着物理IRQ中断将被发送到EL2,同时虚拟中断将被发送到EL1。
6.2 产生虚拟中断
有两种方式产生虚拟中断
- 配置HCR_EL2,由内部CPU核产生
- 使用GICv2及以上版本的外部中断控制器
第一种机制:HCR_EL2中有如下的控制比特位:
-
VI: 配置vIRQ
-
VF: 配置vFIQ
-
VSE: 配置vSError
设置上述比特位等同于中断控制器向vCPU发送中断信号。和常规物理中断一样,虚拟中断受PSTATE控制。这种机制简单易用,但有个明显的缺点,需要由Hypervisor来模拟中断控制器的相关操作,一系列的 陷入 – 模拟将带来性能上的开销。
第二种机制:使用Arm的通用中断控制器(Generic Interrupt Controller, GIC)来产生虚拟中断。从GICv2版本开始,GIC可以通过物理CPU interface 和 虚拟CPU interface发送物理中断和虚拟中断。
这两个CPU interface是等同的,区别是一个发送物理中断信号,另一个发送虚拟中断信号。Hypervisor可以将虚拟CPU interface映射给VM,以便VM可以直接和GIC通信。这种方式的好处是Hypervisor只需建立映射,不需要做任何模拟,从而提升了性能。及减少了陷入EL2的次数。
6.3 中断转发给vCPU的例子
具体步骤如下:
- 物理外围设备发送中断信号给GIC
- GIC产生物理中断异常,可能是IRQ或FIQ。由于配置了HCR_EL2.IMO/FMO,这些异常会被路由到EL2。Hyperviosr发现该设备已被分配给了某个VM,于是检查需要将该中断信号转发给哪个vCPU。
- Hypervisor配置了GIC将该物理中断以虚拟中断的形式转给某个vCPU。GIC于是发送vIRQ/vFIQ信号,如果此时还运行在EL2,这些信号会被忽略。
- Hypervisor将控制权返还给vCPU。
- 处理器运行在EL0或EL1,来自GIC的虚拟中断被接收(受PSTATE控制)。
6.4 中断屏蔽和虚拟中断
中断屏蔽比特位PSTATE.I, PSTATE.F, PSTATE.A分别对应IRQs, FIQs和SErrors。在虚拟化环境中,这些比特位的工作方式有些许不同。
例如,对于IRQs,设置HCR_EL2.IMO意味着
- 物理IRQ路由至EL2
- 对EL0/EL1开启vIRQs
这同时也改变了PSTATE.I 屏蔽的含义, 当运行在EL0/EL1是,如果 HCR_E2.IMO==1, PSTATE.I针对的是虚拟的vIRQs而非物理的pIRQs。
7.通用定时器虚拟化
Arm体系结构中,每个处理器上都有一组通用定时器。通用定时器由一组比较器组成,用来与系统计数器比较。当比较器的值小于等于系统计数器时便会产生时钟中断。在下图中,我们可以看到系统中通用时钟由橘色框部分组成。
下图展示了虚拟化系统中运行两个vCPU的时序。
提问:如果在T=0的时候设置vCPU0的比较器3ms后产生一个中断,那么
- a)是vCPU0的2ms产生中断?
- b)还是 vCPU0虚拟时间3ms的那个点?
实际上,Arm体系结构同时支持上述两种设置,这取决于你使用何种虚拟化方案。
运行在vCPU上的软件可以访问如下两种时钟
- EL1物理时钟
- EL1虚拟时钟
EL1物理时钟会与系统计数器模块直接比较。
EL1虚拟时钟与虚拟计数器比较。虚拟计数器是在物理计数器的基础上减去一个偏移。Hypervisor负责为当前调度运行的vCPU指定对应的偏移寄存器。这种方式使得虚拟时间只会覆盖vCPU实际运行的那部分时间。
下图展示了虚拟时间运作的原理:
8.虚拟化主机扩展(Virtualization Host Extensions, VHE)
下图显示了一个Type 1的虚拟化系统的软件栈和异常级别的对应关系,Hypervisor部分运行在EL2,VMs运行在EL0/1。
下图是Type 2型的系统:
宿主OS的内核部分运行在EL1,Hypervisor运行在EL2。VHE之前的Hypervisor通常需要设计成high-visor和low-visor两部分,前者运行在EL1,后者运行在EL2。分层设计在系统运行时会造成很多不必要的上下文切换,带来不少设计上的复杂性和性能开销。为了解决这个问题,虚拟化主机扩展 (Virtualization Host Extensions, VHE)应运而生。该特性由Armv8.1-A引入,可以让寄主操作系统的内核部分直接运行在EL2上。
8.1 将主机操作系统运行在EL2
VHE由系统寄存器 HCR_EL2中的两个比特位控制
- E2H:VHE使能位
- TGE:当VHE使能时,控制EL0是Guest还是Host
下面的表格总结了典型的设置:
Running in | E2H | TGE |
---|---|---|
Guest kernel (EL1) | 1 | 0 |
Guest application (EL0) | 1 | 0 |
Host kernel (EL2) | 1 | 1* |
Host application (EL0) | 1 | 1 |
***** 当发生异常从VM退出到Hypervisor时,TGE将会初始化为0,软件需要先设置这一比特,再继续运行host kernel的主代码
一个典型的配置如下图:
8.2 虚拟地址空间
下图展示了,引入VHE前的EL0/1的虚拟地址空间:
在内存管理中提到过,EL0/1有两块区域。通常,高地址是内核空间,低地址是用户空间。但是EL2只有一个低地址区域。这个区别是因为,通常情况下hypervisor不运行应用,所以EL2不需要区分用户空间和内核空间。
注意:ARM架构并没有规定内核空间必须在高地址,用户空间必须在低地址,只是习惯使然。
EL0/1虚拟地址空间支持地址空间标识Address Space Identifiers (ASID),而EL2不支持。
为了使得宿主OS在EL2效率更高,我们需要地址空间划分以及ASID支持。
设置HCR_EL2.E2H来解决。如下图所示:
当运行在EL0时,HCR_EL2.TGE控制使用EL1还是EL2空间,当应用运行在Guest OS (TGE==0)为前者,运行在Host OS(TGE==1)为后者。
8.3 重定向寄存器访问
运行在EL2的内核仍然会尝试访问*_EL1的寄存器。为了运行无需修改的内核,我们需要将EL1的寄存器重定向到EL2。当设置E2H后,这一切就会由硬件实现。
但是,重定向又会带来一个新的问题,那就是Hypervisor可能在某些情况下,例如当执行任务切换时, 访问真正EL1的寄存器。为了解决这个问题,Arm架构引入了一种新的别名机制,以_EL12或_EL02结尾。如下例,就可以在ECH==1的EL2访问TTBR0_EL1。
8.4 异常
通常系统寄存器 HCR_EL2.IMO/FMO/AMO的这几个比特位可以用来控制物理异常被路由至EL1或EL2。当运行在EL0且TGE==1时,HCR_EL2路由比特将会被忽略,所有物理异常(除了那些由SCR_EL3控制的会被路由至EL3)全部路由到EL2。这是因为Host OS里运行的应用是Host OS的一部分,而Host OS运行在EL2。
9. 嵌套虚拟化
Hypervisor可以运行在VM中,这称之为嵌套虚拟化。
在Armv8.3-A之前,Guest Hypervisor可以运行在EL0。但这种设计需要大量软件模拟,不仅软件开发困难,性能也很差。Armv8.3-A增加了一些新的特性,可以让Guest Hypervisor运行在EL1。而Armv8.4-A引入的一些新特性,使得这一过程更有效率,虽然仍然需要Host Hypervisor参与做一些额外的工作。
9.1 Guest Hypervisor访问虚拟化控制接口
当Guest Hypervisor运行在EL1,并访问虚拟化控制接口时,HCR_EL2中新的控制比特位可以使这些操作陷入到Host Hypervisor(EL2)以便模拟。
- HCR_EL2.NV:开启硬件辅助嵌套虚拟化
- HCR_EL2.NV1:开启额外需要陷入的操作
- HCR_EL2.NV2:开启重定向到内存
- VNCR_EL2:当NV2==1时,指向一个内存中的结构体
Armv8.3-A添加了NV和NV1控制比特。
- 在此之前,从EL1访问*_EL2寄存器时的行为是未定义的,通常是会产生一个EL1的异常。
- 而控制比特NV和NV1使得这种访问可以被陷入到EL2。
这就使得Guest Hypervisor可以运行在EL1,同时由运行在EL2的Host Hypervisor来模拟这些操作。NV还会导致EL1运行ERET陷入到EL2。
下图展示了Guest Hypervisor如何创建并启动虚拟机
- 从EL1访问*_EL2寄存器将导致Guest Hypervisor陷入到EL2。Host Hypervisor记录Guest Hypervisor创建的相关配置。
- Guest Hypervisor尝试进入其创建的虚拟机,此时ERET指令会陷入到EL2。
- Host Hypervisor根据Guest Hypervisor的配置,设置相关寄存器以便启动VM,清理掉NV比特位,最后进入Guest Hypervisor创建的Guest运行。
**该流程的问题:*按上述的方法, 在Guest Hypervisor访问任何一个_EL2寄存器时都会发生陷入。切换操作如 任务切换,vCPU切换,VMs切换都会访问大量寄存器,每次陷入都会导致异常的进入与返回,从而带来严重的 陷入 – 模拟性能问题。
Armv8.4-A提供了一个更好的方案,当NV2被设置时,从EL1访问*_EL2寄存器将会被重定向到一块内存区域。Guest Hypervisor可以多次读写这块寄存器区域而不发生陷入。只有当最后运行ERET时,才会陷入到EL2。而后,Host Hypervisor可以从该内存区域中提取相关配置并代Guest Hypervisor执行相关操作。
- 从EL1访问*_EL2寄存器将会被重定向到一块内存区域,该内存区域的地址由Host Hypervisor在 VNCR_EL2中指定。
- Guest Hypervisor尝试进入其创建的虚拟机,此时ERET指令会陷入到EL2
- Host Hypervisor从内存中提取配置信息,设置相关寄存器,以便启动VM,清理掉NV比特位,最后进入Guest Hypervisor创建的Guest运行。
10.安全世界虚拟化
10.1 安全EL2与两个IPA空间
Arm体系结构定义了安全世界和非安全世界两个物理地址空间。在非安全状态下,stage 1转换的的输出总是非安全的,因此只需要一个IPA空间来给stage 2使用。然而,对于安全世界,stage 1的输出可能是安全的也能是非安全的。Stage 1转换表中的NS比特位控制使用安全地址还是非安全地址。这意味着在安全世界,需要两个IPA地址空间。
与stage 1表不同,stage 2转换表中没有NS比特位。因为对于一个特定的IPA空间,要么全都是安全地址,要么全都是非安全的,因此只需要由一个寄存器比特位来确定IPA空间。通常来说,非安全地址经过stage 2转换仍然是非安全地址,安全地址经过stage 2转换仍然是安全地址。
11.虚拟化的损耗
虚拟化的损耗主要在于虚拟机和Hypervisor切换需要保存和恢复寄存器。Armv8系统中,最少需要对如下寄存器做处理
- 31 x 64-bit通用寄存器(x0…x30)
- 32 x 128-bit浮点/SIMD寄存器(V0…V31)
- 两个栈寄存器(SP_EL0, SP_EL1)
使用LDP和STP指令,Hypervisor需要运行33条指令来存储和恢复这些寄存器。虚拟化最终的损耗不仅取决于硬件还取决于Hypervisor的设计。
参考
Armv8-A virtualization
https://developer.arm.com/architectures/learn-the-architecture/aarch64-virtualization
Armv8架构虚拟化介绍(对上面的翻译)
https://calinyara.github.io/technology/2019/11/03/armv8-virtualization.html
这篇关于ARM 虚拟化介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!