【处理器】简介硬件虚拟化技术
硬件虚拟化是一种以处理器提供虚拟化能力来高速运行虚拟机的技术。先讲讲软件虚拟化的实现方法。一般是两种方案:
第一种方案是实现解释器,把x86指令当作伪指令进行解释后再执行,不适合高性能编程。
第二种方案是实现重编译器,把x86程序重新编译成不干扰原系统的程序。这种方式即JIT方式,性能通常较好。重编译的方法也常用于各类模拟器之中。
现有硬件虚拟化技术的优势在于处理器可以直接运行虚拟机内程序的指令,虚拟机软件无需对其重编译,处理器运行的是原汁原味的代码。
在硬件虚拟化技术中,AMD对处理器的运行模式进行如下划分:
Host模式。虚拟机以外的程序在此模式中运行。Intel将此模式称为VMX Root运行模式。
Guest模式。虚拟机内的程序运行在此模式中。Intel将此模式称为VMX Non-Root运行模式。
以下两种行为造成Host与Guest的模式间切换:
VM-Entry将Host模式切换到Guest模式。在AMD处理器上,虚拟机软件通过vmrun指令实现切换。在Intel处理器上,虚拟机软件通过vmlaunch和vmresume指令实现切换。
VM-Exit将Guest模式切换到Host模式。虚拟机软件通过设置监视虚拟机的行为,当虚拟机中出现被监控的行为时,处理器发起VM-Exit,从Guest模式切换到Host模式。
关于虚拟机的控制,虚拟机软件在AMD处理器上要设置虚拟机控制块(Virtual Machine Control Block, VMCB),在Intel处理器上要设置虚拟机控制结构(Virtual Machine Control Structure, VMCS)。
由于本人偏向于写AMD64上的程序,本文就先写AMD的硬件虚拟化技术。
硬件虚拟化的最基本的一点是处理器的虚拟化,因此要介绍硬件虚拟化就要先介绍处理器虚拟化。
在AMD处理器上,VMCB是一片内存,大小占一个页,即4KiB,地址必须按页对齐。一个VMCB用于控制一个虚拟机的vCPU。
VMCB被一分为二,分别是控制区(Control Area)和状态区(State Save Area),各自均占1KiB,剩余2KiB被保留,必须清零。
当虚拟机软件执行vmrun指令后,处理器先保存Host状态至Host保存区。Host保存区也是一个4KiB的页,必须按页对齐,虚拟机软件需要事先将其物理地址写入到HSAVE的MSR寄存器中。
保存好Host状态后,处理器读取VMCB的状态区和控制区。处理器会先检查状态是否合法,若非法则发起VM-Exit。如果正常,则处理器将VMCB的状态区覆盖到处理器上下文,然后根据控制区设置行为拦截,最后开始运行。
需要一提的是vmrun并不会把所有状态区的东西都覆盖到处理器上,这一小部分的状态由vmload指令覆盖到处理器上。
当处理器拦截到特定行为时,发起VM-Exit。此时处理器把虚拟机状态保存到VMCB的状态区上,并且把VM-Exit的原因记录下来保存到控制区上。
发起VM-Exit时,处理器从Host保存区读取状态,如果状态被污染,则处理器进入关机状态。
如果处理器支持Decode Assists,则处理器对被拦截的指令进行解码,并保存到控制区上。
如果处理器支持Next RIP Saving,则处理器计算下一条指令的地址,并保存到控制区上。
同样的,VM-Exit时处理器不会把所有的虚拟机状态保存到VMCB的状态区,这一小部分的状态由vmsave指令保存到状态区上。
需要说明的是,这里所谓的状态,指的是处理器的一堆寄存器,如CR3,RIP等寄存器。值得一提的是:
在VMCB控制范围内的寄存器(如cr0, cr3, rsp, rip等)在模式切换中处理器会自动设置这些寄存器,
而不在VMCB控制范围内的寄存器(如rcx, rdx, xmm0等)在模式切换中处理器会自动顺延这些寄存器。
这里就存在一个问题了,如果运行的代码涉及内存怎么办呢?访问内存是不是就直接访问到原地址上了?
答案是:如果虚拟机软件不对其加以控制,确实是的。虚拟机如何利用硬件虚拟化技术实现内存虚拟化呢?
我以前发过内存分页的概念:https://www.0xaa55.com/thread-16949-1-1.html
处理器的分页技术可以将虚拟地址解释到物理地址。那么自然的,虚拟机软件也应该通过这种方式实现内存虚拟化。
这里提出几个概念:GPA, GVA, HPA。
GPA,即Guest Physical Address,指Guest中引用的物理地址。
GVA,即Guest Virtual Address,指Guest中引用的虚拟地址。
HPA,即Host Physical Address,指Host中引用的物理地址。
在内存虚拟化中,我们把虚拟机运行模式分为两种:非分页模式和分页模式。
在非分页模式的内存虚拟化是最简单的,虚拟机中的程序引用的地址直接就是GPA,可以设置虚拟机中已经启动了分页,并设置CR3为GPA转换到HPA的页表。
但分页模式就麻烦了,此时虚拟机的程序引用的地址是GVA,虚拟机也已经设置好了页表,此时就需要虚拟机实现GVA转换到HPA了。
方法是:合并页表。虚拟机软件需要拦截对虚拟机对CR3寄存器的读写。
拦截读取时,虚拟机软件返回GVA转GPA的页表地址。
拦截写入时,虚拟机软件合并完页表后使得页表能转换GVA到HPA,再把页表地址写入到VMCB的状态区中。
这里还要考虑TLB的问题,因此要拦截invlpg指令,所以还要再刷新一次页表结构。
合并页表f的时间复杂度是指数级复杂度,因此在频繁切换CR3的虚拟机环境下非常消耗性能。在什么情况下会切换CR3?答案是操作系统在切换进程的时候。
在这种情况下,嵌入页表技术(Nested Page Table, NPT)就诞生了。NPT的意义在于加速内存虚拟化,以追加一层页表的方式,将虚拟机访问的GPA再转换到HPA。
利用嵌入页表技术,虚拟机软件只需设置好嵌入页表就可以实现高效的内存虚拟化。
不过,一台电脑不光是只有处理器和内存的,它还有外设,如键盘鼠标硬盘网卡声卡显卡等等等等很多硬件。操控外设需要动用I/O,这里就涉及I/O的虚拟化了。
AMD在芯片组上实现了IOMMU,即I/O Memory Management Unit,又称AMD-Vi,来实现I/O虚拟化。不过,第二类虚拟机软件(如VMware Workstation)大抵是不需要动用这项技术的。
原因也很简单,我们在使用VMware Workstation时外设不是直通的,而是被VMware抽象化的。虚拟键盘鼠标在虚拟机中其实都是被抽象出的PS/2设备,至于硬盘网卡等更是不用多说。
即便是USB设备也非“直通”,Windows平台的VMware Workstation是通过USB过滤驱动实现“直通”一个USB设备的。同时,USB控制器同样是被抽象化的(VMware的USB3.0控制器居然可以用Intel的XHCI驱动)。
关于如何实现I/O虚拟化,重点是IOMMU中可以实现DMA与中断的重映射。这是PCI设备直通虚拟机的重点。
再谈谈Intel的硬件虚拟化技术。在Intel处理器上,VMCS也是一个页,占4KiB,地址必须按页对齐,一个VMCS控制一个虚拟机的vCPU。
VMCS被Intel一分为六,分别是:
[*]虚拟机状态区(Guest State Area)处理器在VM-Entry时加载的处理器状态。
[*]宿主机状态区(Host State Area)处理器在VM-Exit时加载的处理器状态。
[*]虚拟机控制域(VM-Execution Control Fields)处理器拦截虚拟机中的特定行为。
[*]VM-Exit控制域(VM-Exit Control Fields)处理器在VM-Exit时的行为。
[*]VM-Entry控制域(VM-Entry Control Fields)处理器在VM-Entry时的行为。
[*]VM-Exit信息域(VM-Exit Information Fields)处理器在VM-Exit时解码的信息。
从中可以看到,Intel VT-x在VM-Entry时是不保存宿主机状态的,需要虚拟机软件事前保存并写进VMCS。
在VM-Entry时,处理器先检查虚拟机控制域和宿主机状态区是否非法,若是则直接进入下一条指令,并把错误写入到VMCS中。
然后检查虚拟机状态区是否非法,若是则产生VM-Exit,反之正式开始执行虚拟机代码。
在VM-Exit时,处理器检查宿主机状态区是否被污染,若是则发起VMX-Abort,并将原因写入VMCS(引起VMX-Abort还有其他原因),处理器进入关机状态。
这里其实省略了不少细节,但我认为作为简介可以省略,想知道具体省略了些啥的话可以翻阅Intel手册。
Intel与AMD不同之处在于,VMCB的结构是透明的,可以使用任意能操作内存的指令对VMCB进行读写,而VMCS的结构是不透明的,只能用vmread和vmwrite指令对VMCS进行读写。
关于内存虚拟化,Intel有扩展页表技术(Extended Page Table, EPT),但与AMD的NPT有少量不同,主要是页表结构的问题。AMD的NPT页表结构与AMD64的长模式分页的结构完全一致。
而Intel EPT的结构相对于长模式分页结构有所变化,但变化并不大,只能说是换汤不换药,但相比NPT,EPT多出了几个特性:
[*]只可执行内存属性,即该页的内容只可执行,不可读写。否则产生EPT违例,触发VM-Exit。这一条特性非常适合隐藏Inline Hook:把被Hook的函数的那一页设置为只可执行,当其他指令试图读写这里的时候就翻转为可读写不可执行,而再执行到的时候就再翻转为只可执行即可。
[*]有特权可执行内存属性,即该页可以设置为仅允许CPL=0的环境下执行。否则产生EPT违例,触发VM-Exit。
[*]EPT违例转为Guest异常,即产生EPT违例时,不触发VM-Exit,而是转换为Guest内的#VE异常。
[*]原子性EPT页表切换,即Guest通过vmfunc指令,将EPT的第四级映射页的物理地址切换为EPTP表中的某一个。若虚拟机软件允许,这个过程不触发VM-Exit。
[*]页修改日志,即Page-Modification Logging,简称PML。虚拟机软件可通过PML追踪虚拟机软件写入页的操作。
关于I/O虚拟化,Intel在芯片组上实现了Intel VT-d,即Intel Virtualization Technology for Directed I/O。功能与AMD-Vi相同,不多做解释了。
最后再谈一个问题:嵌套虚拟化。也就是:虚拟机里能不能再开虚拟机?
答案是可以的,就看虚拟机软件有没有实现这个能力了(VMware Workstation是有的)。在Guest中执行硬件虚拟化的指令会触发VM-Exit,此时虚拟机软件就可以实现虚拟化硬件虚拟化指令了。
当然咯,细节上没那么简单,解释起来比较复杂。在我的开源项目里写过虚拟化Intel VT-x和AMD-V的不完整版文档,介绍了预期的算法,但因为精力问题没有把代码实现出来,这里我直接把文档链接抛出来(纯英文无图解警告):
https://github.com/Zero-Tang/NoirVisor/blob/master/src/vt_core/readme.md#vmx-nesting-algorithm-incomplete-version
https://github.com/Zero-Tang/NoirVisor/blob/master/src/svm_core/readme.md#svm-nesting-algorithm-incomplete-version 学习到了!
页:
[1]