1. 概述
虚拟化技术中最关键的技术之一就是CPU虚拟化。在没有硬件辅助虚拟化技术出来之前,通常都是通过TCG(软件进行指令翻译)的方式实现CPU虚拟化。但是由于TCG方式的虚拟化层开销太大,性能太差,因此引入了硬件辅助虚拟化技术。
1.1 Intel的硬件辅助虚拟化
Intel的硬件辅助虚拟化技术叫做Intel-VT (Virtualization Technology)技术。具备这种硬件辅助虚拟化技术的CPU包括VMX root operation 和 VMX non-root operation两种模式。两种模式都支持Ring 0 ~ Ring 3 共 4 个运行级别。宿主机的VMM运行在VMX root operation模式下。客户机操作系统及其他应用运行在VMX non-root operation模式下。一般来说,客户机的操作系统直接运行在VMX non-root operation模式的Ring0层,大部分指令可以直接发送给CPU而不需要经过VMM。只有在执行一些比较特殊的指令时才会切换到VMM下。
1.2 ARM的硬件辅助虚拟化
ARM的硬件辅助虚拟化技术叫做ARM-V (Virtualization) 技术,从ARMv8开始比较好的支持这种硬件辅助虚拟化技术。类似X86根/非根模式,ARM引入多个异常级来控制资源访问权限。宿主机上VMM运行在EL2层,客户机操作系统运行在EL1层,应用程序运行在EL0层。
1.2.1 ARMv8 CPU异常级
ARMv8的异常级分为4级(EL0~EL3),权限也是从低到高。同时也分了两个CPU运行状态(Non-secure和Secure),顾名思义为了安全和非安全的运行态,此处不扩展。每一级运行的内容如下图所示:
运行在不同异常级最大的区别就是能够访问的寄存器组不同,同时也影响了某些硬件行为(比如对页表的lookup)。运行在高异常级时可以访问低异常级的寄存器组,反之不行。
为了切换到更高的异常级(主动触发某个异常级的异常),需要主动执行特殊的指令:
- Supervisor Call (SVC)。一般由EL0切换到EL1的指令,会进入EL1的异常向量表。
- Hypervisor Call (HVC) 。Non-secure EL1切换到EL2的指令,会进入到EL2的异常向量表。
- Secure monitor Call (SMC) 。切换到EL3的指令,只有在EL1、EL2执行有效。
1.2.2 Virtualization Host Extensions(VHE)特性
ARM有一个比较特殊的技术叫做VHE (Virtualization Host Extension)。如果CPU支持VHE特性,那么宿主机的Linux内核和VMM都运行在EL2异常级,这样可以减少EL1和EL2之间模式切换的次数,具备更高的性能。在不支持VHE的CPU上,VMM代码被分成两部分在不同的异常级执行,其中一部分运行在EL2,比如捕获Guest OS退出异常、VM-Exit保存恢复上下文,另一部分VMM代码和Host内核在EL1异常级执行,这样Hypervisor在进行资源管理和虚拟机调度时需要在EL1和EL2之间进行切换,效率会低一些。
如上图所示,在引入VHE之前(b图),Host Kernel被分为两个部分,一部分在EL1,另外一部分(KVM中的Lowisor)在EL2。如果VM发生中断,那么会首先Trap到EL2的Lowisor,然后调用Eret回到EL1的KVM进行处理。当处理完之后,再次进入VM之前,需要再Trap进入EL2,然后再调用Eret回到VM。总共发生了2次Trap,以及调用了2次Eret。不管是软件还是硬件的开销,都不小,尤其是频繁VM exit的情况下。
引入VHE之后(c图),Host Kernel完全运行在EL2上。因此,当VM发生中断,Trap到EL2后,所有的处理逻辑都会在EL2实现,而不需要再回到EL1做额外的处理。对比non-VHE,减少了大量Trap带来的软件和硬件上的开销。
2. Qemu-KVM CPU虚拟化
Qemu-KVM是当前主流的一种虚拟化实现方式。从总体框架上看,大致如下图:
大致分为qemu和内核KVM的视角来分析。
2.1 Qemu视角
首先,因为Qemu也是用户态程序,所以运行在EL0。Qemu会为虚拟机创建配置的vCPU,每个vCPU就是一个线程(thread)。同时,Qemu也创建了其他的辅助线程,比如用来处理主线程事件的event loop线程、处理IO请求的io threads、vnc worker threads等。当然,Qemu除了模拟CPU,也可以模拟虚拟机的其它任何设备,本文着重分析KVM vCPU线程的实现(硬件辅助虚拟化下虚拟CPU的模拟)。
连接qemu和kvm的桥梁就是通过ioctl来请求/dev/kvm的接口服务。因此,KVM相关的功能,通过/dev/kvm设备的形式呈现给了userspace。
2.2 KVM视角
KVM作为Linux内核的一个模块,集成了硬件虚拟化扩展的功能(可以认为就是硬件虚拟化特性的驱动模块)。ARM的KVM模块在目前的开源最新版本还不支持配置成模块化的方式,只能内建于Linux内核。
KVM提供了几个重要的ioctl接口。
- KVM_CREATE_KVM:创建一个虚拟机,并返回一个fd_vm的虚拟机文件描述符。
- KVM_CREATE_VCPU:可以对fd_vm所对应的虚拟机创建vCPU,并对vCPU做初始化操作。
- KVM_RUN:对fd_vcpus操作,启动运行虚拟机。
每个vCPU thread通过调用KVM_RUN ioctl来执行vCPU结构体中保存的Guest代码。当虚拟机上下文发生异常、中断、执行高权限指令时,虚拟机会发生VM exit,Trap到Hyp Mode回到KVM,由KVM接管、处理产生的异常、中断,以及必要时是否模拟执行的高特权指令。
某些异常是KVM处理不了的,比如虚拟机内部读写virtio设备的地址空间产生的VM exit,此时KVM会返回到Qemu,由Qemu做进一步处理,待Qemu处理完之后,再通过KVM_RUN ioctl重新回到虚拟机上下文。
3. 小结
本文主要介绍了ARM硬件辅助虚拟化原理,ARMv8 VHE特性。通过分析Qemu-KVM虚拟化软件流程,配置CPU异常级,模拟VCPU线程,控制虚拟机陷入陷出,最终达到物理CPU资源复用的能力。