1. 程式人生 > >ernel 3.10核心原始碼分析--KVM相關--虛擬機器執行

ernel 3.10核心原始碼分析--KVM相關--虛擬機器執行

1、基本原理
KVM虛擬機器通過字元裝置/dev/kvm的ioctl介面建立和執行,相關原理見之前的文章說明。
虛擬機器的執行通過/dev/kvm裝置ioctl VCPU介面的KVM_RUN指令實現,VMVCPU建立好並完成初始化後,就可以排程該虛擬機器運行了,通常,一個VCPU對應於一個執行緒,虛擬機器執行的本質為排程該虛擬機器相關的VCPU所線上程執行。虛擬機器(VCPU)的執行主要任務是要進行上下文切換,上下文主要包括相關暫存器、APIC狀態、TLB等,通常上下文切換的過程如下:
1、
儲存當前的上下文。
2、使用kvm_vcpu結構體中的上下文資訊,載入到物理CPU中。
3、
執行kvm_x86_ops
中的run_vcpu函式,呼叫硬體相關的指令(VMLAUNCH),進入虛擬機器執行環境中
虛擬機器運行於qemu-kvm的程序上下文中,從硬體的角度看,虛擬機器的執行過程,實質為相關指令的執行過程,虛擬機器編譯後的也就是相應的CPU指令序列,而虛擬機器的指令跟Host機的指令執行過程並沒有太多的差別,最關鍵的差別為“敏感指令”(通常為IO、記憶體等關鍵操作)的執行,這也是虛擬化實現的本質所在,當在虛擬機器中(Guest模式)執行“敏感指令”時,會觸發(由硬體觸發)VM-exit,使當前CPU從Guest模式(non-root模式)切換到root模式,當前CPU的控制權隨之轉交給VMM(Hypervisor,KVM中即Host),由VMM進行相應的處理,處理完成後再次通過應該硬體指令
(VMLAUNCH),重新進入到Guest模式,從而進入虛擬機器執行環境中繼續執行
本文簡單解釋及分析在3.10版本核心程式碼中的相關流程,使用者態qemu-kvm部分暫不包括。


2、大致流程:
Qemu-kvm
可以通過ioctl(KVM_RUN…)使虛擬機器執行,最終進入核心態,由KVM相關核心流程處理,在核心態執行的大致過程如下:
kvm_vcpu_ioctl
 -->
    kvm_arch_vcpu_ioctl_run
具體由核心函式
kvm_arch_vcpu_ioctl_run完成相關工作。主要流程如下:


1、Sigprocmask()遮蔽訊號,防止在此過程中受到訊號的干擾。

2、設定當前VCPU狀態為KVM_MP_STATE_UNINITIALIZED

3、配置APICmmio相關資訊

4、VCPU中儲存的上下文資訊寫入指定位置

5、然後的工作交由__vcpu_run完成

6、__vcpu_run最終呼叫vcpu_enter_guest,該函式實現了進入Guest,並執行Guest OS具體指令的操作。

7、vcpu_enter_guest最終呼叫kvm_x86_ops中的run函式執行。對應於Intel平臺,該函式為vmx_vcpu_run(設定Guest CR3和其他暫存器、EPT/影子頁表相關設定、彙編程式碼VMLAUNCH切換到非根模式,執行Guest目的碼)

8、Guest程式碼執行到敏感指令或因其他原因(比如中斷/異常)VM-Exit退出非根模式,返回到vcpu_enter_guest函式繼續執行。

9、vcpu_enter_guest函式中會判斷VM-Exit原因,並進行相應處理。

10、處理完成後VM-EntryGuest重新執行Guest程式碼,或重新等待下次排程。


3、程式碼分析
kvm_vcpu_ioctl():

點選(此處)摺疊或開啟

  1. /*
  2. * kvm ioctl VCPU指令的入口,傳入的fd為KVM_CREATE_VCPU中返回的fd。
  3. * 主要針對具體的VCPU進行引數設定。如:相關暫存器的讀
  4. * 寫、中斷控制等
  5. */
  6. static long kvm_vcpu_ioctl(struct file *filp,
  7.              unsigned int ioctl, unsigned long arg)
  8. {
  9. struct kvm_vcpu *vcpu = filp->private_data;
  10.     void __user *argp = (void __user *)arg;
  11. int r;
  12. struct kvm_fpu *fpu = NULL;
  13. struct kvm_sregs *kvm_sregs = NULL;
  14. if (vcpu->kvm->mm != current->mm)
  15.         return -EIO;
  16. #if defined(CONFIG_S390) || defined(CONFIG_PPC) || defined(CONFIG_MIPS)
  17.     /*
  18. * Special cases: vcpu ioctls that are asynchronous to vcpu execution,
  19. * so vcpu_load() would break it.
  20. */
  21. if (ioctl == KVM_S390_INTERRUPT || ioctl == KVM_INTERRUPT)
  22.         return kvm_arch_vcpu_ioctl(filp, ioctl, arg);
  23. #endif
  24.     // KVM虛擬機器VCPU資料結構載入物理CPU
  25.     r = vcpu_load(vcpu);
  26. if (r)
  27.         return r;
  28.     switch (ioctl) {
  29.     /* 
  30. * 執行虛擬機器,最終通過執行VMLAUNCH指令進入non root模式,
  31. * 進入虛擬機器執行。當虛擬機器內部執行敏感指令時,由硬
  32. * 件觸發VM-exit,返回到root模式
  33. */
  34.     case KVM_RUN:
  35.         r = -EINVAL;
  36.         // 不能帶引數。
  37. if (arg)
  38. goto out;
  39.         // 執行VCPU(即執行虛擬機器)的入口函式
  40.         r = kvm_arch_vcpu_ioctl_run(vcpu, vcpu->run);
  41.         trace_kvm_userspace_exit(vcpu->run->exit_reason, r);
  42.         break;
  43. ...

kvm_vcpu_ioctl()-->kvm_arch_vcpu_ioctl_run()-->__vcpu_run():

點選(此處)摺疊或開啟

  1. static int __vcpu_run(struct kvm_vcpu *vcpu)
  2. {
  3. int r;
  4.     struct kvm *kvm = vcpu->kvm;
  5.     vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
  6. /*設定vcpu->arch.apic->vapic_page*/
  7.     r = vapic_enter(vcpu);
  8. if (r) {
  9.         srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
  10.         return r;
  11. }
  12.     r = 1;
  13. while (> 0) {
  14. /*檢查狀態*/
  15. if (vcpu->arch.mp_state == KVM_MP_STATE_RUNNABLE &&
  16. !vcpu->arch.apf.halted)
  17. /* 進入Guest模式,最終通過VMLAUNCH指令實現