购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3.4 vCPU运行

在QEMU中,vCPU线程创建完成后,便会进入循环状态,等到条件满足时开始运行虚拟机代码。虚拟机执行敏感指令或收到外部中断时,便会触发VM-Exit,进入KVM模块进行处理;部分虚拟机退出时,则需要进一步退出到QEMU中进行处理,如I/O模拟。处理完成后,恢复虚拟机运行。vCPU整体运行流程如图2-13所示,相应的函数调用流程如图2-14所示。

图2-13 vCPU整体运行流程

图2-14 QEMU/KVM vCPU运行函数调用流程
注:①QEMU进行ioctl系统调用,进入KVM;②从ioctl系统调用返回。

接下来遵循上述流程查看QEMU/KVM中vCPU运行是如何实现的。首先qemu_kvm_cpu_thread_fn函数调用kvm_init_vcpu函数在KVM中创建vCPU对应的结构体,得到相应的vCPU文件描述符后便会进入循环。在循环中,先判断CPU能否运行,若不能运行便调用qemu_kvm_wait_io_event函数进而调用qemu_cond_wait函数等待cpu->halt_cond。而QEMU后续在main函数中将会调用vm_start函数直至最终调用qemu_cond_broadcast函数唤醒所有的vCPU线程,相关代码如下。

qemu-4.1.1/cpus.c

vCPU线程被唤醒后将执行kvm_cpu_exec函数运行vCPU。kvm_cpu_exec函数首先调用前述kvm_arch_put_registers函数设置vCPU通用寄存器和段寄存器的值,然后调用kvm_vcpu_ioctl通过vCPU文件描述符发起KVM_RUN ioctl调用陷入KVM中。KVM中相应的处理函数为前述kvm_arch_vcpu_ioctl_run,该函数首先调用vcpu_load函数,该函数最终通过kvm_x86_ops的vcpu_load成员调用前述vmx_vcpu_load函数执行VMPTRLD指令绑定该vCPU对应的VMCS,然后调用vcpu_run函数运行vCPU。vcpu_run函数首先调用kvm_vcpu_running函数判断该vCPU能否运行,若能运行则调用vcpu_enter_guest函数准备进入非根模式。具体代码如下。

linux-4.19.0/arch/x86/kvm/x86.c

vcpu_enter_guest函数首先判断vCPU是否存在待处理的请求,并调用一系列kvm_check_request函数对这些请求进行处理。这些请求可能在各个地方发生,以中断事件注入为例,当虚拟IOAPIC需要注入一个虚拟中断时,会调用kvm_make_request函数发起一个KVM_REQ_EVENT类型的请求,设置kvm_vcpu结构中的requests成员对应的位,并在vcpu_enter_guest函数中进行处理。vcpu_enter_guest函数检查requests发现有KVM_REQ_EVENT类型的请求,则调用inject_pending_event函数注入相应事件,后续中断虚拟化部分将详述该流程。具体代码如下。

linux-4.19.0/arch/x86/kvm/x86.c

阻塞请求处理完成后,vcpu_enter_guest函数通过kvm_x86_ops的prepare_guest_switch成员调用vmx.c中的vmx_prepare_switch_to_guest函数,该函数将部分宿主机的状态保存到VMCS与host_state中,如FS/GS相应的段选择子和段地址等。具体代码如下。

linux-4.19.0/arch/x86/kvm/vmx.c

然后vcpu_enter_guest函数通过kvm_x86_ops的run成员调用vmx.c中的vmx_vcpu_run函数,该函数通过一系列内联汇编指令保存宿主机通用寄存器的值,并加载客户机通用寄存器的值,然后调用VMLAUNCH/VMRESUME指令进入非根模式执行虚拟机代码。具体代码如下。

linux-4.19.0/arch/x86/kvm/vmx.c

当vCPU发生VM-Exit时,vmx_vcpu_run函数保存虚拟机通用寄存器的值并从VMCS中读取虚拟机退出原因保存至vmx->exit_reason中,然后返回至vcpu_enter_guest函数。vcpu_enter_guest函数通过kvm_x86_ops的handle_exit成员调用vmx.c中的vmx_handle_exit函数,该函数根据前面的vmx->exit_reason调用全局数组kvm_vmx_exit_handlers中对应的虚拟机退出处理函数。对于由I/O指令触发的EXIT_REASON_IO_INSTRUCTION类型的VM-Exit,它对应的处理函数handle_io最终调用emulator_pio_in_out函数进行处理。emulator_pio_in_out函数首先调用kernel_pio函数尝试在KVM中处理该PIO请求,若KVM无法处理,它将vcpu->run->exit_reason设置为KVM_EXIT_IO并最终返回0,这导致vcpu_run退出循环并返回至QEMU中进行处理。前面提到vCPU中的run成员主要保存VM-Exit相关信息并通过mmap与QEMU共享,故退出至QEMU后,QEMU将读取kvm_run结构中的exit_reason成员,根据其退出原因进行进一步处理。I/O指令的处理将在4.2节详述。具体代码如下。

linux-4.19.0/arch/x86/kvm/vmx.c

linux-4.19.0/arch/x86/kvm/x86.c N4slt3FKXO830OX7SDqtzk6EX1Jc9jvoiyVSWH/14gn0HoiEnVjTMMf/y7i0v7/Z

点击中间区域
呼出菜单
上一章
目录
下一章
×