Page 54 - 《软件学报》2020年第10期
P. 54
3030 Journal of Software 软件学报 Vol.31, No.10, October 2020
4.2 用于支持控制流切换的内存映射与用户态接口
包括跳板代码页在内的共享内存映射是主动式跨虚拟机通信的关键,本系统在 KVM 的 CPUID 处理函数
中根据初始化时的虚拟机下陷指令,为服务端与客户端虚拟机按顺序预先配置了以下映射.
(1) 客户端 CR3、LSTAR 在服务端虚拟机中的映射.这两种映射的添加是后续跳板代码页执行过程中零下
陷的基础.本系统首先在服务端虚拟机下陷时的 VMCS 中读取到服务端 CR3 的值后,利用软件模拟的方式遍历
服务端虚拟机的扩展页表,翻译得到服务端进程页表在主机物理内存中的 HPA.然后在客户端虚拟机下陷时的
VMCS 中读取到客户端 CR3 的值,再次遍历服务端虚拟机的扩展页表添加从客户端 CR3 到服务端页表 HPA 的
映射.类似地,首先在服务端虚拟机下陷时读取到服务端 LSTAR MSR 的值,利用服务端页表进行地址翻译获取
对应的 GPA,然后在客户端虚拟机下陷时获取客户端 LSTAR MSR 的值作为新的 GVA,接着在服务端页表中建
立从该 GVA 到服务端系统调用代码页 GPA 的映射.
(2) 共享内存.客户端虚拟机中前端模块拦截到的 CUDA API 参数需要转发给服务端虚拟机中的后端模块,
存在大量 API 需要进行主机与设备间的内存拷贝.为了达到跨虚拟机传参的效果,本系统让 KVM 分配一块足够
大的内存作为传参用共享内存,在服务端进程与客户端进程的高地址空间预留了一段起始 GVA,然后在 KVM
中添加双方应用程序页表与扩展页表的映射,使得 CPU 在切换地址空间前后可以通过预留的 GVA 访问到同一
块物理内存.
(3) 过渡用共享栈.在控制流从客户端进程切换到服务端进程的过程中有一段中间过渡期,为了不污染服
务端进程与客户端进程原有的栈结构,本系统在 KVM 中分配了 16 个大小为 4KB 的页内存,映射到了双方进程
的高地址空间中的相同 GVA,保证控制流切换前后栈结构的可用性.
(4) 处理函数的指针数组在客户端虚拟机中的映射.控制流从跳板代码页跳转到服务端进程地址空间时需
要明确目标函数的位置,本系统在 KVM 中为每个服务端虚拟机中维护了一个函数指针数组,在服务端进程初
始化时会下陷到 KVM 将所有可用的处理函数虚拟地址存入该数组.类似地,为了过渡时能够在用户态访问处
理函数的指针数组,本系统也将其映射到了客户端进程的高地址空间中.
(5) 跳板代码页.在上述准备工作完成后,本系统在 KVM 中存放了一份跳板代码页,其对用户态暴露了
delegate_to_server 函数接口,参数包括服务端虚拟机的偏移量和后端处理函数的偏移量,使得客户端虚拟机中
的通信模块通过调用 delegate_to_server 切换到服务端虚拟机中对应的后端处理函数.跳板代码页被映射到了
双方应用程序的高地址空间中的相同 GVA,客户端进程将该地址强制性地转换为函数指针后即可按照函数调
用的方式调用 delegate_to_server 接口.跳板代码的逻辑是:(a) 在被客户端应用程序调用时,先将当前所有的寄
存器压栈来保存上下文,然后保存当前的栈指针并替换为过渡用的临时栈,最后发起 arch_prctl 系统调用替换
FS.base 和 GS.base MSR;(b) 调用 VMFUNC 指令切换到服务端进程的地址空间,此时完成了一次跨虚拟机通
信;(c) 接着检查参数的合法性后从函数指针数组中读取后端处理函数的地址,将地址作为函数指针间接跳转
进入后端处理函数执行;(d) 待后端处理函数返回后,调用 VMFUNC 指令切换回到客户端进程的地址空间;
(e) 发起 arch_prctl 系统调用设置客户端进程的 FS.base 和 GS.base MSR,恢复为客户端进程原生的栈结构,然后
从栈中恢复代理执行前的上下文.
4.3 服务端虚拟机的冻结
完成初始化后,服务端虚拟机的后端模块会从用户态进入冻结状态,此后服务端虚拟机的 CPU 资源可以释
放给其他客户虚拟机使用,内存与 I/O 资源仍要保留供代理执行使用.要从用户态进入冻结状态的原因是在代
理执行时客户端虚拟机的控制流也是从用户态切换而来,如果在内核态冻结则会导致内核的专用栈等数据结
构被污染,代理执行过程中会造成内核的崩溃等严重错误.幸运的是,CPUID 指令在用户态与内核态都会无条件
地触发虚拟机下陷,因此后端模块会在用户态调用 CPUID 传递冻结指示参数下陷到 KVM 中,KVM 在收到冻结
请求后会在 CPUID 处理函数中设置该虚拟机的冻结标志.在每个虚拟机的 vCPU 试图执行 VMRESUME 恢复
运行之前,KVM 会检查该虚拟机的冻结标志,如果为真,则拦截其 vCPU,并主动进入调度以释放 CPU 资源.