在创建完VM和vCPU并且完成了初始化工作之后,就可以通过调度程序调度执行。因为在Linux中,KVM虚拟机作为一个系统线程运行,因此,KVM虚拟机的调度程序实际上也就是Linux的调度程序,具体的调度将在后文qemu部分进行讨论,在当前,KVM的调用是从ioctl的KVM_RUN指令字开始的。
KVM_RUN指令字针对fd_vcpu描述符操作,当vCPU准备完成之后,即可通过该指令让虚拟机运行起来。虚拟机运行的主要任务则是进行上下文切换。上下文切换的内容较多,通常包括通用寄存器、浮点寄存器、段寄存器、控制寄存器、MSR等,在KVM中,还包括APIC状态、TLB等。广州网站建设
通常,进行上下文切换的过程可以归纳为如下步骤。
1)KVM保存自己的上下文。
2)KVM通过使用将kvm_vcpu结构体中的相关上下文加载到物理CPU中。
3)KVM执行kvm_x86_ops中的run_vcpu函数,调用具体的平台相关指令,进入虚拟机运行环境中。
由此可见,上下文切换次数过于频繁会带来不小的性能开销,因此,很有必要对这方面进行优化。和操作系统进行进程切换的思路一样,KVM使用Lazy Save/Restore的方法进行优化。其基本思想是尽量不要对寄存器进行恢复/保存操作,直到必须要这么做的时候,才进行类似的操作。
执行vCPU的请求首先发送到kvm_vcpu_ioctl函数中,然后加载vCPU参数,调用kvm_arch_vcpu_ioctl_run函数进入具体的vCPU运行环节。
1)通过调用sigprocmask函数,保证在vCPU的初始化过程中,不会因为来自其他线程的信号干扰而中断。
2)将vCPU的状态切换为KVM_MP_STATE_UNINITIALIZED。广州网站建设
3)配置APIC和mmio的中断信息。
4)对要进入的虚拟机进行一些关键指令的测试,在测试中主要针对内存读/ 写情况进行测试。
5)将vCPU中保存的上下文信息(寄存器状态等)写入指定的位置。
6)接下来才开始实质性的工作,调用__vcpu_run函数进行后续处理。
__vcpu_run函数的代码如下。
代码5-11 __vcpu_run函数
- (5232) static int __vcpu_run(struct kvm_vcpu *vcpu)
- (5233) {
- (5234) int r;
- (5235) struct kvm *kvm = vcpu->kvm;
- (5236)
- (5237) if (unlikely(vcpu->arch.mp_state == KVM_MP_STATE_SIPI_RECEIVED)) {
- (5238) pr_debug("vcpu %d received sipi with vector # %x\n",
- (5239) vcpu->vcpu_id, vcpu->arch.sipi_vector);
- (5240) kvm_lapic_reset(vcpu);
- (5241) r = kvm_arch_vcpu_reset(vcpu);
- (5242) if (r)
- (5243) return r;
- (5244) vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
- (5245) }
- (5246)
- (5247) vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
- (5248) vapic_enter(vcpu);
- (5249)
- (5250) r = 1;
- (5251) while (r > 0) {
- (5252) if (vcpu->arch.mp_state == KVM_MP_STATE_RUNNABLE)
- (5253) r = vcpu_enter_guest(vcpu);
- (5254) else {
- (5255) srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
- (5256) kvm_vcpu_block(vcpu);
- (5257) vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
- (5258) if (kvm_check_request(KVM_REQ_UNHALT, vcpu))
- (5259) {
- (5260) switch(vcpu->arch.mp_state) {
- (5261) case KVM_MP_STATE_HALTED:
- (5262) vcpu->arch.mp_state =
- (5263) KVM_MP_STATE_RUNNABLE;
- (5264) case KVM_MP_STATE_RUNNABLE:
- (5265) break;
- (5266) case KVM_MP_STATE_SIPI_RECEIVED:
- (5267) default:
- (5268) r = -EINTR;
- (5269) break;
- (5270) }
- (5271) }
- (5272) }
- (5273)
- (5274) if (r <= 0)
- (5275) break;
- (5276)
- (5277) clear_bit(KVM_REQ_PENDING_TIMER, &vcpu->requests);
- (5278) if (kvm_cpu_has_pending_timer(vcpu))
- (5279) kvm_inject_pending_timer_irqs(vcpu);
- (5280)
- (5281) if (dm_request_for_irq_injection(vcpu)) {
- (5282) r = -EINTR;
- (5283) vcpu->run->exit_reason = KVM_EXIT_INTR;
- (5284) ++vcpu->stat.request_irq_exits;
- (5285) }
- (5286) if (signal_pending(current)) {
- (5287) r = -EINTR;
- (5288) vcpu->run->exit_reason = KVM_EXIT_INTR;
- (5289) ++vcpu->stat.signal_exits;
- (5290) }
- (5291) if (need_resched()) {
- (5292) srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
- (5293) kvm_resched(vcpu);
- (5294) vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
- (5295) }
- (5296) }
- (5297)
- (5298) srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
- (5299)
- (5300) vapic_exit(vcpu);
- (5301)
- (5302) return r;
- (5303) }



