19.3 Trap-and-Emulate --- Emulate

VMM会为每一个Guest维护一套虚拟状态信息。所以VMM里面会维护虚拟的STVEC寄存器,虚拟的SEPC寄存器以及其他所有的privileged寄存器。当Guest操作系统运行指令需要读取某个privileged寄存器时,首先会通过trap走到VMM,因为在用户空间读取privileged寄存器是非法的。之后VMM会检查这条指令并发现这是一个比如说读取SEPC寄存器的指令,之后VMM会模拟这条指令,并将自己维护的虚拟SEPC寄存器,拷贝到trapframe的用户寄存器中(注,有关trapframe详见Lec06,这里假设Guest操作系统通过类似“sread a0, sepc”的指令想要将spec读取到用户寄存器a0)。之后,VMM会将trapframe中保存的用户寄存器拷贝回真正的用户寄存器,通过sret指令,使得Guest从trap中返回。这时,用户寄存器a0里面保存的就是SEPC寄存器的值了,之后Guest操作系统会继续执行指令。最终,Guest读到了VMM替自己保管的虚拟SEPC寄存器。

学生提问:VMM是怎么区分不同的Guest?

Robert教授:VMM会为每个Guest保存一份虚拟状态信息,然后它就像XV6知道是哪个进程一样,VMM也知道是哪个Guest通过trap走到VMM的。XV6有一个针对每个CPU的变量表明当前运行的是哪个进程,类似的VMM也有一个针对每个CPU的变量表明当前是哪个虚拟机在运行,进而查看对应的虚拟状态信息。

学生提问:VMM可以给一个Guest分配多个CPU核吗?

Robert教授:稍微复杂点的VMM都可以实现。

学生提问:在实际的硬件中会有对应寄存器,那么为什么我们不直接使用硬件中的寄存器,而是使用虚拟的寄存器?

Robert教授:这里的原因是,VMM需要使用真实的寄存器。举个例子,想象一下SCAUSE寄存器,当Guest操作系统尝试做任何privileged操作时(注,也就是读写privileged寄存器),会发生trap。硬件会将硬件中真实的SCAUSE寄存器设置成引起trap的原因,这里的原因是因为权限不够。但是假设Guest操作系统只是从Guest用户进程执行了一个系统调用,Guest操作系统需要看到SCAUSE的值是系统调用。也就是说Guest操作系统在自己的trap handler中处理来自Guest用户进程的系统调用时,需要SCAUSE的值表明是系统调用。

而实际的SCAUSE寄存器的值却表明是因为指令违反了privilege规则才走到的trap。通常情况下,VMM需要看到真实寄存器的值,而Guest操作系统需要能看到符合自己视角的寄存器的值。(注,在Guest操作系统中,可能有两种情况会触发trap,一种是Guest用户空间进程的系统调用,也就是正常操作系统中正常的trap流程,另一种是Guest内核空间读取privileged寄存器时,因为Guest内核空间实际上也是在宿主机的用户空间,导致这是个非法操作并触发trap。Robert这边举的例子的流程应该是这样,Guest用户进程执行系统调用,在这一个瞬间SCAUSE寄存器的值是ECALL,也就是8,详见6.6。但是稍后在Guest系统内核的trap handler中需要读取SCAUSE的值,以确定在Guest中引起trap的原因,但是这就触发了第二种trap,SCAUSE的值会变成Illegal Access。我们不能让Guest系统内核看到这个值,所以VMM这里将它变成ECALL并返回。)

在这种虚拟机的实现中,Guest整个运行在用户空间,任何时候它想要执行需要privilege权限的指令时,会通过trap走到VMM,VMM可以模拟这些指令。这种实现风格叫做Trap and Emulate。你可以完全通过软件实现这种VMM,也就是说你可以只通过修改软件就将XV6变成一个可以运行在RISC-V上的VMM,然后再在之上运行XV6虚拟机。当然,与常规的XV6一样,VMM需要运行在Supervisor mode。

所有以S开头的寄存器,也就是所有的Supervisor控制寄存器都必须保存在虚拟状态信息中。同时还有一些信息并不能直接通过这些控制寄存器体现,但是又必须保存在这个虚拟状态信息中。其中一个信息就是mode。VMM需要知道虚拟机是运行在Guest user mode还是Guest Supervisor mode。例如,Guest中的用户代码尝试执行privileged指令,比如读取SCAUSE寄存器,这也会导致trap并走到VMM。但是这种情况下VMM不应该模拟指令并返回,因为这并不是一个User mode中的合法指令。所以VMM需要跟踪Guest当前是运行在User mode还是Supervisor mode,所以在虚拟状态信息里面也会保存mode。

VMM怎么知道Guest当前的mode呢?当Guest从Supervisor mode返回到User mode时会执行sret指令,而sret指令又是一个privileged指令,所以会通过trap走到VMM,进而VMM可以看到Guest正在执行sret指令,并将自己维护的mode从Supervisor变到User。

虚拟状态信息中保存的另外一个信息是hartid,它代表了CPU核的编号。即使通过privileged指令,也不能直接获取这个信息,VMM需要跟踪当前模拟的是哪个CPU。

实际中,在不同类型的CPU上实现Trap and Emulate虚拟机会有不同的难度。不过RISC-V特别适合实现Trap and Emulate虚拟机,因为RISC-V的设计人员在设计指令集的时候就考虑了Trap and Emulate虚拟机的需求。举个例子,设计人员确保了每个在Supervisor mode下才能执行的privileged指令,如果在User mode执行都会触发trap。你可以通过这种机制来确保VMM针对Guest中的每个privileged指令,都能看到一个trap。

学生提问:Guest操作系统内核中会实际运行任何东西吗?还是说它总是会通过trap走到VMM?

Robert教授:如果你只是执行一个ADD指令,这条指令会直接在硬件上以硬件速度执行。如果你执行一个普通的函数调用,代码的执行也没有任何特殊的地方。所有User代码中合法的指令,以及内核代码中的non-priviledged指令,都是直接以全速在硬件上执行。

学生提问:在Guest操作系统中是不是也有类似的User mode和Kernel mode?

Robert教授:有的。Guest操作系统就是一个未被修改的普通操作系统,所以我们在Guest中运行的就是Linux内核或者XV6内核。而XV6内核知道自己运行在Supervisor mode,从代码的角度来说,内核代码会认为自己运行在Supervisor mode,并执行各种privileged指令,并期望这些指令能工作。当Guest操作系统执行sret指令时,它也知道自己将要进入到User空间。不过在宿主机上,Guest操作系统是运行在User mode,VMM也确保了这里能正常工作。但是从Guest角度来说,自己的内核看起来像是运行在Supervisor mode,自己的用户程序看起来像是运行在User mode。

所以,当Guest执行sret指令从Supervisor mode进入到User mode,因为sret是privileged指令,会通过trap进入到VMM。VMM会更新虚拟状态信息中的mode为User mode,尽管当前的真实mode还是Supervisor mode,因为我们还在执行VMM中的代码。在VMM从trap中返回之前,VMM会将真实的SEPC寄存器设置成自己保存在虚拟状态信息中的虚拟SEPC寄存器。因为当VMM使用自己的sret指令返回到Guest时,它需要将真实的程序计数器设置成Guest操作系统想要的程序计数器值(注,因为稍后Guest代码会在硬件上执行,因此依赖硬件上的程序计数器)。所以在一个非常短的时间内,真实的SEPC寄存器与虚拟的SEPC寄存器值是一样的。同时,当VMM返回到虚拟机时,还需要切换Page table,这个我们稍后会介绍。

Guest中的用户代码,如果是普通的指令,就直接在硬件上执行。当Guest中的用户代码需要执行系统调用时,会通过执行ECALL指令(注,详见6.3,6.4)触发trap,而这个trap会走到VMM中(注,因为ECALL也是个privileged指令)。VMM可以发现当前在虚拟状态信息中记录的mode是User mode,并且发现当前执行的指令是ECALL,之后VMM会更新虚拟状态信息以模拟一个真实的系统调用的trap状态。比如说,它将设置虚拟的SEPC为ECALL指令所在的程序地址(注,执行sret指令时,会将程序计数器的值设置为SEPC寄存器的值。这样,当Guest执行sret指令时,可以从虚拟的SEPC中读到正确的值);将虚拟的mode更新成Supervisor;将虚拟的SCAUSE设置为系统调用;将真实的SEPC设置成虚拟的STVEC寄存器(注,STVEC保存的是trap函数的地址,将真实的SEPC设置成STVEC这样当VMM执行sret指令返回到Guest时,可以返回到Guest的trap handler。Guest执行系统调用以为自己通过trap走到了Guest内核,但是实际上却走到了VMM,这时VMM需要做一些处理,让Guest以及之后Guest的所有privileged指令都看起来好像是Guest真的走到了Guest内核);之后调用sret指令跳转到Guest操作系统的trap handler,也就是STVEC指向的地址。

Last updated