看完之后你将学会:
知识储备需求:对虚拟化、虚拟机、hypervisor的作用的基本理解。熟悉异常等级模型和内存管理的地址变换。
下文的hypervisor泛指:用于创建、管理、调度虚拟机的软件。
虚拟化的特性:
type 2:寄生
宿主OS对硬件平台和资源具有全部的控制权,包括CPU和物理内存。如Virtual Box和VMware Workstation就寄生在宿主OS上。
type 1:独立
Hypervisor对硬件平台和资源拥有全部的控制权。
举个例子体会两者的区别:页面分配给虚拟机,而中断只有单个的vcpu能够收到。
注意:arm中的通用术语应为vPE,只是vcpu更广为人知
运行在EL2之上的软件可以访问并控制虚拟化功能:
非安全状态和安全状态的异常等级如下所示:
这个架构支持的特性:
stage 2 转换允许hypervisor控制虚拟机的内存视图,即hypervisor可以控制哪一块内存中的系统资源VM可以访问,以及在VM中的哪一块地址空间出现了这些资源,
能够控制 内存访问 对于隔离性和沙盒特性是很重要的。stage 2 转换用于确保VM只能访问到被分配的资源。
地址转换:VA ->IPA(Intermediate Physical Address)->PA
stage 2 和stage 1 的转换表格式很像,但是,部分属性的处理不同。并且,判断内存类型是正常还是设备的信息被编码到了表中,而不是通过MAIR_ELx 寄存器
作用:用于标记某个特定的TLB项属于哪一个VM。VMID使得不同的VM可以共享同一块TLB缓存。
储存位置:寄存器VTTBR_EL2,可以是8或16比特,由VTCR_EL2.vs比特位控制。
注意,EL2和EL3的地址转换不需要VMID标记,因为它们不需要stage 2转换。
ASID:
stage 1 和 stage 2映射都包含属性,例如存储类型,访问权限等。内存管理单元(MMU)会将两个阶段的属性整合成一个最终属性,整合的原则是选择更有限制的属性。
在上面的例子中,Device属性比起Normal属性更具限制性,因此最终结果是Device属性。同样的原理,如果你将顺序调换一下也不会改变最终的属性。
如果hypervisor希望改变默认的行为,可以通过改变如下的寄存器比特来实现:
与物理机器的物理地址空间类似,VM的IPA地址空间包含了内存与外围设备两种区域。如下图所示
VM使用外围设备区域来访问其看到的物理外围设备,这其中包含了直通设备和虚拟外围设备。虚拟设备完全由Hypervisor模拟,如下图所示
可以看到:
stage 1 faults和stage2 faults的区别:
ESR_ELx寄存器用于报告发生异常的相关信息。当loads或stores一个通用寄存器触发stage 2 fault时,相关异常信息由这些寄存器提供。这些信息包含了,访问的长度,访问的原地址或目的地址。Hypervisor可以以此来判断对虚拟外围设备访问的权限。
下图展示了一个 陷入(trapping) – 模拟(emulating) 的访问过程。
扩展stage 2映射以保护主设备的地址空间。个DMA控制器没有使用虚拟化,那它看起来应该如下图所示
在不采取扩展措施的情况下,操作系统运行在虚拟机中的场景如下
驱动则直接与DMA控制器交互,这会产生两个问题:
解决方法:这些主设备控制器也需要一个MMU,Armv8称之为SMMU(通常也称为IOMMU)。
Hypervisor负责配置SMMU,以使DMA控制器看到的物理地址空间与kenrel看到的物理地址空间相同。这样就能解决上述两个问题。
Armv8包含一些陷入控制来帮助实现 陷入(trapping) – 模拟(emulating)。
陷入 – 模拟的另一个用途是用来呈现虚拟寄存器的值。
陷入也可用作惰性上下文切换的一部分。举个例子,OS通常会在boot期间初始化MMU配置寄存器(TTBR_EL1, TCR_EL1 and MAIR_EL1),之后就不会对他们重新编程。hypervisor可以利用这个特性优化上下文切换,通过不保存这些寄存器。
但是OS也可能重新编程这些寄存器。为了避免引起麻烦,hypervisor可以设置HCR_EL2.TVM陷入。这个设置使得任何改写MMU相关寄存器的操作都会陷入到EL2异常。
陷入和异常路由的区别:
陷入 – 模拟 的开销是很大的。这种操作需要先陷入到EL2,然后由Hypervisor做相应模拟再返回客户操作系统。对于某些寄存器如 ID_AA64MMFR0_EL1,操作系统并不经常访问,陷入 – 模拟的开销还是可以接受的。但对于某些经常访问的寄存器以及性能敏感的代码,陷入太频繁会对系统性能造成很大影响。对于这些情况,我们需要尽可能地优化 陷入。
Hypervisor可能希望在访问上述两个寄存器时不要总是陷入。对这些寄存器,Armv8提供了与其对应的不需要陷入的版本。Hypervisor可以在进入VM 时先配置好这些寄存器的值。当VM中读到 MIDR_EL1 / MPIDR_EL1时会自动返回VPIDR_EL2 / VMPIDR_EL2的值而不发生陷入。
注意:VPIDR_EL2 / VMPIDR_EL2 在硬件reset后没有初始化的值,它们必须由软件启动代码初始化一个合理的值。
需要支持两种处理中断的方法:
Armv8提供了vIRQs, vFIQs, 和vSErrors来支持虚拟中断。这些中断的行为和物理中断(IRQs, FIQs, 和 SErrors)类似,只不过只有当系统运行在EL0/1是才会收到,运行在EL2/3是收不到虚拟中断的。
为了发送虚拟中断到EL0/1, Hypervisor需要设置 HCR_EL2中相应的中断路由比特位。例如,开启vIRQ,你需要设置 HCR_EL2.IMO, 这意味着物理IRQ中断将被发送到EL2,同时虚拟中断将被发送到EL1。
有两种方式产生虚拟中断
第一种机制: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的次数。
具体步骤如下:
中断屏蔽比特位PSTATE.I, PSTATE.F, PSTATE.A分别对应IRQs, FIQs和SErrors。在虚拟化环境中,这些比特位的工作方式有些许不同。
例如,对于IRQs,设置HCR_EL2.IMO意味着
这同时也改变了PSTATE.I 屏蔽的含义, 当运行在EL0/EL1是,如果 HCR_E2.IMO==1, PSTATE.I针对的是虚拟的vIRQs而非物理的pIRQs。
Arm体系结构中,每个处理器上都有一组通用定时器。通用定时器由一组比较器组成,用来与系统计数器比较。当比较器的值小于等于系统计数器时便会产生时钟中断。在下图中,我们可以看到系统中通用时钟由橘色框部分组成。
下图展示了虚拟化系统中运行两个vCPU的时序。
提问:如果在T=0的时候设置vCPU0的比较器3ms后产生一个中断,那么
实际上,Arm体系结构同时支持上述两种设置,这取决于你使用何种虚拟化方案。
运行在vCPU上的软件可以访问如下两种时钟
EL1物理时钟会与系统计数器模块直接比较。
EL1虚拟时钟与虚拟计数器比较。虚拟计数器是在物理计数器的基础上减去一个偏移。Hypervisor负责为当前调度运行的vCPU指定对应的偏移寄存器。这种方式使得虚拟时间只会覆盖vCPU实际运行的那部分时间。
下图展示了虚拟时间运作的原理:
下图显示了一个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上。
VHE由系统寄存器 HCR_EL2中的两个比特位控制
下面的表格总结了典型的设置:
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的主代码
一个典型的配置如下图:
下图展示了,引入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)为后者。
运行在EL2的内核仍然会尝试访问*_EL1的寄存器。为了运行无需修改的内核,我们需要将EL1的寄存器重定向到EL2。当设置E2H后,这一切就会由硬件实现。
但是,重定向又会带来一个新的问题,那就是Hypervisor可能在某些情况下,例如当执行任务切换时, 访问真正EL1的寄存器。为了解决这个问题,Arm架构引入了一种新的别名机制,以_EL12或_EL02结尾。如下例,就可以在ECH==1的EL2访问TTBR0_EL1。
通常系统寄存器 HCR_EL2.IMO/FMO/AMO的这几个比特位可以用来控制物理异常被路由至EL1或EL2。当运行在EL0且TGE==1时,HCR_EL2路由比特将会被忽略,所有物理异常(除了那些由SCR_EL3控制的会被路由至EL3)全部路由到EL2。这是因为Host OS里运行的应用是Host OS的一部分,而Host OS运行在EL2。
Hypervisor可以运行在VM中,这称之为嵌套虚拟化。
在Armv8.3-A之前,Guest Hypervisor可以运行在EL0。但这种设计需要大量软件模拟,不仅软件开发困难,性能也很差。Armv8.3-A增加了一些新的特性,可以让Guest Hypervisor运行在EL1。而Armv8.4-A引入的一些新特性,使得这一过程更有效率,虽然仍然需要Host Hypervisor参与做一些额外的工作。
当Guest Hypervisor运行在EL1,并访问虚拟化控制接口时,HCR_EL2中新的控制比特位可以使这些操作陷入到Host Hypervisor(EL2)以便模拟。
Armv8.3-A添加了NV和NV1控制比特。
这就使得Guest Hypervisor可以运行在EL1,同时由运行在EL2的Host Hypervisor来模拟这些操作。NV还会导致EL1运行ERET陷入到EL2。
下图展示了Guest Hypervisor如何创建并启动虚拟机
**该流程的问题:*按上述的方法, 在Guest Hypervisor访问任何一个_EL2寄存器时都会发生陷入。切换操作如 任务切换,vCPU切换,VMs切换都会访问大量寄存器,每次陷入都会导致异常的进入与返回,从而带来严重的 陷入 – 模拟性能问题。
Armv8.4-A提供了一个更好的方案,当NV2被设置时,从EL1访问*_EL2寄存器将会被重定向到一块内存区域。Guest Hypervisor可以多次读写这块寄存器区域而不发生陷入。只有当最后运行ERET时,才会陷入到EL2。而后,Host Hypervisor可以从该内存区域中提取相关配置并代Guest Hypervisor执行相关操作。
Arm体系结构定义了安全世界和非安全世界两个物理地址空间。在非安全状态下,stage 1转换的的输出总是非安全的,因此只需要一个IPA空间来给stage 2使用。然而,对于安全世界,stage 1的输出可能是安全的也能是非安全的。Stage 1转换表中的NS比特位控制使用安全地址还是非安全地址。这意味着在安全世界,需要两个IPA地址空间。
与stage 1表不同,stage 2转换表中没有NS比特位。因为对于一个特定的IPA空间,要么全都是安全地址,要么全都是非安全的,因此只需要由一个寄存器比特位来确定IPA空间。通常来说,非安全地址经过stage 2转换仍然是非安全地址,安全地址经过stage 2转换仍然是安全地址。
虚拟化的损耗主要在于虚拟机和Hypervisor切换需要保存和恢复寄存器。Armv8系统中,最少需要对如下寄存器做处理
使用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