虚拟化-基础学习
虚拟化
Hypervisor
Hypervisor,又称虚拟机器监视器(英语:virtual machine monitor,缩写为 VMM),是用来管理虚拟机运行的。运行虚拟机的电脑被称为宿主机,虚拟机称为客户机,各个客户机共享虚拟化后的硬件资源。Hypervisor又分为两类:
type1
虚拟机管理程序Hypervisor直接运行在宿主机的硬件上来控制硬件和管理客户机。
- 特点
- 需要硬件支持
- 虚拟机监视器作为主操作系统
- 运行效率高
type2
Hypervisor运行在传统的操作系统上,就像其他计算机程序那样运行。
- 特点
- 虚拟机监视器作为应用程序运行在主操作系统环境内
- 运行效率一般较类型 I 低
热更新
热更新(Hot Update)指的是在应用程序运行时更新应用程序代码、配置文件等内容,而不需要停止应用程序或重新启动应用程序。
Rhyper
Rhyper 基于 AArch64 体系结构,采用高级程序语言 Rust 完成代码实现。基于 Type I 虚拟机监视器的设计模式.
Rhyper 实现了多种模拟设备,提供基本的磁盘、网络、串口等设备的虚拟化实现,保障资源的高利用率。另一方面,Rhyper 实现的模拟设备具备资源隔离属性,保障不同虚拟机间的隔离性。
Rhyper 中将虚拟机划分为四类,即管理虚拟机(MVM)、客户虚拟机 (GVM)、实时虚拟机(RTVM),裸应用虚拟机,每类虚拟机的功能介绍如下:
- MVM:MVM 作为管理虚拟机,Rhyper 为其实现了专用的 Linux 内核模块。通过 该模块 MVM 可以调用 Rhyper 为它提供的 HVC 功能接口,实现虚拟机迁移、监视 器动态升级等功能。
- GVM:GVM 作为客户虚拟机,其上运行的计算任务往往不具备实时特性,是最通 用的虚拟机
- RTVM:RTVM 是实时虚拟机,Rhyper 采用完全独占的资源分配方式保障其实时 性。在 Rhyper 动态升级的流程中,需要使 RTVM 所在核心承担更少的计算任务, 从而缩短虚拟机陷入的时间,保障实时性。
- 裸应用虚拟机:裸应用虚拟机以 uniRyper 为代表,没有用户态和内核态的区分, 它的设计目标是将操作系统内核与应用程序合并为一个单一的可执行文件,从而最 大程度地减少操作系统的大小和复杂性,并提高性能和安全性。
MVM是由虚拟机监视器内置的MVM配置文件完成静态启动和创建。其他虚拟机通过MVM的用户态CLI以及内核模块实现动态创建和启动
Rhyper整体架构:
底层一些常见术语
- Instruction set architecture
指令集架构,ISA,是一个抽象模型。它定义了指令集、寄存器、主存、虚存等。它不同于microarchitecture(微架构),微架构是ISA在一个特定处理器上的实现形式,比如Intel和AMD都实现了x86指令集,但是他们具体的实现形式有很大区别。
- ISA的分类
根据结构复杂度,可以分为两类:
- complex instruction set computer(CISC): 有许多特定的指令,有些一般不会用到
- reduced instrution set computer(RISC): 处理器仅实现经常使用的指令。RISC-V、MIPS都是这种
Armv8 内存管理
Armv8的内存管理符合一般的操作系统的内存管理,有MMU,CPU发出虚拟地址,经由MMU查找TLB,如果没有缓存,那么查找页表。
页表的层次最多有4层。
上面显示了3个虚拟地址空间:
非安全El0和El1;
非安全EL2;
EL3;
物理地址空间也有多个:
非安全PAS0
安全PAS
Stage1和Stage2的地址转换过程基本是一样的,除了页表不同。正常访问Stage2的地址也不会陷入到hypervisor中
一个虚拟地址可以映射到某个物理地址上,取决于处理器当前的安全状态:非安全状态和安全状态。如下图:
AArch64是64位架构,但不意味着所有地址都是64位的。
Armv8-A 虚拟化
参考文献:Armv8-A virtualization.pdf
Arm中,常使用的虚拟机监视器有Xen(type1)和KVM(type2)。
- 全虚拟化和半虚拟化
全虚拟化是指一般的虚拟机,它可以完全模拟物理机器。但性能差,每次对寄存器的操作都得经过监视器。
半虚拟化是为了提升性能的,如Guest OS,它好像运行在一个虚拟硬件平台上。
- 虚拟机和虚拟CPU
- AArch64下的虚拟化
Stage 2 translation
Stage 2 translation允许监视器控制VM的内存视图。可确保一个VM只能看到分配给它的资源。
客户机OS控制着Stage 1 Tables,自认为控制了物理地址空间。然而,还有第二个阶段的地址转换,这是由监视器控制。
每个VM都有一个虚拟机标识符VMID,这样在TLB里可以通过VMID来区分不同的VM对应的表项。
同时,TLB表项中还会有一个ASID标识符(Address Space Identifier),它标识不同的应用,防止不同应用相互干扰。每个VM有自己独立的ASID命名空间。即使ASID相同,但VM不同,它们也不会互相影响。
- 属性合并和覆盖
阶段1和阶段2的映射都包含一些属性,比如类型和权限。MMU会合并这两个映射的属性,得到最终的属性值。MMU使用两个阶段里最严格的属性。
模拟内存映射IO
一个VM的IPA空间包含内存和外设区域。VM的外设可以是虚拟外设,也可以是真实外设(直通设备)。
- 真实外设
如果是直通设备,也就是一个真实的物理设备真的分配给了VM,并直接映射到了IPA地址空间。因此VM可以直接访问外设。
- 虚拟外设
虚拟外设是指hypervisor模拟出来的外设。当VM访问外设对应IPA地址空间的位置时,会产生abort,之后交由hypervisor完成访问,之后异常返回。
在Rhyper中,SMMU应该是一个虚拟外设,而DMA设备则是一个直通外设。
系统MMU(SMMU)
对于其他IO方式,例如DMA,它有一个DMA控制器。对于VM来讲,系统结构图是这样的:
如果让监视器来实现DMA的两阶段映射的话,会带来额外的性能开销。解决办法就是IOMMU,又叫做SMMU。这样DMA看到的内存,与VM(OS级)看到的物理内存,是一样的了。
通过trap模拟特定指令的执行
如果客户机想要执行一些特权指令,那么监视器应该能捕获到这种指令,并来模拟该指令去修改,这样可以保证安全性。
例如执行Wait For Interrupt(WFI)指令可以让CPU处于低功耗状态。当EL0或EL1时执行WFI,发生trap交由hypervisor来处理。
注意,客户机的程序执行流也是在真实CPU上跑的,不同EL级的差别在这些CPU寄存器的配置位不同
当客户机想读取一个寄存器,而Hypervisor想呈现一个不同的值,叫做虚拟值。这时就可以发生trap到EL2级,hypervisor返回时呈现一个自定义的值给客户机。
异常的虚拟化
使用虚拟化的系统,一些中断可能被hypervisor自己来处理,一些可能由VM的软件来处理。为了避免出现VM此时没有被调度但针对该VM的中断发生的情况,ARM采用了两个机制:
- 一些中断支持在EL2级被hypervisor解决
- 其他中断能交给对应的VM或vCPU
为了实现这2种机制,ARM包含了对虚拟中断的支持:vIRQ和vFIQ。这两种中断只能在当执行El0和El1发生。
VM通常只会接受虚拟中断,而不是真实中断。
- 允许虚拟中断
设置HCR_EL2.IMO位,即可开启vIRQ等虚拟中断
- 产生虚拟中断
有2种机制可以用来产生虚拟中断。
a. 通过设定HCR_EL2寄存器的3个特定位VI、VF、VSE来产生一个对vCPU的中断信号。这样由hypervisor来模拟产生中断,会带来额外运行开销。
b. 使用GIC产生虚拟中断。GIC通过两套相同的接口来产生真实和虚拟中断。hypervisor可以将虚拟CPU接口映射到VM,实现VM和GIC的直接通信,从而不需要hypervisor来模拟中断
- 一个例子
假设使用GIC来产生虚拟中断。一台物理外设连接GIC,当物理外设发出中断信号时,交由GIC处理,之后的步骤如下图所示:
异常处理
Privilege and Exception levels
特权级规定了一个软件实体可见和控制的处理器资源。特权级的改变只能通过异常或从异常中返回,因此特权级又称为Exception level。
Arm v8共有4个exception levels,level越高,特权越大
特权级分为两类:
- 内存特权级:比如是否允许读和写这块内存,通过MMU实现
- 寄存器特权级:访问某些寄存器也是需要特权级的,如果一个寄存器的后缀为Elx,则说明至少得是Elx的特权级才能访问。不同特权级相同名称的寄存器,在硬件上是独立且分开的
处理异常时的关键系统寄存器。
Execution & security states
- 执行状态
执行状态分有两种:
- AArch32:32bit的执行状态
- AArch64:64bit的执行状态
执行状态的切换可以通过异常状态的切换而改变,但需要满足下面那个图。
- 安全状态
AArch64支持2种安全状态,可以更细的划分和隔离软件。
- 安全状态:处理器可以访问安全和非安全的物理地址空间,以及banked寄存器的安全拷贝版
- 非安全状态:PE(Processing Element)只能访问非安全的物理地址空间,以及允许非安全访问的系统寄存器
banked registers就理解成寄存器就行了。
安全状态的切换必须经过EL3。另外El2只能是安全的对于Armv8.0来说。
异常类型
异常可分为两类,同步异常和异步异常。也可以叫做内部和外部异常(中断)。
-
同步异常
-
非法指令和trap:当一些权限不够的指令运行时,会发生trap,转移到特权级更高的软件来执行这些指令
-
访问非法内存
-
异常产生指令,类似MOS的syscall,arm中包含SVC(supervisor call to El1),HVC(hypervisor call to El2),SMC(secure monitor call to El3,切换安全状态)。这些指令的执行路径如下:
-
Debug
-
data abort:当从内存中读或写一个地址时,出现错误
-
-
异步异常
- 物理中断
- System Error(SError):主要指内存管理出现的一些意外错误
- 虚拟中断:被VM处理的中断叫做虚拟中断。有3个指令用来支持虚拟中断,vSError, vIRQ, vFIQ。这3个指令可以通过hypervisor或者GIC来发出,让EL1来处理
异步异常分有两种类型,IRQ和FIQ,这两种异常用于支持外设中断,由Generic Interrupt Controller(GIC)管理,GIC用于中断的管理、优先级划分等等。
IRQ:Interrupt Request,中断请求
FIQ:Fast Interrupt Request
异步异常可以被屏蔽。
处理异常
和MOS有点像,也是有个异常向量表,查找相应的异常处理函数。
不过发生异常或异常返回时,exception level不一定必须改变,这取决于异常的类型和系统寄存器的配置。
处理异常的步骤如下:
- 保存当前处理器状态
SPSR.PSTATE保存了当前处理器状态,包含异常掩码、El等级。它的保存和更新由硬件自动完成。
- 异常路由和中断控制器
异常发生后,到哪个EL等级是由以下两个之一来决定:
a. 异常类型隐式决定,就比如SVC,HVC和SMC。
b. 系统寄存器的配置bit显式决定
对于IRQ、FIQ和SError,则是由寄存器SCR_EL3和HCR_EL2来决定。
有许多个堆栈寄存器,SP_ELx,被用来在不同的EL等级下保存寄存器上下文信息
- 从异常中返回
恢复所有寄存器,并执行ERET指令从异常中返回。
data abort异常处理
ESR_ELn:异常发生的原因
FAR_ELn:错误的虚拟地址
ELR_ELn:造成data abort的错误的指令地址