接下来我将讨论硬件对于虚拟机的支持,这里特指的就是Intel的VT-x。为什么Intel和其他的硬件厂商会为虚拟机提供直接的硬件支持呢?
- 首先虚拟机应用的非常广泛,硬件厂商的大量客户都在使用虚拟机
- 其次,我们刚刚描述的Trap and Emulate虚拟机方案中,经常会涉及到大量高成本的trap,所以这种方案性能并不特别好。
- 第三个原因或许就没那么有趣了。RISC-V非常适合Trap and Emulate虚拟机方案,但是Intel的x86处理器的一些具体实现使得它可以支持虚拟化,但是又没那么容易。所以Intel也有动力来修复这里的问题,因为它的很多客户想要在x86上运行VMM。
这里硬件上的支持,是为了让人们能够更容易地构建运行更快的虚拟机。它已经存在了10年左右了,并且现在在构建虚拟机时使用的非常非常广泛。在Trap and Emulate方案中,VMM会为每个Guest在软件中保存一份虚拟状态信息,而现在,这些虚拟状态信息会保存在硬件中。这样Guest中的软件可以直接执行privileged指令来修改保存在硬件中的虚拟寄存器,而不是通过trap走到VMM来修改VMM中保存在软件中的虚拟寄存器。所以这里的目标是Guest可以在不触发trap的前提下,执行privileged指令。
我们还是有一个VMM在内核空间,并且Guest运行在用户空间。当我们使用这种新的硬件支持的方案时,我们的VMM会使用真实的控制寄存器,而当VMM通知硬件切换到Guest mode时,硬件里还会有一套完全独立,专门为Guest mode下使用的虚拟控制寄存器。在Guest mode下可以直接读写控制寄存器,但是读写的是寄存器保存在硬件中的拷贝,而不是真实的寄存器。
硬件会对Guest操作系统的行为做一些额外的操作,以确保Guest不会滥用这些寄存器并从虚拟机中逃逸。在这种硬件支持的虚拟机方案中,存在一些技术术语,至少Intel是这么叫的,Guest mode被称为non-root mode,Host mode中会使用真实的寄存器,被称为root mode。所以,硬件中保存的寄存器的拷贝,或者叫做虚拟寄存器是为了在non-root mode下使用,真实寄存器是为了在root mode下使用。
现在,当我们运行在Guest kernel时,可以在不触发任何trap的前提下执行任何privileged指令。比如说如果想读写STVEC寄存器,硬件允许我们直接读写STVEC寄存器的non-root拷贝。这样,privileged指令可以全速运行,而不用通过trap走到VMM。这对于需要触发大量trap的代码,可以运行的快得多。
现在当VMM想要创建一个新的虚拟机时,VMM需要配置硬件。在VMM的内存中,通过一个结构体与VT-x硬件进行交互。这个结构体称为VMCS(注,Intel的术语,全称是Virtual Machine Control Structure)。当VMM要创建一个新的虚拟机时,它会先在内存中创建这样一个结构体,并填入一些配置信息和所有寄存器的初始值,之后VMM会告诉VT-x硬件说我想要运行一个新的虚拟机,并且虚拟机的初始状态存在于VMCS中。Intel通过一些新增的指令来实现这里的交互。
- VMLAUNCH,这条指令会创建一个新的虚拟机。你可以将一个VMCS结构体的地址作为参数传给这条指令,再开始运行Guest kernel。
- VMRESUME。在某些时候,Guest kernel会通过trap走到VMM,然后需要VMM中需要通过执行VMRESUME指令恢复代码运行至Guest kernel。
- VMCALL,这条新指令在non-root模式下使用,它会使得代码从non-root mode中退出,并通过trap走到VMM。
通过硬件的支持,Guest现在可以在不触发trap的前提下,直接执行普通的privileged指令。但是还是有一些原因需要让代码执行从Guest进入到VMM中,其中一个原因是调用VMCALL指令,另一个原因是设备中断,例如定时器中断会使得代码执行从non-root模式通过trap走到VMM。所以通常情况下设备驱动还是会使得Guest通过trap走回到VMM。这表示着Guest操作系统不能持续占有CPU,每一次触发定时器中断,VMM都会获取控制权。如果有多个Guest同时运行,它们可以通过定时器中断来分时共享CPU(注,类似于线程通过定时器中断分时共享CPU一样)。
VT-x机制中的另外一大部分是对于Page Table的支持。当我们在Guest中运行操作系统时,我们仍然需要使用Page Table。首先Guest kernel还是需要属于自己的Page Table,并且会想要能够加载CR3寄存器,这是Intel中类似于SATP的寄存器。VT-x使得Guest可以加载任何想要的值到CR3寄存器,进而设置Page Table。而硬件也会执行Guest的这些指令,这很好,因为现在Guest kernel可以在不用通过trap走到VMM再来加载Page Table。
但是我们也不能让Guest任意的修改它的Page Table,因为如果这样的话,Guest就可以读写任意的内存地址。所以VT-x的方案中,还存在另一个重要的寄存器:EPT(Extended Page Table)。EPT会指向一个Page Table。当VMM启动一个Guest kernel时,VMM会为Guest kernel设置好EPT,并告诉硬件这个EPT是为了即将运行的虚拟机准备的。
之后,当计算机上的MMU在翻译Guest的虚拟内存地址时,它会先根据Guest设置好的Page Table,将Guest虚拟地址(gva)翻译到Guest 物理地址(gha)。之后再通过EPT,将Guest物理地址(gha)翻译成主机物理地址(hpa)。硬件会为每一个Guest的每一个内存地址都自动完成这里的两次翻译。EPT使得VMM可以控制Guest可以使用哪些内存地址。Guest可以非常高效的设置任何想要的Page Table,因为它现在可以直接执行privileged指令。但是Guest能够使用的内存地址仍然被EPT所限制,而EPT由VMM所配置,所以Guest只能使用VMM允许其使用的物理内存Page(注,EPT类似于19.4中的Shadow Page Table)。
学生提问:我对于硬件中保存的虚拟寄存器有问题,如果你有两个CPU核,然后你想要运行两个虚拟机,你会得到多少虚拟寄存器?
Robert教授:每一个CPU核都有一套独立的VT-x硬件。所以每一个CPU核都有属于自己的32个通用寄存器,属于自己的真实的控制寄存器,属于自己的用在Guest mode下的虚拟控制寄存器,属于自己的EPT,所以你可以在两个CPU核上运行两个不同的虚拟机,它们不会共用任何寄存器,每个CPU核都有属于自己的寄存器。
学生提问:那也需要一个新的VMM吗?
Robert教授:VMM可以像一个普通的操作系统一样。XV6可以支持多个进程,并且为每个进程配备一个proc结构体。而我们的VMM也会为每个虚拟机配备一个vm结构体,用来跟踪Guest的信息。并且,如我之前所说的,如果你只有一个CPU核,但是有3个Guest,可以通过定时器中断结合VMM在3个Guest之间切换。