不患寡而患不均,不患贫而患不安。—- 孔子
本文翻译自文档AArch64 Virtualzation。
1 AArch64虚拟化
大多数主流操作系统都建立在一个假设上:系统由一个特权OS运行多个非特权OS应用。但是ARM虚拟化可以使能多个OS共存且运行在相同的系统上。实现这些虚拟core要求更精细化的硬件扩展(用来加速虚拟化之间的切换)和hypervisor软件。
Hypervisor为运行多个操作系统共享单个硬件处理器的程序。虚拟化hypervisor根据设计分为裸机或host。不管怎么分类,hypervisor的功能角色是相同的,平台资源的仲裁,以最小工作量和运行牺牲实现单个guest操作系统的无缝操作。
在如下图中,对于type1,裸机hypervisor,每个VM包含一个guest OS。对于type2,托管的hypervisor为host OS的扩展,每个子guest OS包含在单个VM中。这里主要由两个开源hypervisor,KVM和XEN。在这个图中,XEN为type1 hypervisor而KVM为type2 hypervisor。
裸机虚拟化意味着type1 hypervisor可以直接访问硬件资源,它会导致更好的性能。
Type2 hypervisor首先要求一个OS被安装。Host OS先启动。hypervisor像一个应用一样被安装到OS上。该方法提供了更好的硬件兼容性,因为OS负责硬件驱动而不是hypervisor。零一方面,托管hypervisor不能直接访问硬件且必须通过OS,这会减低VM性能。因为由很多服务和应用运行在host OS上,hypervisor通常在VM运行在上面时使用硬件资源。
在相同的系统上使用多个操作系统变成可能,但提供的OS通过多个架构特性决定系统的归属:
- hypervisor代码执行在EL2
- 支持修改core上下文和状态的陷入异常
- 支持路由异常和虚拟化中断
- 两stage的转换,第二stage对于hypervisor隔离guest操作系统
- hypervisor异常调用HVC
ARMv8-A架构允许使用AArch32或AArch64异常状态的虚拟化。在EL2上的hypervisor可以运行在AArch32或AArch64异常状态。
当EL2上的hypervisor运行在AArch64时,这里由几个寄存器有用:
- 异常返回状态寄存器:SPSR_EL2和ELR_EL2
- 栈指针:SP_EL2和SP_EL0
2 Hypervisor软件
Hypervisor提供的功能不依赖hypervisor实现的类型。它们包括:
- 内存管理
- 设备模拟
- 设备分配
- 异常处理
- 指令陷入
- 管理虚拟异常
- 中断控制管理
- 调度
- 上下文切换
- 内存转换
- 管理多个虚拟地址空间
2.1 内存管理
一个普通的OS管理应用运行的内存,OS定位到的内存。hypervisor负责自己内存的管理,同时也负责guest操作系统的内存管理。整个物理内存由hypervisor直接处理。EL2的MMU被hypervisor用于转换虚拟地址,hypervisor使用虚拟地址定位到物理地址。EL2因此有自己独立的vector table。
使用寄存器VBAR_EL2设置内存中vector table的位置。
下列代码例子如下:
ADR X0, vector_table
MSR VBAR_EL2, X0
除了建立和管理自己的转换表,hypervisor必须创建和管理每个guest的stage2转换表。
例子代码如下所示:
由hypervisor建立起的stage2转换表将IPA转换为物理内存地址。尝试在stage2转换地址导致的任何abort会陷入到EL2。
Hypervisor负责接受abort并合适处理他们。对于故意且合法的fault,hypervisor会采取补救措施如模拟设备或给guest操作系统分配更多内存。对于非预期fault,hypervisor可以选择中止guest操作系统或将abort报告给guest。
2.2 设备模拟
平台设备是内存映射的,当虚拟化生效时guest访问设备至少要经过stage2转换,在这种情况下存在一些情况hypervisor在软件中模拟设备,并提供一个替代驱动来访问物理设备或虚拟软件设备。虚拟机的模拟设备由软件实现硬件。它们完全在软件中。
当超过一个guest能够意识到一个平台设备,设备模拟就非常必要,它使用物理地址,并尝试访问。因为共享的性质,guest不被允许直接访问而没有仲裁。在这种情况下,hypervisor用stage2转换表描述符可以控制所有guest对设备区域的访问。
Guest可以通过读取ID寄存器或设备树中的询问寄存器来检测平台设备。与之前采用相同的陷入机制一样,hypervisor可以返回guest读的dummy值,并忽略写,给guest影响设备不能存在在平台上。
当模型有相同的数据传递时,hypervisor发起虚拟IRQ(vIRQ)。Guest OS通过尝试读取硬件寄存器进行回复。hypervisor陷入这些访问并提供模拟回复。
在当前存在的hypervisor方案上,如KVM或Xen,让guest访问平台设备并不常见。这两个hypervisor都提供了虚拟平台并模拟所有设备,异常仅发生在网络或存储控制器,而不是平台设备。
2.3 设备分配
设备模拟是必要的但也是昂贵的,因为guest所有访问到设备都会被陷入并在软件中被模拟。Hypervisor可以选择将独立的设备分配给独立的guest操作系统因此guest可以拥有并运行该设备而不需要Hypervisor仲裁。
挑战在于guest需要隐藏设备位于不同的物理地址,并产生guest期望的不同中断ID。相反,hypervisor可能会选择从一组guest中隐藏设备,或因为设备不存在或已经分配给不同guest。
透明的stage2映射,中断虚拟化设法回避这些挑战。
2.4 异常处理
异常被陷入,并路由到EL2,最终由hypervisor处理。这些行为可以通过HCR_EL2的控制位进行选择。
这些位如下所示:
位 | 名称 | 功能 |
[3] | FMO | 控制FIR异步异常是否被路由到hypervisor |
[4] | IMO | 控制IRQ异步异常是否被路由到hypervisor |
[5] | AMO | 控制SError异步异常是否被路由到hypervisor |
[6] | VF | 虚拟FIQ错误。当HCR_EL2.FMO被设置时虚拟FIQ仅被使能 |
[7] | VI | 虚拟IRQ错误。当HCR_EL2.IMO被设置时虚拟IRQ仅被使能 |
[8] | VSE | 虚拟SError/异步abor |
[19] | TSC | 控制非安全EL1执行的SMC指令是否被陷入 |
[27] | TGE | 对同步异常使能或禁用陷入 |
AArch64异常向量表包含四块。到底哪一块被使用依赖于core是否运行在低异常级别(EL0/EL1)还是运行在EL2。
如果core运行在低异常级别时,EL1的执行状态决定哪个块被使用。如果core运行在EL2时,当前选择栈指针PSTATE.SPSel决定哪个块。
2.5 指令陷入
除了陷入异常外,hypervisor也可以被配置为陷入某个指令。这很正常,因为指令可能携带地址或由hypervisor表转换的系统地址修改。这也可以通过HCR_EL2配置。
当某个指令被陷入时,hypervisor代码读取ESR_EL2来获取陷入指令的某些必要信息。
下列指令可以被陷入:
- 虚拟内存控制寄存器的访问,如TTBRn和TTBCR
- 系统指令,如cache和TLB维护指令
- ACTLR_EL1的访问
- 读取ID寄存器
- WFE和WFI指令。比如当当前guest OS尝试进入低功耗状态时,这两个指令用于使能hypervisor修改运行的guest OS。
2.6 虚拟异常
ARMv8-A提供三种虚拟异常的支持:虚拟SError,虚拟IRQ和虚拟FIQ。虚拟异常为event,如由hypervisor人工产生的中断和abort,或响应一个物理异常,或由于设备模拟人为产生的(不需要回复物理异常)。它们以相同的方式回复对应的物理异常。物理异常被配置由hypervisor进行处理。仅虚拟异常被传递给guest。
当对应的物理异常被路由到运行在EL2的hypervisor时虚拟异常被发出。
通过写HCR_EL2将它们进行注册,然后如果没有屏蔽,它们会被guest OS获取。
这意味着hypervisor控制虚拟异常的屏蔽和产生。当一个真实的物理异常产生且被路由到hypervisor软件时,hypervisor可能会运行一些代码,然后发出一个虚拟异常给当前guest OS。Guest OS处理异常就像处理相同的物理异常一样,它并没有意识到hypervisor已经参与。虚拟异常通过虚拟CPU接口被发出,或使用HCR位。当异常被路由到hypervisor时,PSTATE A,I,F位不再屏蔽物理异常,替代它们在guest OS中屏蔽处理虚拟异常。
在非安全EL0和EL1中,每个虚拟异常由相应的PSTATE屏蔽位进行屏蔽。运行在EL1的代码可以写PSTATE.{A, I, F}。但是如果物理异常被配置为路由到EL3时,hypervisor不再访问且不能产生虚拟异常来回复。
2.7 上下文切换
当hypervisor调试另一个guest运行在core上时,它必须发出上下文切换,即保存当前运行guest的上下文到内存中,然后从内存中恢复新的guest的上下文。目的是在唤醒之前在当前代码上创建新的guest的环境,仿佛不会中断执行一样。通过发出上下文切换,hypervisor保证执行环境跟随guest,并提供假像guest一直都占用虚拟core。
下列Guest上下文必须被保存和恢复:
- 代码的通用寄存器包括各个模块的bank寄存器
- 为内存管理和访问控制的系统寄存器内容
- core上的私有中断的pending和active状态
- 如果guest使用core私有timer,timer寄存器必须被保存和恢复因此它们可以在期望的间隔产生中断
物理内存被分配给guest,然后guest可以看到RAM,它保存且不需要保存或恢复。通过使用内存转换的两个阶段,guest使用的物理内存保持私有且与众不同,即使对于相同的guest。
2.8 内存转换
转换机制是一个广泛的术语,包括core和转换表的特权和异常级别。在Normal world的虚拟化系统中可能使用多个内存转换机制。
在这种情况下的转换为基于内存表的内存访问。转换stage包含一组将输入地址转化为输出地址的转换表。输入地址或虚拟地址由编译器和链接器使用,将代码放在内存中。输出地址或物理地址由实际系统硬件使用。
这些转换使用MMU进行,且转换表结构由软件创建来控制转换。输入输出地址根据转换的stage有不同的名称。
hypervisor控制两种转换。当代码执行在EL0或EL1时,转换由OS建立和控制。在没有虚拟化的系统中,该机制用于将虚拟地址转化为物理地址。在虚拟化系统中,物理地址被当作IPA,因为它会被放到stage2。
在stage2存在时,第一级转换被称为stage1。IPA不能用于定位系统内存。虽然被称为IPA,从guest角度来看,该转换输出的即为物理地址。
转换的第二级使用VTTBR_EL2和VTCR_EL2,它们由hypervisor控制:
对于hypervisor的虚拟地址空间,使用单个stage转换,由TTBR0_EL2和TCR_EL2控制:
ARMv8-A虚拟化也引入VMID的概念。每个虚拟机分配一个VMID,它为8位值,保存在VTTBR_EL2中。
架构不会定义在硬件上VTTBR_EL2.VMID被复位成什么值。这意味着运行在每个active core的引导代码软件必须初始化VTTBR_EL2.VMID为已知值,比如为0,即使转换的第二级不再使用。