曹旭辉 发表于 2024-7-17 16:58:55

【万字长文】学习Armv8架构虚拟化一篇就够了

Perface

本文描述了Armv8-A AArch64的虚拟化支持。包括stage 2页表转换,虚拟异常,以及陷阱。
本文介绍了一些基础的硬件辅助虚拟化理论以及一些Hypervisor如何利用这些虚拟化特性的例子。
文本不会讲述某一具体的Hypervisor软件是如何工作的以及如何开发一款Hypervisor软件(对具体实现感爱好,请参考aVisor: 基于ARM架构的Hypervisor及利用系统实现)。
通过阅读本文,你可以学到两种范例的Hypervisor以及它们是如何映射到Arm的异常级别。
你将能解释陷阱是如何工作的以及其是如何被用来进行各种模拟利用。你将能描述Hypervisor可以产生什么虚拟异常以及产生这些虚拟异常的机制。明确本文内容需要一定基础,本文假定你熟悉ARMv8体系布局的异常模型和内存管理。
虚拟化简介

这里我们将介绍一些基础的Hypervisor和虚拟化的理论知识。如果你已经有一定的基础或是已经熟悉了这些概念,可以跳过这部分内容。
我们用Hypervisor这个词来定义一种负责创建,管理以及调度虚拟机(Virtual Machines, VMs)的软件。
虚拟化为什么重要?

虚拟化是一种在现代云计算和企业基础架构中广泛使用的技术。开发人员用虚拟机在一个硬件平台上运行多个差别的利用系统来开发和测试软件,以避免对主计算环境造成可能的粉碎。
虚拟化技术在服务器上非常盛行,大多数面向服务器的处置惩罚器都需要支持虚拟化功能,这是因为虚拟化能给数据中心服务器带来如下一些需要的特性:


[*] 隔离:利用虚拟化可以对同一个物理核上运行的虚拟机进行隔离。这使得相互间不可信的的计算环境可以共享同一套硬件环境。例如,两个竞争对手可以共享同一个物理呆板而又不能访问对方的数据。
[*] 高可用性: 虚拟化可以在差别的物理呆板之间无缝透明地迁移负载。这个技术广泛用于将负载从出错的硬件平台迁移至其他可用平台,以便维护和更换出错的硬件而不影响服务。
[*] 负载均衡: 为了低落数据中心硬件和功耗本钱,需**要尽可能充分地利用硬件平台资源。**将负载均衡地迁移到差别地物理机上,有利用充分利用物理机资源,低落功耗,同时为租户提供最佳性能。
[*] 沙箱:虚拟机可以作为一个沙箱来为运行在其中的应用屏蔽其他软件的干扰,大概避免其干扰其他软件。例如在虚拟机中运行特定软件,可以避免该软件的bug或病毒导致物理呆板上的其他软件损坏。
Hypervisor的两种范例

Hypervisor通常被分成两种范例,独立范例Type 1和寄生范例 Type 2。
Type 2

我们先看看Type 2范例的Hypervisor。 对于Type 2范例的Hypervisor,其寄生的宿主利用系统拥有对硬件平台和资源(包括CPU和物理内存…)的全部控制权。
下图展示了Type 2范例的Hypervisor。
https://img-blog.csdnimg.cn/img_convert/a683c3aa368d5fe1b3c26979f35313b8.png
宿主利用系统,指的是直接运行在硬件平台上并为Type 2范例的Hypervisor提供运行环境的利用系统。
这类Hypervisor可以充分利用宿主利用系统对物理硬件的管理功能,而Hypervisor只需提供虚拟化相关功能即可。不知你是否使用过Virtual Box或是VMware Workstation, 这类软件就是Type 2范例的Hypervisor。
Type 1

接下来,看看独立范例的Type 1 Hypervisor, 如图2。 这类Hypervisor没有宿主利用系统。其直接运行在物理硬件之上,直接受理各种物理资源,同时管理并运行客户机利用系统。
https://img-blog.csdnimg.cn/img_convert/163a661148c4ae27aad3c0a264276214.png
在开源社区常见的Hypervisor, Xen (Type 1) 和 KVM (Type 2)就分属这两种差别的范例。其他开源的或知识产权的Hypervisor,可参见 WiKi。
全虚拟化和半虚拟化

关于虚拟机,经典定义是:虚拟机是一个独立的隔离的计算环境,这种计算环境让使用者看起来就像在使用真实的物理呆板一样。
尽管我们可以在基于ARM的硬件平台上模拟真实硬件,但这通常不是最有效的做法,因此我们常常不这么做。
例如,模拟一个真实的以太网装备是非常慢的,这是因为对任何一个模拟寄存器的访问都会陷入到Hypervisor当中进行模拟。
比起直接访问物理寄存器来说,这种利用的代价要昂贵得多。一个替换方案是修改客户利用系统,使之意识到自身运行在虚拟机当中,通过在Hypervisor中模拟一个虚拟装备来给客户机使用。以此来变更更好得I/O性能。
严酷来说,全虚拟化需要完全模拟真实硬件,性能上会比较差。开源项目Xen推进了半虚拟化,通过修改客户机利用系统的焦点部分使其更适合在虚拟环境中运行,以此来进步性能。
另一个使用半虚拟化的原因是早期的体系布局并不是为虚拟化而设计的,存在虚拟化漏洞。因为虚拟化要求所有敏感指令或访问敏感资源的指令都能被截获模拟。
对于存在虚拟化漏洞的体系布局,则需要通过半虚拟化的方案来填补漏洞。而今,大多数体系机构都支持硬件辅助虚拟化,包括Arm。这使得利用系统的焦点部分无需修改也能获得较好得性能。
只有少数存储和网络相关的I/O装备仍然采用半虚拟化的方案来改善性能,这类半虚拟化的方案如,virtio 和 Xen PV Bus。
虚拟机(VM)和虚拟CPU (vCPU)

有必要区分虚拟机(VM)和虚拟CPU(vCPU)。这有利于明确本文的后续部分。
例如,一个内存页面可以分配给一个虚拟机,因此所有属于该VM的vCPUs都可以访问它。
而一个虚拟中断只是针对某个vCPU,因此只有该vCPU可以收到。虚拟机(VM)和虚拟CPU(vCPU)的关系如图3所示。
https://img-blog.csdnimg.cn/img_convert/40d5f3e10ac3a3df374fe5ba0d7e2e86.png
留意:ARM体系布局定义了处置惩罚单元(Processing Element, PE)一词,现代CPU可能包含多个内核或线程,PE用来指代单一的执行单元。同样的这里的vCPU严酷来说应该是vPE。
AArch64的虚拟化

对于ARMv8, Hypervisor运行在EL2异常级别。只有运行在EL2或更高异常级别的软件才可以访问并配置各项虚拟化功能。


[*]Stage 2转换
[*]EL1/0指令和寄存器访问
[*]注入虚拟异常
安全状态和非安全状态下的异常级别及可运行的软件如图4所示
https://img-blog.csdnimg.cn/img_convert/3dc1912f8aa10079f58b08aca346edfb.png
   留意:安全状态的EL2用灰色显示是因为,安全状态的EL2并不总是可用,这是Armv8.4-A引入的特性。
Stage 2 转换

什么是Stage 2 转换

Stage 2 转换允许Hypervisor控制虚拟机的内存视图。具体来说,其可以控制虚拟机是否可以访问特定的某一块物理内存,以及该内存块出现在虚拟机内存空间的位置。
这种本领对于虚拟机的隔离和沙箱功能来说至关重要。这使得虚拟机只能看到分配给它自己的物理内存。
为了支持Stage 2 转换, 需要增长一个页表,我们称之为Stage 2页表。
利用系统控制的页表转换称之为stage 1转换,负责将虚拟机视角的虚拟地址转换为虚拟机视角的物理地址。
而stage 2页表由Hypervisor控制,负责将虚拟机视角的物理地址转换为真实的物理地址。虚拟机视角的物理地址在Armv8中有特定的词描述,叫中心物理地址(intermediate Physical Address, IPA)。
stage 2转换表的格式和stage 1的雷同,但也有些属性的处置惩罚不太一样,例如,判断内存范例 是normal 还是 device的信息被直接编码进了表里,而不是通过查询MAIR_ELx寄存器。
https://img-blog.csdnimg.cn/img_convert/5d43595e0da4a84b069952f0a63f751b.png
VMID

每一个虚拟机都被分配一个ID号,称之为VMID。这个ID号用于标记某个特定的TLB项属于哪一个VM。
VMID使得差别的VM可以共享同一块TLB缓存。VMID存储在寄存器VTTBR_EL2中,可以是8或16比特,由VTCR_EL2.vs比特位控制,其中16比特的VMID支持是在armv8.1-A中扩展的,是可选的。
需留意,EL2和EL3的地址转换不需要VMID标记,因为它们不需要stage 2转换。
VMID vs ASID

TLB项也可以用ASID(Address Space Identifier)标记,每个应用都被利用系统分配有一个ASID,所有属于同一个应用的TLB项都有雷同的ASID。
这使得差别应用可以共享同一块TLB缓存。每一个VM有它自己的ASID空间。例如两个差别的VMs同时使用ASID 5,但指的是差别的东西。对于虚拟机而言,通常VMID会结合ASID同时使用。
属性整合和覆盖

stage 1 和 stage 2映射都包含属性,例如存储范例,访问权限等。内存管理单元(MMU)会将两个阶段的属性整合成一个最终属性,整合的原则是选择更有限制的属性。且看如下例子:
   交集
https://img-blog.csdnimg.cn/img_convert/b8d40d85903b835c644fee62084197b7.png
在上面的例子中,Device属性比起Normal属性更具限制性,因此最终结果是Device属性。同样的原理,如果你将顺序变更一下也不会改变最终的属性。
属性整合在大多数情况下都可以工作。但有些时间,例如在VM的早期启动阶段,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属性,而不是使用默认的限制性整合原则。
模拟MMIO

与物理呆板的物理地址空间雷同,VM的IPA地址空间包含了内存与外围装备两种区域。如下图所示
https://img-blog.csdnimg.cn/img_convert/ac3dd45373535b76feaee7bd9c06ccda.png
VM使用外围装备区域来访问其看到的物理外围装备,这其中包含了直通装备和虚拟外围装备。虚拟装备完全由Hypervisor模拟,如下图所示
https://img-blog.csdnimg.cn/img_convert/a1bd310026f6395c261bbb65f6ebf64d.png
一个直通装备被直接分配给VM并映射到IPA地址空间,这使得VM中的软件可用直接访问真实的物理硬件。一个虚拟的外围装备由Hypervisor模拟,其stage 2的转换项被标记为fault。
固然VM中的软件看来其是直接与物理装备交互,但实际上这一访问会导致stage 2转换fault,从而进入相应的异常处置惩罚程序由Hypervisor模拟。
为了模拟一个外围装备,Hypervisor需要知道哪一个外围装备被访问,外围装备的哪一个寄存器被访问,是读访问还是写访问,访问长度是多少,以及使用哪些寄存器来传送数据。
当处置惩罚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) 的访问过程。
https://img-blog.csdnimg.cn/img_convert/6b1cf65cf645586a1beccd21c2b3e1f0.png


[*]VM里的软件尝试访问虚拟外围装备,这个例子当中是虚拟UART的吸收FIFO。
[*]该访问被stage 2转换block住,导致一个abort异常被路由到EL2。

[*]异常处置惩罚程序查询ESR_EL2关于异常的信息,如访问长度,目标寄存器,是load还是store利用。
[*]异常处置惩罚程序查询HPFAR_EL2,取得发生abort的IPA地址。

[*]Hypervisor通过ESR_EL2和HPFAR_EL2里的相关信息对相关虚拟外围装备作模拟,模拟完成后通过ERET指令返回vCPU,并从发生异常的下一条指令继承执行。
系统内存管理单元(System Memory Management Units, SMMUs)

到目前为止,我们只考虑了从处置惩罚器发起的各种访问。我们还需要考虑其他主装备如DMA控制器发起的访问。
我们需要一种方法来扩展stage 2映射以掩护这些主装备的地址空间。如果一个DMA控制器没有使用虚拟化,那它看起来应该如下图所示
https://img-blog.csdnimg.cn/img_convert/0b2905e5a57e4313e216da8b606c64b7.png
DMA控制器通常由内核驱动编程控制。内核驱动会确保不违反利用系统层面的内存掩护原则,即一个应用不能使用DMA访问其没有权限访问的其他应用的内存。
下面让我们考虑利用系统运行在虚拟机中的场景。
https://img-blog.csdnimg.cn/img_convert/909daf6fd7228b36e00b413d2f8888da.png
在这个系统中,Hyperviosr通过stage 2映射来隔离差别VMs的地址空间。这是基于Hypervisor控制的stage 2映射表实现的。而驱动则直接与DMA控制器交互,这会产生两个标题:


[*]隔离:DMA控制器访问在虚拟机之间没有了隔离,这粉碎了虚拟机的沙箱功能。
[*]地址空间: 利用两级映射转换,使内核看到的PAs实际上是IPAs。但DMA控制器看到的仍然是PAs。因此DMA控制器和内核看到的是差别的地址空间,为相识决这个标题,**每当VM与DMA控制器交互时就需要陷入到Hypervisor中做必要的转换。**这种处置惩罚方式是极其没有效率的,且轻易出错。
解决的办法是将stage 2的机制推广到DMA控制器。这么做的话,这些主装备控制器也需要一个MMU,Armv8称之为SMMU(通常也称为IOMMU)。
https://img-blog.csdnimg.cn/img_convert/6f1574eba16b58949b5797160e4ef107.png
Hypervisor负责配置SMMU,以使DMA控制器看到的物理地址空间与kenrel看到的物理地址空间雷同。如许就能解决上述两个标题。
指令的陷入与模拟

有时Hypervisor需要模拟一些利用,例如VM里运行的软件试图配置处置惩罚器的一些属性,如电源管理或是缓存同等性时。
通常你不会允许VM直接配置这些属性,因为这会打破隔离性,从而影响其他VMs。这就需要通过以陷入的方式产生异常,在异常处置惩罚程序中做相应的模拟。
Armv8包含一些陷入控制来帮助实现 陷入(trapping) – 模拟(emulating)。如果对相应利用配置了陷入,则这种利用发生时会陷入到更高的异常级别,便于Hypervisor模拟。
举个例子,执行等待中断指令WFI通过会使CPU进入低功耗状态。然而,当配置HCR_EL2.TWI==1时,如果在EL0/EL1执行WFI则会导致EL2的异常。
   注:陷入不是为虚拟化而设计的,有陷入到EL3和EL1的异常,但异常对虚拟化实现至关重要。
对于 WFI的例子里, 利用系统通过在一个idle loop里执行 WFI指令,但虚拟机中的利用系统执行该指令时,会陷入到Hypervisor里模拟,这时Hypervisor通常会调度另一个vCPU执行。
https://img-blog.csdnimg.cn/img_convert/b0d7bb0310a70903ed82f5a1e3870504.png
寄存器的访问

陷入 – 模拟的另一个用途是用来出现虚拟寄存器的值。
例如寄存器ID_AA64MMFR0_EL1是用来报告处置惩罚器内存相关特性的,利用系统可能会读取该寄存器来决定在内核中开启或关闭某些特性。
Hypervisor可能会给VM出现一个与实际物理寄存器差别的值。这是怎么实现的呢?
首先Hypervisor需要开启对该寄存器读利用的陷入。然后,在陷入的异常处置惩罚中判断异常相关的信息并进行模拟。在如下例子中,就是设置一个虚拟的值,然后ERET返回。
https://img-blog.csdnimg.cn/img_convert/3d51210eadda82115fb2243d7e61f840.png
避免陷入

**陷入 – 模拟 的开销是很大的。**这种利用需要先陷入到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后没有初始化的值,它们必须由软件启动代码初始化一个合理的值。
异常虚拟化

中断是硬件通知软件的机制,在一个使用虚拟化的系统中,中断处剖析变得更为复杂。
有些中断会由Hypervisor直接处置惩罚,有些中断被分配给了VM,需要由VM中的处置惩罚程序处置惩罚,并且还有可能在吸收到这个中断时,对应的VM并没有被调度运行。
这意味着我们不但需要支持在EL2中直接处置惩罚中断,还需要一种机制能将收到的中断转发给相应VM的vCPU。
Armv8提供了vIRQs, vFIQs, 和vSErrors来支持虚拟中断。
这些中断的行为和物理中断(IRQs, FIQs, 和 SErrors)雷同,只不过只有当系统运行在EL0/1是才会收到,运行在EL2/3是收不到虚拟中断的。
开启虚拟中断

虚拟中断也是根据中断范例控制的。为了发送虚拟中断到EL0/1, Hypervisor需要设置 HCR_EL2中相应的中断路由比特位。
例如,开启vIRQ,你需要设置 HCR_EL2.IMO, 这意味着物理IRQ中断将被发送到EL2,同时虚拟中断将被发送到EL1。
理论上,Armv8可以配置成VM直接吸收物理FIQs和虚拟IRQs。但在实际应用中,通常配置VM只吸收虚拟中断。
产生虚拟中断

有两种方式产生虚拟中断


[*]配置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发送物理中断和虚拟中断。见下图:
https://img-blog.csdnimg.cn/img_convert/0da0078b3d747d83239c676aa4f7a614.png
这两个CPU interface是等同的,区别是一个发送物理中断信号,另一个发送虚拟中断信号。
Hypervisor可以将虚拟CPU interface映射给VM,以便VM可以直接和GIC通信。
这种方式的好处是Hypervisor只需建立映射,不需要做任何模拟,从而提升了性能。(PS:虚拟化性能提升的关键就在优化陷入,镌汰次数,优化流程)
中断转发给vCPU的例子

上面介绍了虚拟中断是如何开启和生产的。让我们来看一个中断转发给vCPU的例子。
考虑一个物理外围装备,该装备被分配给了某个VM,如下图所示:
https://img-blog.csdnimg.cn/img_convert/f26c19dd70b9b9e4adac945afd6a6556.png
具体步骤如下:


[*]物理外围装备发送中断信号给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控制)。
上面的例子展示了如何将一个物理中断以虚拟中断的形式转发给VM。如果是一个没有物理中断对应的纯虚拟中断,Hypervisor可以直接注入虚拟中断。
中断屏蔽

我们知道中断屏蔽比特位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。
时钟虚拟化

Arm体系布局中,每个处置惩罚器上都有一组通用时钟。通用时钟由一组比较器构成,用来与系统计数器比较。
当比较器的值小于即是系统计数器时便会产生时钟中断。在下图中,我们可以看到系统中通用时钟由黄色框部分构成。
https://img-blog.csdnimg.cn/img_convert/945e2793fad7481e35f1fea08d474318.png
下图展示了虚拟化系统中运行两个vCPU的时序。
https://img-blog.csdnimg.cn/img_convert/654e807c1196256ff689785f105445e2.png
物理天下的时间(墙上时间)4ms里,每个vCPU各运行了2ms。如果我们设置vCPU0的比较器在T=0之后的3ms产生一个中断,那么你希望实际在哪个墙上时间点产生中断呢?
是vCPU0的虚拟时间的2ms,也就是墙上时间3ms那个点还是 vCPU0虚拟时间3ms的那个点?
实际上,Arm体系布局同时支持上述两种设置,这取决于你使用何种虚拟化方案。让我们看看这是如何实现的。
运行在vCPU上的软件可以访问如下两种时钟


[*]EL1物理时钟
[*]EL1虚拟时钟
EL1物理时钟会与系统计数器模块直接比较,使用的是绝对的墙上时间。而EL1虚拟时钟与虚拟计数器比较。虚拟计数器是在物理计数器的基础上减去一个偏移。
Hypervisor负责为当前调度运行的vCPU指定对应的偏移寄存器。这种方式使得虚拟时间只会覆盖vCPU实际运行的那部分时间。
https://img-blog.csdnimg.cn/img_convert/2ab7cf30b68ef9a079d06593f8ef4943.png
下图展示了虚拟时间运作的原理
https://img-blog.csdnimg.cn/img_convert/e91866bc42beea234e7d3762341aa752.png
在一个6ms的时段里,每个vCPU分别运行了3ms。
Hypervisor可以使用偏移寄存器来将vCPU的时间调解为实在际运行的时间。
虚拟化主机扩展(Virtualization Host Extensions, VHE)

Type 1范例的虚拟化系统的软件栈与异常级别的对应关系,Hypervisor部分运行在EL2,VMs运行在EL0/1。
https://img-blog.csdnimg.cn/img_convert/7ad7bf1df15e3a412f8d90fa8c24d247.webp?x-oss-process=image/format,png
然而,对于一个Type 2范例的系统,其软件栈与异常级别的对应关系可能如图22所示
https://img-blog.csdnimg.cn/img_convert/44a8d8b44b609ba9dc012e2686909f13.png
通常,寄主利用系统的内核部分运行在EL1,控制虚拟化的部分运行在EL2。
然而,这种设计有一个显着的标题。
VHE之前的Hypervisor通常需要设计成high-visor和low-visor两部分,前者运行在EL1,后者运行在EL2。
分层设计在系统运行时会造成很多不必要的上下文切换,带来不少设计上的复杂性和性能开销。
为相识决这个标题,虚拟化主机扩展 (Virtualization Host Extensions, VHE)应运而生。该特性由Armv8.1-A引入,可以让寄主利用系统的内核部分直接运行在EL2上。
将主机利用系统运行在EL2

VHE由系统寄存器 HCR_EL2中的两个比特位控制


[*]E2H:VHE使能位
[*]TGE:当VHE使能时,控制EL0是Guest还是Host
当发生异常从VM退出到Hypervisor时,TGE将会初始化为0,软件需要先设置这一比特,再继承运行host kernel的主代码
一个典型的配置如下图
https://img-blog.csdnimg.cn/img_convert/51a566ae024f449131d7092b6b4d3a27.webp?x-oss-process=image/format,png
虚拟地址空间

在VHE引入之前,EL0/1的虚拟地址空间看起来如下。EL0/1分两块区域,上面是内核空间,下面是用户空间。
EL2只有一个空间,Hypervisor通常不需要运行应用,因此没有必要划分内核与用户空间。
同理,EL0/1虚拟地址空间支持ASID,但EL2不需要支持。
https://img-blog.csdnimg.cn/img_convert/c1588e8a480fa18a8a7c5fd617d73572.webp?x-oss-process=image/format,png
当VHE引入之后,EL2可以直接运行利用系统代码。因此需要将地址空间划分和ASID的支持添加进来。同样,通过设置 HCR_EL2.E2H来解决。
https://img-blog.csdnimg.cn/img_convert/6e6694536e899505756045c87727af56.webp?x-oss-process=image/format,png
当运行在EL0时,HCR_EL2.TGE控制使用EL1还是EL2空间,当应用运行在Guest OS (TGE0)为前者,运行在Host OS(TGE1)为后者。
重定向寄存器访问

除了会使用差别的地址空间映射,VHE还有一个标题需要解决,那就寄存器访问。
运行在EL2的内核仍然会尝试访问*_EL1的寄存器。为了运行无需修改的内核,我们需要将EL1的寄存器重定向到EL2。当你设置E2H后,这一切就会由硬件实现。
https://img-blog.csdnimg.cn/img_convert/afb18a8edf34c67d156789f0c54604d2.png
但是,重定向又会带来一个新的标题,那就是Hypervisor完全可能在某些情况下,例如当执行任务切换时, 访问真正EL1的寄存器。
为相识决这个标题,Arm架构引入了一种新的别名机制,以_EL12或_EL02结尾。如下例,就可以在ECH==1的EL2访问TTBR0_EL1。
https://img-blog.csdnimg.cn/img_convert/c417eeb6cd69aa40ab3357bf2ec74729.png
异常

通常系统寄存器 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中,这称之为嵌套虚拟化。
https://img-blog.csdnimg.cn/img_convert/128cdfc6fecb861f77688a363c5ede91.webp?x-oss-process=image/format,png
我们将第一个Hypervisor称为Host Hypervisor,VM中运行的Hypervisor称为Guest Hypervisor。
在Armv8.3-A之前,Guest Hypervisor可以运行在EL0。但这种设计需要大量软件模拟,不但软件开发困难,性能也很差。
Armv8.3-A增长了一些新的特性,可以让Guest Hypervisor运行在EL1。而Armv8.4-A引入的一些新特性,使得这一过程更有效率,固然仍然需要Host Hypervisor到场做一些额外的工作。
Guest Hypervisor访问虚拟化控制接口

我们并不希望Guest Hypervisor能直接访问虚拟化控制接口,因为这么做会粉碎VM的沙箱机制,使得虚拟机能够看到Host平台的信息。
当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如何创建并启动虚拟机
https://img-blog.csdnimg.cn/img_convert/481237db9639c14ffb39eca4a95024cc.webp?x-oss-process=image/format,png


[*]从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执行相关利用。
https://img-blog.csdnimg.cn/img_convert/8668833a267b065007a1733e72f6bf35.webp?x-oss-process=image/format,png


[*]从EL1访问*_EL2寄存器将会被重定向到一块内存区域,该内存区域的地址由Host Hypervisor在 VNCR_EL2中指定。
[*]Guest Hypervisor尝试进入其创建的虚拟机,此时ERET指令会陷入到EL2
[*]Host Hypervisor从内存中提取配置信息,设置相关寄存器,以便启动VM,清理掉NV比特位,末了进入Guest Hypervisor创建的Guest运行。
这个改进方法相比之前的方法会镌汰陷入到Host Hypervisor的次数,从而提升了性能。
安全天下虚拟化

虚拟化扩展最早是在Armv7-A引入的。在Armv7-A中的Hyp模式等同于AArch32中的EL2,仅仅在非安全天下才存在。作为一个可选特性,Armv8.4-A增长了安全天下下EL2的支持。
支持安全天下EL2的处置惩罚器,需配置EL3下的SCR_EL3.EEL2比特位来开启这一特性。设置了这一比特位,才允许使用安全状态下的虚拟化功能。
在安全天下虚拟化之前,EL3通常用于运行安全状态切换软件和平台固件。
然而从设计上来说,我们希望EL3中运行的软件越少越好,因为越简单才会更安全。
安全状态虚拟化使得我们可以将平台固件移到EL1中运行,由虚拟化来隔离平台固件和可信利用系统内核。下图展示了这一理念
https://img-blog.csdnimg.cn/img_convert/0d619007bc4723b7260ed2d651fc4d08.webp?x-oss-process=image/format,png
安全EL2与两个IPA空间

Arm体系布局定义了安全天下和非安全天下两个物理地址空间。
在非安全状态下,stage 1转换的的输出总是非安全的,因此只需要一个IPA空间来给stage 2使用。
然而,对于安全天下,stage 1的输出可能是安全的也能是非安全的。Stage 1转换表中的NS比特位控制使用安全地址还是非安全地址。
这意味着在安全天下,需要两个IPA地址空间。
https://img-blog.csdnimg.cn/img_convert/e9ae96cc62092b46d6460eedbbe02301.webp?x-oss-process=image/format,png
与stage 1表差别,stage 2转换表中没有NS比特位。因为对于一个特定的IPA空间,要么全都是安全地址,要么全都是非安全的,因此只需要由一个寄存器比特位来确定IPA空间。
   通常来说,非安全地址经过stage 2转换仍然是非安全地址,安全地址经过stage 2转换仍然是安全地址。
虚拟化的损耗

虚拟化的损耗主要在于虚拟机和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架构虚拟化介绍】

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【万字长文】学习Armv8架构虚拟化一篇就够了