1.1.4 VCPU生命周期
对于每个虚拟处理器(VCPU),VMM使用一个线程来代表VCPU这个实体。在Guest运转过程中,每个VCPU基本都在如图1-3所示的状态中不断地转换。
图1-3 VCPU生命周期
1)在用户空间准备好后,VCPU所在线程向内核中KVM模块发起一个ioctl请求KVM_RUN,告知内核中的KVM模块,用户空间的操作已经完成,可以切入Guest模式运行Guest了。
2)在进入内核态后,KVM模块将调用CPU提供的虚拟化指令切入Guest模式。如果是首次运行Guest,则使用VMLaunch指令,否则使用VMResume指令。在这个切换过程中,首先,CPU的状态(也就是Host的状态)将会被保存到VMCS中存储Host状态的区域,非CPU自动保存的状态由KVM负责保存。然后,加载存储在VMCS中的Guest的状态到物理CPU,非CPU自动恢复的状态则由KVM负责恢复。
3)物理CPU切入Guest模式,运行Guest指令。当执行Guest指令遇到敏感指令时,CPU将从Guest模式切回到Host模式的ring 0,进入Host内核的KVM模块。在这个切换过程中,首先,CPU的状态(也就是Guest的状态)将会被保存到VMCS中存储Guest状态的区域,然后,加载存储在VMCS中的Host的状态到物理CPU。同样的,非CPU自动保存的状态由KVM模块负责保存。
4)处于内核态的KVM模块从VMCS中读取虚拟机退出原因,尝试在内核中处理。如果内核中可以处理,那么虚拟机就不必再切换到Host模式的用户态了,处理完后,直接快速切回Guest。这种退出也称为轻量级虚拟机退出。
5)如果内核态的KVM模块不能处理虚拟机退出,那么VCPU将再进行一次上下文切换,从Host的内核态切换到Host的用户态,由VMM的用户空间部分进行处理。VMM用户空间处理完毕,再次发起切入Guest模式的指令。在整个虚拟机运行过程中,步骤1~5循环往复。
下面是KVM切入、切出Guest的代码:
commit 6aa8b732ca01c3d7a54e93f4d701b8aabbe60fb7 [PATCH] kvm: userspace interface linux.git/drivers/kvm/vmx.c static int vmx_vcpu_run(struct kvm_vcpu *vcpu, …) { u8 fail; u16 fs_sel, gs_sel, ldt_sel; int fs_gs_ldt_reload_needed; again: … /* Enter guest mode */ "jne launched \n\t" ASM_VMX_VMLAUNCH "\n\t" "jmp kvm_vmx_return \n\t" "launched: " ASM_VMX_VMRESUME "\n\t" ".globl kvm_vmx_return \n\t" "kvm_vmx_return: " /* Save guest registers, load host registers, keep flags */ … if (kvm_handle_exit(kvm_run, vcpu)) { … goto again; } } return 0; }
在从Guest退出时,KVM模块首先调用函数kvm_handle_exit尝试在内核空间处理Guest退出。函数kvm_handle_exit有个约定,如果在内核空间可以成功处理虚拟机退出,或者是因为其他干扰比如外部中断导致虚拟机退出等无须切换到Host的用户空间,则返回1;否则返回0,表示需要求助KVM的用户空间处理虚拟机退出,比如需要KVM用户空间的模拟设备处理外设请求。
如果内核空间成功处理了虚拟机的退出,则函数kvm_handle_exit返回1,在上述代码中即直接跳转到标签again处,然后程序流程会再次切入Guest。如果函数kvm_handle_exit返回0,则函数vmx_vcpu_run结束执行,CPU从内核空间返回到用户空间,以kvmtool为例,其相关代码片段如下:
commit 8d20223edc81c6b199842b36fcd5b0aa1b8d3456 Dump KVM_EXIT_IO details kvmtool.git/kvm.c int main(int argc, char *argv[]) { … for (;;) { kvm__run(kvm); switch (kvm->kvm_run->exit_reason) { case KVM_EXIT_IO: … } … }
根据代码可见,kvmtool发起进入Guest的代码处于一个for的无限循环中。当从KVM内核空间返回用户空间后,kvmtool在用户空间处理Guest的请求,比如调用模拟设备处理I/O请求。在处理完Guest的请求后,重新进入下一轮for循环,kvmtool再次请求KVM模块切入Guest。