分三部分:一是KVM虚拟机创建、二是VCPU创建、三是KVM虚拟机运行

第一部分:

1、基本原理
如之前分析,kvm虚拟机通过对/dev/kvm字符设备的ioctl的System指令KVM_CREATE_VM进行创建。
对虚拟机(VM)来说,kvm结构体是关键,一个虚拟机对应一个kvm结构体,虚拟机的创建过程实质为kvm结构体的创建和初始化过程。
本文简单解释及分析在3.10版本内核代码中的相关流程,用户态qemu-kvm部分暂不包括。

2、大致流程如下:
用户态ioctl(fd,KVM_CREATE_VM,..)
    内核态kvm_dev_ioctl()
        kvm_dev_ioctl_create_vm()
            kvm_create_vm() //实现虚拟机创建的主要函数
                kvm_arch_alloc_vm() // 分配kvm结构体
                kvm_arch_init_vm() // 初始化kvm结构中的架构相关部分,比如中断
                hardware_enable_all() // 使能硬件,架构相关操作
                    hardware_enable_nolock
                        kvm_arch_hardware_enable()
                            kvm_x86_ops->hardware_enable()
                kzalloc() // 分配memslots结构,并初始化为0
                kvm_init_memslots_id() // 初始化内存槽位(slot)的id信息
                kvm_eventfd_init() // 初始化事件通道
                kvm_init_mmu_notifier() // 初始化mmu操作的通知链
                list_add(&kvm->vm_list, &vm_list) // 将新创建的虚拟机的kvm结构,加入到全局链表vm_list中

3、代码分析
kvm结构体:

点击(此处)折叠或打开

  1. /*
  2. * kvm中全局的数据结构,其中包含kvm相关的重要数据信息,包括memslot等
  3. */
  4. struct kvm {
  5. // 用于保护mmu的spin_lock
  6. spinlock_t mmu_lock;
  7. struct mutex slots_lock;
  8. // 指向qemu用户态进程的mm_struct?
  9. struct mm_struct *mm; /* userspace tied to this vm */
  10. /*
  11. * kvm_mem_slot是kvm内存管理相关主要数据结构,用来表示虚拟机GPA和主机HVA之间的
  12. * 映射关系,一个kvm_mem_slot表示一段内存区域(slot)的映射关系,kvm_memslots结构体是
  13. * kvm_mem_slot的封装,其中包含一个kvm_mem_slot的数组,对应于该虚拟机使用的所有
  14. * 内存区域(slot)。
  15. */
  16. struct kvm_memslots *memslots;
  17. struct srcu_struct srcu;
  18. #ifdef CONFIG_KVM_APIC_ARCHITECTURE
  19. u32 bsp_vcpu_id;
  20. #endif
  21. // 虚拟机中包含的VCPU结构体数组,一个VCPU对应一个数组成员。
  22. struct kvm_vcpu *vcpus[KVM_MAX_VCPUS];
  23. // online的vcpu数量
  24. atomic_t online_vcpus;
  25. int last_boosted_vcpu;
  26. struct list_head vm_list;
  27. struct mutex lock;
  28. //虚拟机中包括的IO总线结构体数组,一条总线对应一个kvm_io_bus结构体,如ISA总线、PCI总线。
  29. struct kvm_io_bus *buses[KVM_NR_BUSES];
  30. // 事件通道相关
  31. #ifdef CONFIG_HAVE_KVM_EVENTFD
  32. struct {
  33. spinlock_t lock;
  34. struct list_head items;
  35. struct list_head resampler_list;
  36. struct mutex resampler_lock;
  37. } irqfds;
  38. struct list_head ioeventfds;
  39. #endif
  40. // 虚拟机中的运行时状态信息,比如页表、MMU等状态。
  41. struct kvm_vm_stat stat;
  42. // 架构相关的部分。
  43. struct kvm_arch arch;
  44. // 引用计数
  45. atomic_t users_count;
  46. #ifdef KVM_COALESCED_MMIO_PAGE_OFFSET
  47. struct kvm_coalesced_mmio_ring *coalesced_mmio_ring;
  48. spinlock_t ring_lock;
  49. struct list_head coalesced_zones;
  50. #endif
  51. struct mutex irq_lock;
  52. #ifdef CONFIG_HAVE_KVM_IRQCHIP
  53. /*
  54. * Update side is protected by irq_lock and,
  55. * if configured, irqfds.lock.
  56. */
  57. // irq相关部分
  58. struct kvm_irq_routing_table __rcu *irq_routing;
  59. struct hlist_head mask_notifier_list;
  60. struct hlist_head irq_ack_notifier_list;
  61. #endif
  62. #if defined(CONFIG_MMU_NOTIFIER) && defined(KVM_ARCH_WANT_MMU_NOTIFIER)
  63. // mmu通知链
  64. struct mmu_notifier mmu_notifier;
  65. unsigned long mmu_notifier_seq;
  66. long mmu_notifier_count;
  67. #endif
  68. // dirty TLB数量
  69. long tlbs_dirty;
  70. struct list_head devices;
  71. };

kvm_dev_ioctl()-->kvm_dev_ioctl_create_vm()-->kvm_create_vm():

点击(此处)折叠或打开

  1. /*
  2. * 实现虚拟机创建的主要函数
  3. */
  4. static struct kvm *kvm_create_vm(unsigned long type)
  5. {
  6. int r, i;
  7. /*
  8. * 分配kvm结构体,一个虚拟机对应一个kvm结构,其中包括了虚拟机中的
  9. * 关键系统,比如内存、中断、VCPU、总线等信息,该结构体也是kvm的关键结
  10. * 构体之一
  11. */
  12. struct kvm *kvm = kvm_arch_alloc_vm();
  13. if (!kvm)
  14. return ERR_PTR(-ENOMEM);
  15. // 初始化kvm结构中的架构相关部分,比如中断
  16. r = kvm_arch_init_vm(kvm, type);
  17. if (r)
  18. goto out_err_nodisable;
  19. // 硬件使能,最终调用架构相关的kvm_x86_ops->hardware_enable()接口
  20. r = hardware_enable_all();
  21. if (r)
  22. goto out_err_nodisable;
  23. #ifdef CONFIG_HAVE_KVM_IRQCHIP
  24. INIT_HLIST_HEAD(&kvm->mask_notifier_list);
  25. INIT_HLIST_HEAD(&kvm->irq_ack_notifier_list);
  26. #endif
  27. BUILD_BUG_ON(KVM_MEM_SLOTS_NUM > SHRT_MAX);
  28. r = -ENOMEM;
  29. // 分配memslots结构,并初始化为0
  30. kvm->memslots = kzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
  31. if (!kvm->memslots)
  32. goto out_err_nosrcu;
  33. // 初始化内存槽位(slot)的id信息,便于后续索引
  34. kvm_init_memslots_id(kvm);
  35. if (init_srcu_struct(&kvm->srcu))
  36. goto out_err_nosrcu;
  37. // 初始化虚拟机的bus信息。
  38. for (i = 0; i < KVM_NR_BUSES; i++) {
  39. kvm->buses[i] = kzalloc(sizeof(struct kvm_io_bus),
  40. GFP_KERNEL);
  41. if (!kvm->buses[i])
  42. goto out_err;
  43. }
  44. // 初始化mmu_lock
  45. spin_lock_init(&kvm->mmu_lock);
  46. // 设置虚拟机的mm(mm_struct)为当前进程的mm
  47. kvm->mm = current->mm;
  48. atomic_inc(&kvm->mm->mm_count);
  49. // 初始化事件通道
  50. kvm_eventfd_init(kvm);
  51. mutex_init(&kvm->lock);
  52. mutex_init(&kvm->irq_lock);
  53. mutex_init(&kvm->slots_lock);
  54. atomic_set(&kvm->users_count, 1);
  55. INIT_LIST_HEAD(&kvm->devices);
  56. // 初始化mmu操作的通知链
  57. r = kvm_init_mmu_notifier(kvm);
  58. if (r)
  59. goto out_err;
  60. raw_spin_lock(&kvm_lock);
  61. // 将新创建的虚拟机的kvm结构,加入到全局链表vm_list中
  62. list_add(&kvm->vm_list, &vm_list);
  63. raw_spin_unlock(&kvm_lock);
  64. return kvm;
  65. out_err:
  66. cleanup_srcu_struct(&kvm->srcu);
  67. out_err_nosrcu:
  68. hardware_disable_all();
  69. out_err_nodisable:
  70. for (i = 0; i < KVM_NR_BUSES; i++)
  71. kfree(kvm->buses[i]);
  72. kfree(kvm->memslots);
  73. kvm_arch_free_vm(kvm);
  74. return ERR_PTR(r);
  75. }

第2部分---VCPU创建

1、基本原理
如之前的文章分析,在KVM虚拟化环境中,硬件虚拟化使用VCPU(Virtual CPU)描述符来描述虚拟CPU,VCPU描述符与OS中进程描述符类似,本质是一个结构体kvm_vcpu,其中包含如下信息:

VCPU标识信息,如VCPU的ID号,VCPU属于哪个Guest等。

虚拟寄存器信息,在VT-x的环境中,这些信息包含在VMCS中。

VCPU状态信息,标识白VCPU当前所处的状态(睡眠、运行等),主要供调度器使用。

额外的寄存器/部件信息,主要指未包含在VMCS中的寄存器或CPU部件,比如:浮点寄存器和虚拟的LAPIC等。

其他信息:用户VMM进行优化或存储额外信息的字段,如:存放该VCPU私有数据的指针。

当 VMM 创建虚拟机时,首先要为虚拟机创建 VCPU ,整个虚拟机的运行实际上可以看做 VMM 调度不同的 VCPU 运行 。
虚拟机的VCPU通过ioctl VM指令 K VM_CREATE_VCPU实现 ,实质为创建kvm_vcpu结构体,并进行相关初始化。本文简单分析VCPU创建过程,qemu-kvm用户态实现部分暂不包括。

2、基本流程
kvm_vm_ioctl() // kvm ioctl vm指令入口
    kvm_vm_ioctl_create_vcpu() // 为虚拟机创建VCPU的ioctl调用的入口函数
        kvm_arch_vcpu_create() // 创建vcpu结构,架构相关,对于intel x86来说,最终调用vmx_create_vcpu
        kvm_arch_vcpu_setup() // 设置VCPU结构
        create_vcpu_fd() // 为新创建的vcpu创建对应的fd,以便于后续通过该fd进行ioctl操作
        kvm_arch_vcpu_postcreate() // 架构相关的善后工作,比如再次调用vcpu_load,以及tsc相关处理

3、代码分析
kvm_vcpu结构:

点击(此处)折叠或打开

  1. struct kvm_vcpu {
  2. // 指向此vcpu所属的虚拟机对应的kvm结构
  3. struct kvm *kvm;
  4. #ifdef CONFIG_PREEMPT_NOTIFIERS
  5. struct preempt_notifier preempt_notifier;
  6. #endif
  7. int cpu;
  8. // vcpu id,用于唯一标识该vcpu
  9. int vcpu_id;
  10. int srcu_idx;
  11. int mode;
  12. unsigned long requests;
  13. unsigned long guest_debug;
  14. struct mutex mutex;
  15. // 执行虚拟机对应的kvm_run结构
  16. struct kvm_run *run;
  17. int fpu_active;
  18. int guest_fpu_loaded, guest_xcr0_loaded;
  19. wait_queue_head_t wq;
  20. struct pid *pid;
  21. int sigset_active;
  22. sigset_t sigset;
  23. // vcpu状态信息
  24. struct kvm_vcpu_stat stat;
  25. // mmio相关部分
  26. #ifdef CONFIG_HAS_IOMEM
  27. int mmio_needed;
  28. int mmio_read_completed;
  29. int mmio_is_write;
  30. int mmio_cur_fragment;
  31. int mmio_nr_fragments;
  32. struct kvm_mmio_fragment mmio_fragments[KVM_MAX_MMIO_FRAGMENTS];
  33. #endif
  34. #ifdef CONFIG_KVM_ASYNC_PF
  35. struct {
  36. u32 queued;
  37. struct list_head queue;
  38. struct list_head done;
  39. spinlock_t lock;
  40. } async_pf;
  41. #endif
  42. #ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT
  43. /*
  44. * Cpu relax intercept or pause loop exit optimization
  45. * in_spin_loop: set when a vcpu does a pause loop exit
  46. * or cpu relax intercepted.
  47. * dy_eligible: indicates whether vcpu is eligible for directed yield.
  48. */
  49. struct {
  50. bool in_spin_loop;
  51. bool dy_eligible;
  52. } spin_loop;
  53. #endif
  54. bool preempted;
  55. // 架构相关部分,包括的寄存器、apic、mmu相关等架构相关的内容
  56. struct kvm_vcpu_arch arch;
  57. };

kvm_vm_ioctl()-->kvm_vm_ioctl_create_vcpu():

点击(此处)折叠或打开

  1. /*
  2. * 为虚拟机创建VCPU的ioctl调用的入口函数,本质为创建vcpu结构并初始化,并将其填入kvm结构中。
  3. */
  4. static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
  5. {
  6. int r;
  7. struct kvm_vcpu *vcpu, *v;
  8. // 创建vcpu结构,架构相关,对于intel x86来说,最终调用vmx_create_vcpu
  9. vcpu = kvm_arch_vcpu_create(kvm, id);
  10. if (IS_ERR(vcpu))
  11. return PTR_ERR(vcpu);
  12. preempt_notifier_init(&vcpu->preempt_notifier, &kvm_preempt_ops);
  13. /*
  14. * 设置vcpu结构,主要调用kvm_x86_ops->vcpu_load,KVM虚拟机VCPU数据结构载入物理CPU,
  15. * 并进行虚拟机mmu相关设置,比如进行ept页表的相关初始工作或影子页表
  16. * 相关的设置。
  17. */
  18. r = kvm_arch_vcpu_setup(vcpu);
  19. if (r)
  20. goto vcpu_destroy;
  21. mutex_lock(&kvm->lock);
  22. if (!kvm_vcpu_compatible(vcpu)) {
  23. r = -EINVAL;
  24. goto unlock_vcpu_destroy;
  25. }
  26. if (atomic_read(&kvm->online_vcpus) == KVM_MAX_VCPUS) {
  27. r = -EINVAL;
  28. goto unlock_vcpu_destroy;
  29. }
  30. // 检测分配的vcpu id是否已经存在
  31. kvm_for_each_vcpu(r, v, kvm)
  32. if (v->vcpu_id == id) {
  33. r = -EEXIST;
  34. goto unlock_vcpu_destroy;
  35. }
  36. /*
  37. * kvm->vcpus[]数组包括该vm的所有vcpu,定义为KVM_MAX_VCPUS大小的数组。
  38. * 在kvm结构初始化时,其中所有成员都初始化为0,在vcpu还没有
  39. * 分配之前,如果不为0,那就是bug了。
  40. */
  41. BUG_ON(kvm->vcpus[atomic_read(&kvm->online_vcpus)]);
  42. /* Now it's all set up, let userspace reach it */
  43. // 增加kvm的引用计数
  44. kvm_get_kvm(kvm);
  45. // 为新创建的vcpu创建对应的fd,以便于后续通过该fd进行ioctl操作
  46. r = create_vcpu_fd(vcpu);
  47. if (r < 0) {
  48. kvm_put_kvm(kvm);
  49. goto unlock_vcpu_destroy;
  50. }
  51. // 将新创建的vcpu填入kvm->vcpus[]数组中
  52. kvm->vcpus[atomic_read(&kvm->online_vcpus)] = vcpu;
  53. // 内存屏障,防止同时访问kvm结构时乱序
  54. smp_wmb();
  55. // 增加online vcpu的数量
  56. atomic_inc(&kvm->online_vcpus);
  57. mutex_unlock(&kvm->lock);
  58. // 架构相关的善后工作,比如再次调用vcpu_load,以及tsc相关处理
  59. kvm_arch_vcpu_postcreate(vcpu);
  60. return r;
  61. unlock_vcpu_destroy:
  62. mutex_unlock(&kvm->lock);
  63. vcpu_destroy:
  64. kvm_arch_vcpu_destroy(vcpu);
  65. return r;
  66. }

kvm_vm_ioctl()-->kvm_vm_ioctl_create_vcpu()-->kvm_arch_vcpu_create()-->kvm_x86_ops->vcpu_create()-->vmx_create_vcpu():

点击(此处)折叠或打开

  1. /*
  2. * Intel x86架构中创建并初始化VCPU中架构相关部分
  3. */
  4. static struct kvm_vcpu *vmx_create_vcpu(struct kvm *kvm, unsigned int id)
  5. {
  6. int err;
  7. // 从slab中,分配vcpu_vmx结构体,其中包括VMX技术硬件相关信息。
  8. struct vcpu_vmx *vmx = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL);
  9. int cpu;
  10. if (!vmx)
  11. return ERR_PTR(-ENOMEM);
  12. // 分配vpid,vpid为VCPU的唯一标识。
  13. allocate_vpid(vmx);
  14. // 初始化vmx中的vcpu结构
  15. err = kvm_vcpu_init(&vmx->vcpu, kvm, id);
  16. if (err)
  17. goto free_vcpu;
  18. // 分配Guest的msr寄存器保存区
  19. vmx->guest_msrs = kmalloc(PAGE_SIZE, GFP_KERNEL);
  20. err = -ENOMEM;
  21. if (!vmx->guest_msrs) {
  22. goto uninit_vcpu;
  23. }
  24. vmx->loaded_vmcs = &vmx->vmcs01;
  25. /*
  26. * 分配VMCS结构,该结构用于保存虚拟机和虚拟机监控器的系统编程接口状态。
  27. * 当执行VM exit和VM entry操作时,VT-x自动根据VMCS中的内容完成虚拟机和虚拟机监
  28. * 控器间的系统编程接口状态切换。
  29. */
  30. vmx->loaded_vmcs->vmcs = alloc_vmcs();
  31. if (!vmx->loaded_vmcs->vmcs)
  32. goto free_msrs;
  33. // 是否设置了vmm_exclusive
  34. if (!vmm_exclusive)
  35. // VMXON指令用于开启VMX模式
  36. kvm_cpu_vmxon(__pa(per_cpu(vmxarea, raw_smp_processor_id())));
  37. loaded_vmcs_init(vmx->loaded_vmcs);
  38. if (!vmm_exclusive)
  39. // VMXON指令用于关闭VMX模式
  40. kvm_cpu_vmxoff();
  41. // 当前cpu
  42. cpu = get_cpu();
  43. // KVM虚拟机VCPU数据结构载入物理CPU
  44. vmx_vcpu_load(&vmx->vcpu, cpu);
  45. vmx->vcpu.cpu = cpu;
  46. // 设置vmx相关信息
  47. err = vmx_vcpu_setup(vmx);
  48. vmx_vcpu_put(&vmx->vcpu);
  49. put_cpu();
  50. if (err)
  51. goto free_vmcs;
  52. if (vm_need_virtualize_apic_accesses(kvm)) {
  53. err = alloc_apic_access_page(kvm);
  54. if (err)
  55. goto free_vmcs;
  56. }
  57. // 是否支持EPT
  58. if (enable_ept) {
  59. if (!kvm->arch.ept_identity_map_addr)
  60. kvm->arch.ept_identity_map_addr =
  61. VMX_EPT_IDENTITY_PAGETABLE_ADDR;
  62. err = -ENOMEM;
  63. // 分配identity页表
  64. if (alloc_identity_pagetable(kvm) != 0)
  65. goto free_vmcs;
  66. // 初始化identity页表
  67. if (!init_rmode_identity_map(kvm))
  68. goto free_vmcs;
  69. }
  70. vmx->nested.current_vmptr = -1ull;
  71. vmx->nested.current_vmcs12 = NULL;
  72. return &vmx->vcpu;
  73. free_vmcs:
  74. free_loaded_vmcs(vmx->loaded_vmcs);
  75. free_msrs:
  76. kfree(vmx->guest_msrs);
  77. uninit_vcpu:
  78. kvm_vcpu_uninit(&vmx->vcpu);
  79. free_vcpu:
  80. free_vpid(vmx);
  81. kmem_cache_free(kvm_vcpu_cache, vmx);
  82. return ERR_PTR(err);
  83. }

kvm_vm_ioctl()-->kvm_vm_ioctl_create_vcpu()-->kvm_arch_vcpu_setup():

点击(此处)折叠或打开

  1. int kvm_arch_vcpu_setup(struct kvm_vcpu *vcpu)
  2. {
  3. int r;
  4. vcpu->arch.mtrr_state.have_fixed = 1;
  5. // KVM虚拟机VCPU数据结构载入物理CPU
  6. r = vcpu_load(vcpu);
  7. if (r)
  8. return r;
  9. // vcpu重置,包括相关寄存器、时钟、pmu等,最终调用vmx_vcpu_reset
  10. kvm_vcpu_reset(vcpu);
  11. /*
  12. * 进行虚拟机mmu相关设置,比如进行ept页表的相关初始工作或影子页表
  13. * 相关的设置。
  14. */
  15. r = kvm_mmu_setup(vcpu);
  16. vcpu_put(vcpu);
  17. return r;
  18. }

kvm_vm_ioctl()-->kvm_vm_ioctl_create_vcpu()-->kvm_arch_vcpu_setup()-->kvm_mmu_setup()-->init_kvm_mmu():

点击(此处)折叠或打开

  1. static int init_kvm_mmu(struct kvm_vcpu *vcpu)
  2. {
  3. // NPT(Nested page table,AMD x86硬件提供的内存虚拟化技术,相当于Intel中的EPT技术)相关初始化
  4. if (mmu_is_nested(vcpu))
  5. return init_kvm_nested_mmu(vcpu);
  6. /*
  7. * EPT(Extended page table,Intel x86硬件提供的内存虚拟化技术)相关初始化
  8. * 主要是设置一些函数指针,其中比较重要的如缺页异常处理函数
  9. */
  10. else if (tdp_enabled)
  11. return init_kvm_tdp_mmu(vcpu);
  12. // 影子页表(软件实现内存虚拟化技术)相关初始化
  13. else
  14. return init_kvm_softmmu(vcpu);
  15. }

第3部分---KVM_SET_USER_MEMORY_REGION流程

1、基本原理
如之前分析,kvm虚拟机实际运行于qemu-kvm的进程上下文中,因此,需要建立虚拟机的物理内存空间(GPA)与qemu-kvm进程的虚拟地址空间(HVA)的映射关系。
虚拟机的物理地址空间实际也是不连续的,分成不同的内存区域(slot),因为物理地址空间中通常还包括BIOS、MMIO、显存、ISA保留等部分。

qemu-kvm通过ioctl vm指令KVM_SET_USER_MEMORY_REGION来为虚拟机设置内存。主要建立guest物理地址空间中的内存区域与qemu-kvm虚拟地址空间中的内存区域的映射,从而建立其从GVA到HVA的对应关系,该对应关系主要通过kvm_mem_slot结构体保存,所以实质为设置kvm_mem_slot结构体。
本文简介ioctl vm指令KVM_SET_USER_MEMORY_REGION在内核中的执行流程,qemu-kvm用户态部分暂不包括。

2、基本流程
ioctl vm指令KVM_SET_USER_MEMORY_REGION在内核主要执行流程如下:
kvm_vm_ioctl()
    kvm_vm_ioctl_set_memory_region()
        kvm_set_memory_region()
            __kvm_set_memory_region()
                kvm_iommu_unmap_pages() // 原来的slot需要删除,所以需要unmap掉相应的内存区域
                install_new_memslots() //将new分配的memslot写入kvm->memslots[]数组中
                kvm_free_physmem_slot() // 释放旧内存区域相应的物理内存(HPA)

3、代码分析
kvm_mem_slot结构:

点击(此处)折叠或打开

  1. /*
  2. * 由于GPA不能直接用于物理 MMU 进行寻址,所以需要将GPA转换为HVA,
  3. * kvm中利用 kvm_memory_slot 数据结构来记录每一个地址区间(Guest中的物理
  4. * 地址区间)中GPA与HVA的映射关系
  5. */
  6. struct kvm_memory_slot {
  7. // 虚拟机物理地址(即GPA)对应的页框号
  8. gfn_t base_gfn;
  9. // 当前slot中包含的page数
  10. unsigned long npages;
  11. // 脏页位图
  12. unsigned long *dirty_bitmap;
  13. // 架构相关的部分
  14. struct kvm_arch_memory_slot arch;
  15. /*
  16. * GPA对应的Host虚拟地址(HVA),由于虚拟机都运行在qemu的地址空间中
  17. * 而qemu是用户态程序,所以通常使用根模式下用户地址空间。
  18. */
  19. unsigned long userspace_addr;
  20. u32 flags;
  21. short id;
  22. };

kvm_vm_ioctl():

点击(此处)折叠或打开

  1. /*
  2. * kvm ioctl vm指令的入口,传入的fd为KVM_CREATE_VM中返回的fd。
  3. * 主要用于针对VM虚拟机进行控制,如:内存设置、创建VCPU等。
  4. */
  5. static long kvm_vm_ioctl(struct file *filp,
  6. unsigned int ioctl, unsigned long arg)
  7. {
  8. struct kvm *kvm = filp->private_data;
  9. void __user *argp = (void __user *)arg;
  10. int r;
  11. if (kvm->mm != current->mm)
  12. return -EIO;
  13. switch (ioctl) {
  14. // 创建VCPU
  15. case KVM_CREATE_VCPU:
  16. r = kvm_vm_ioctl_create_vcpu(kvm, arg);
  17. break;
  18. // 建立guest物理地址空间中的内存区域与qemu-kvm虚拟地址空间中的内存区域的映射
  19. case KVM_SET_USER_MEMORY_REGION: {
  20. // 存放内存区域信息的结构体,该内存区域从qemu-kvm进程的用户地址空间中分配
  21. struct kvm_userspace_memory_region kvm_userspace_mem;
  22. r = -EFAULT;
  23. // 从用户态拷贝相应数据到内核态,入参argp指向用户态地址
  24. if (copy_from_user(&kvm_userspace_mem, argp,
  25. sizeof kvm_userspace_mem))
  26. goto out;
  27. // 进入实际处理流程
  28. r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem);
  29. break;
  30. }
  31. ...

kvm_vm_ioctl()-->kvm_vm_ioctl_set_memory_region()-->kvm_set_memory_region()-->__kvm_set_memory_region()

点击(此处)折叠或打开

  1. /*
  2. * 建立guest物理地址空间中的内存区域与qemu-kvm虚拟地址空间中的内存区域的映射
  3. * 相应信息由uerspace_memory_region参数传入,而其源头来自于用户态qemu-kvm。每次
  4. * 调用设置一个内存区间。内存区域可以不连续(实际的物理内存区域也经常不连
  5. * 续,因为有可能有保留内存)
  6. */
  7. int __kvm_set_memory_region(struct kvm *kvm,
  8. struct kvm_userspace_memory_region *mem)
  9. {
  10. int r;
  11. gfn_t base_gfn;
  12. unsigned long npages;
  13. struct kvm_memory_slot *slot;
  14. struct kvm_memory_slot old, new;
  15. struct kvm_memslots *slots = NULL, *old_memslots;
  16. enum kvm_mr_change change;
  17. // 标记检查
  18. r = check_memory_region_flags(mem);
  19. if (r)
  20. goto out;
  21. r = -EINVAL;
  22. /* General sanity checks */
  23. // 合规检查,防止用户态恶意传参,导致安全漏洞
  24. if (mem->memory_size & (PAGE_SIZE - 1))
  25. goto out;
  26. if (mem->guest_phys_addr & (PAGE_SIZE - 1))
  27. goto out;
  28. /* We can read the guest memory with __xxx_user() later on. */
  29. if ((mem->slot < KVM_USER_MEM_SLOTS) &&
  30. ((mem->userspace_addr & (PAGE_SIZE - 1)) ||
  31. !access_ok(VERIFY_WRITE,
  32. (void __user *)(unsigned long)mem->userspace_addr,
  33. mem->memory_size)))
  34. goto out;
  35. if (mem->slot >= KVM_MEM_SLOTS_NUM)
  36. goto out;
  37. if (mem->guest_phys_addr + mem->memory_size < mem->guest_phys_addr)
  38. goto out;
  39. // 将kvm_userspace_memory_region->slot转换为kvm_mem_slot结构,该结构从kvm->memslots获取
  40. slot = id_to_memslot(kvm->memslots, mem->slot);
  41. // 内存区域起始位置在Guest物理地址空间中的页框号
  42. base_gfn = mem->guest_phys_addr >> PAGE_SHIFT;
  43. // 内存区域大小转换为page单位
  44. npages = mem->memory_size >> PAGE_SHIFT;
  45. r = -EINVAL;
  46. if (npages > KVM_MEM_MAX_NR_PAGES)
  47. goto out;
  48. if (!npages)
  49. mem->flags &= ~KVM_MEM_LOG_DIRTY_PAGES;
  50. new = old = *slot;
  51. new.id = mem->slot;
  52. new.base_gfn = base_gfn;
  53. new.npages = npages;
  54. new.flags = mem->flags;
  55. r = -EINVAL;
  56. if (npages) {
  57. // 判断是否需新创建内存区域
  58. if (!old.npages)
  59. change = KVM_MR_CREATE;
  60. // 判断是否修改现有的内存区域
  61. else { /* Modify an existing slot. */
  62. // 修改的区域的HVA不同或者大小不同或者flag中的
  63. // KVM_MEM_READONLY标记不同,直接退出。
  64. if ((mem->userspace_addr != old.userspace_addr) ||
  65. (npages != old.npages) ||
  66. ((new.flags ^ old.flags) & KVM_MEM_READONLY))
  67. goto out;
  68. /*
  69. * 走到这,说明被修改的区域HVA和大小都是相同的
  70. * 判断区域起始的GFN是否相同,如果是,则说明需
  71. * 要在Guest物理地址空间中move这段区域,设置KVM_MR_MOVE标记
  72. */
  73. if (base_gfn != old.base_gfn)
  74. change = KVM_MR_MOVE;
  75. // 如果仅仅是flag不同,则仅修改标记,设置KVM_MR_FLAGS_ONLY标记
  76. else if (new.flags != old.flags)
  77. change = KVM_MR_FLAGS_ONLY;
  78. // 否则,啥也不干
  79. else { /* Nothing to change. */
  80. r = 0;
  81. goto out;
  82. }
  83. }
  84. } else if (old.npages) {/*如果新设置的区域大小为0,而老的区域大小不为0,则表示需要删除原有区域。*/
  85. change = KVM_MR_DELETE;
  86. } else /* Modify a non-existent slot: disallowed. */
  87. goto out;
  88. if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
  89. /* Check for overlaps */
  90. r = -EEXIST;
  91. // 检查现有区域中是否重叠的
  92. kvm_for_each_memslot(slot, kvm->memslots) {
  93. if ((slot->id >= KVM_USER_MEM_SLOTS) ||
  94. (slot->id == mem->slot))
  95. continue;
  96. if (!((base_gfn + npages <= slot->base_gfn) ||
  97. (base_gfn >= slot->base_gfn + slot->npages)))
  98. goto out;
  99. }
  100. }
  101. /* Free page dirty bitmap if unneeded */
  102. if (!(new.flags & KVM_MEM_LOG_DIRTY_PAGES))
  103. new.dirty_bitmap = NULL;
  104. r = -ENOMEM;
  105. // 如果需要创建新区域
  106. if (change == KVM_MR_CREATE) {
  107. new.userspace_addr = mem->userspace_addr;
  108. // 设置新的内存区域架构相关部分
  109. if (kvm_arch_create_memslot(&new, npages))
  110. goto out_free;
  111. }
  112. /* Allocate page dirty bitmap if needed */
  113. if ((new.flags & KVM_MEM_LOG_DIRTY_PAGES) && !new.dirty_bitmap) {
  114. if (kvm_create_dirty_bitmap(&new) < 0)
  115. goto out_free;
  116. }
  117. // 如果删除或move内存区域
  118. if ((change == KVM_MR_DELETE) || (change == KVM_MR_MOVE)) {
  119. r = -ENOMEM;
  120. // 复制kvm->memslots的副本
  121. slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),
  122. GFP_KERNEL);
  123. if (!slots)
  124. goto out_free;
  125. slot = id_to_memslot(slots, mem->slot);
  126. slot->flags |= KVM_MEMSLOT_INVALID;
  127. // 安装新memslots,返回旧的memslots
  128. old_memslots = install_new_memslots(kvm, slots, NULL);
  129. /* slot was deleted or moved, clear iommu mapping */
  130. // 原来的slot需要删除,所以需要unmap掉相应的内存区域
  131. kvm_iommu_unmap_pages(kvm, &old);
  132. /* From this point no new shadow pages pointing to a deleted,
  133. * or moved, memslot will be created.
  134. *
  135. * validation of sp->gfn happens in:
  136. *     - gfn_to_hva (kvm_read_guest, gfn_to_pfn)
  137. *     - kvm_is_visible_gfn (mmu_check_roots)
  138. */
  139. // flush影子页表中的条目
  140. kvm_arch_flush_shadow_memslot(kvm, slot);
  141. slots = old_memslots;
  142. }
  143. // 处理private memory slots,对其分配用户态地址,即HVA
  144. r = kvm_arch_prepare_memory_region(kvm, &new, mem, change);
  145. if (r)
  146. goto out_slots;
  147. r = -ENOMEM;
  148. /*
  149. * We can re-use the old_memslots from above, the only difference
  150. * from the currently installed memslots is the invalid flag. This
  151. * will get overwritten by update_memslots anyway.
  152. */
  153. if (!slots) {
  154. slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),
  155. GFP_KERNEL);
  156. if (!slots)
  157. goto out_free;
  158. }
  159. /*
  160. * IOMMU mapping: New slots need to be mapped. Old slots need to be
  161. * un-mapped and re-mapped if their base changes. Since base change
  162. * unmapping is handled above with slot deletion, mapping alone is
  163. * needed here. Anything else the iommu might care about for existing
  164. * slots (size changes, userspace addr changes and read-only flag
  165. * changes) is disallowed above, so any other attribute changes getting
  166. * here can be skipped.
  167. */
  168. if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
  169. r = kvm_iommu_map_pages(kvm, &new);
  170. if (r)
  171. goto out_slots;
  172. }
  173. /* actual memory is freed via old in kvm_free_physmem_slot below */
  174. if (change == KVM_MR_DELETE) {
  175. new.dirty_bitmap = NULL;
  176. memset(&new.arch, 0, sizeof(new.arch));
  177. }
  178. //将new分配的memslot写入kvm->memslots[]数组中
  179. old_memslots = install_new_memslots(kvm, slots, &new);
  180. kvm_arch_commit_memory_region(kvm, mem, &old, change);
  181. // 释放旧内存区域相应的物理内存(HPA)
  182. kvm_free_physmem_slot(&old, &new);
  183. kfree(old_memslots);
  184. return 0;
  185. out_slots:
  186. kfree(slots);
  187. out_free:
  188. kvm_free_physmem_slot(&new, &old);
  189. out:
  190. return r;
  191. }

第4部分---虚拟机运行

1、基本原理

KVM虚拟机通过字符设备/dev/kvm的ioctl接口创建和运行,相关原理见之前的文章说明。
虚拟机的运行通过/dev/kvm设备ioctl VCPU接口的KVM_RUN指令实现,在VM和VCPU创建好并完成初始化后,就可以调度该虚拟机运行了,通常,一个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 (怎么在VCPU INIT里面(kvm_arch_vcpu_init))

3、    配置APIC和mmio相关信息 (只在kvm_arch_vcpu_init中发现了APIC的创建,没有找到MMIO??)

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-Entry到Guest重新执行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():

  44. static int __vcpu_run(struct kvm_vcpu *vcpu)
  45. {
  46. int r;
  47. struct kvm *kvm = vcpu->kvm;
  48. vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
  49. /*设置vcpu->arch.apic->vapic_page*/
  50. r = vapic_enter(vcpu);
  51. if (r) {
  52. srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
  53. return r;
  54. }
  55. r = 1;
  56. while (r > 0) {
  57. /*检查状态*/
  58. if (vcpu->arch.mp_state == KVM_MP_STATE_RUNNABLE &&
  59. !vcpu->arch.apf.halted)
  60. /* 进入Guest模式,最终通过VMLAUNCH指令实现*/
  61. r = vcpu_enter_guest(vcpu);
  62. else {/*什么情况下会走到这里?*/
  63. srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
  64. /*阻塞VCPU,其实就是schddule()调度出去,但在有特殊情况时(比如有挂起的定时器或信号时),不进行调度而直接退出*/
  65. kvm_vcpu_block(vcpu);
  66. vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
  67. if (kvm_check_request(KVM_REQ_UNHALT, vcpu)) {
  68. kvm_apic_accept_events(vcpu);
  69. switch(vcpu->arch.mp_state) {
  70. case KVM_MP_STATE_HALTED:
  71. vcpu->arch.pv.pv_unhalted = false;
  72. vcpu->arch.mp_state =
  73. KVM_MP_STATE_RUNNABLE;
  74. case KVM_MP_STATE_RUNNABLE:
  75. vcpu->arch.apf.halted = false;
  76. break;
  77. case KVM_MP_STATE_INIT_RECEIVED:
  78. break;
  79. default:
  80. r = -EINTR;
  81. break;
  82. }
  83. }
  84. }
  85. if (r <= 0)
  86. break;
  87. clear_bit(KVM_REQ_PENDING_TIMER, &vcpu->requests);
  88. if (kvm_cpu_has_pending_timer(vcpu))
  89. kvm_inject_pending_timer_irqs(vcpu);
  90. if (dm_request_for_irq_injection(vcpu)) {
  91. r = -EINTR;
  92. vcpu->run->exit_reason = KVM_EXIT_INTR;
  93. ++vcpu->stat.request_irq_exits;
  94. }
  95. kvm_check_async_pf_completion(vcpu);
  96. if (signal_pending(current)) {
  97. r = -EINTR;
  98. vcpu->run->exit_reason = KVM_EXIT_INTR;
  99. ++vcpu->stat.signal_exits;
  100. }
  101. /*这是kvm中的一个调度时机点,即选择新VCPU运行的时机点*/
  102. if (need_resched()) {
  103. srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
  104. kvm_resched(vcpu);
  105. vcpu->srcu_idx = srcu_read_lock(&kvm->srcu);
  106. }
  107. }
  108. srcu_read_unlock(&kvm->srcu, vcpu->srcu_idx);
  109. vapic_exit(vcpu);
  110. return r;
  111. }

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

  112. /* 进入Guest模式,最终通过VMLAUNCH指令实现*/
  113. static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
  114. {
  115. int r;
  116. bool req_int_win = !irqchip_in_kernel(vcpu->kvm) &&
  117. vcpu->run->request_interrupt_window;
  118. bool req_immediate_exit = false;
  119. /*进入Guest模式前先处理相关挂起的请求*/
  120. if (vcpu->requests) {
  121. /*卸载MMU*/
  122. if (kvm_check_request(KVM_REQ_MMU_RELOAD, vcpu))
  123. kvm_mmu_unload(vcpu);
  124. /*定时器迁移*/
  125. if (kvm_check_request(KVM_REQ_MIGRATE_TIMER, vcpu))
  126. __kvm_migrate_timers(vcpu);
  127. /*主时钟更新*/
  128. if (kvm_check_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu))
  129. kvm_gen_update_masterclock(vcpu->kvm);
  130. /*全局时钟更新*/
  131. if (kvm_check_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu))
  132. kvm_gen_kvmclock_update(vcpu);
  133. /*虚拟机时钟更新*/
  134. if (kvm_check_request(KVM_REQ_CLOCK_UPDATE, vcpu)) {
  135. r = kvm_guest_time_update(vcpu);
  136. if (unlikely(r))
  137. goto out;
  138. }
  139. /*更新mmu*/
  140. if (kvm_check_request(KVM_REQ_MMU_SYNC, vcpu))
  141. kvm_mmu_sync_roots(vcpu);
  142. /*刷新TLB*/
  143. if (kvm_check_request(KVM_REQ_TLB_FLUSH, vcpu))
  144. kvm_x86_ops->tlb_flush(vcpu);
  145. if (kvm_check_request(KVM_REQ_REPORT_TPR_ACCESS, vcpu)) {
  146. vcpu->run->exit_reason = KVM_EXIT_TPR_ACCESS;
  147. r = 0;
  148. goto out;
  149. }
  150. if (kvm_check_request(KVM_REQ_TRIPLE_FAULT, vcpu)) {
  151. vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
  152. r = 0;
  153. goto out;
  154. }
  155. if (kvm_check_request(KVM_REQ_DEACTIVATE_FPU, vcpu)) {
  156. vcpu->fpu_active = 0;
  157. kvm_x86_ops->fpu_deactivate(vcpu);
  158. }
  159. if (kvm_check_request(KVM_REQ_APF_HALT, vcpu)) {
  160. /* Page is swapped out. Do synthetic halt */
  161. vcpu->arch.apf.halted = true;
  162. r = 1;
  163. goto out;
  164. }
  165. if (kvm_check_request(KVM_REQ_STEAL_UPDATE, vcpu))
  166. record_steal_time(vcpu);
  167. if (kvm_check_request(KVM_REQ_NMI, vcpu))
  168. process_nmi(vcpu);
  169. if (kvm_check_request(KVM_REQ_PMU, vcpu))
  170. kvm_handle_pmu_event(vcpu);
  171. if (kvm_check_request(KVM_REQ_PMI, vcpu))
  172. kvm_deliver_pmi(vcpu);
  173. if (kvm_check_request(KVM_REQ_SCAN_IOAPIC, vcpu))
  174. vcpu_scan_ioapic(vcpu);
  175. }
  176. // 检查是否有事件请求
  177. if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) {
  178. kvm_apic_accept_events(vcpu);
  179. if (vcpu->arch.mp_state == KVM_MP_STATE_INIT_RECEIVED) {
  180. r = 1;
  181. goto out;
  182. }
  183. // 注入阻塞的事件,中断,异常和nmi等
  184. inject_pending_event(vcpu);
  185. /* enable NMI/IRQ window open exits if needed */
  186. /*
  187. * 使能NMI/IRQ window,参见Intel64 System Programming Guide 25.3节(P366)
  188. * 当使能了interrupt-window exiting或NMI-window exiting(由VMCS中相关字段控制),
  189. * 表示在刚进入虚拟机后,就会立刻因为有pending或注入的中断导致VM-exit
  190. */
  191. if (vcpu->arch.nmi_pending)
  192. req_immediate_exit =
  193. kvm_x86_ops->enable_nmi_window(vcpu) != 0;
  194. else if (kvm_cpu_has_injectable_intr(vcpu) || req_int_win)
  195. req_immediate_exit =
  196. kvm_x86_ops->enable_irq_window(vcpu) != 0;
  197. if (kvm_lapic_enabled(vcpu)) {
  198. /*
  199. * Update architecture specific hints for APIC
  200. * virtual interrupt delivery.
  201. */
  202. if (kvm_x86_ops->hwapic_irr_update)
  203. kvm_x86_ops->hwapic_irr_update(vcpu,
  204. kvm_lapic_find_highest_irr(vcpu));
  205. update_cr8_intercept(vcpu);
  206. kvm_lapic_sync_to_vapic(vcpu);
  207. }
  208. }
  209. // 装载MMU,待深入分析
  210. r = kvm_mmu_reload(vcpu);
  211. if (unlikely(r)) {
  212. goto cancel_injection;
  213. }
  214. preempt_disable();
  215. // 进入Guest前期准备,架构相关
  216. kvm_x86_ops->prepare_guest_switch(vcpu);
  217. if (vcpu->fpu_active)
  218. kvm_load_guest_fpu(vcpu);
  219. kvm_load_guest_xcr0(vcpu);
  220. vcpu->mode = IN_GUEST_MODE;
  221. /* We should set ->mode before check ->requests,
  222. * see the comment in make_all_cpus_request.
  223. */
  224. smp_mb();
  225. local_irq_disable();
  226. /*
  227. * 如果VCPU处于EXITING_GUEST_MODE或者vcpu->requests(?)或者需要调度或者
  228. * 有挂起的信号,则放弃
  229. */
  230. if (vcpu->mode == EXITING_GUEST_MODE || vcpu->requests
  231. || need_resched() || signal_pending(current)) {
  232. vcpu->mode = OUTSIDE_GUEST_MODE;
  233. smp_wmb();
  234. local_irq_enable();
  235. preempt_enable();
  236. r = 1;
  237. goto cancel_injection;
  238. }
  239. srcu_read_unlock(&vcpu->kvm->srcu, vcpu->srcu_idx);
  240. // req_immediate_exit在前面使能NMI/IRQ window失败时设置,此时需要立即退出,触发重新调度
  241. if (req_immediate_exit)
  242. smp_send_reschedule(vcpu->cpu);
  243. // 计算虚拟机的enter时间
  244. kvm_guest_enter();
  245. // 调试相关
  246. if (unlikely(vcpu->arch.switch_db_regs)) {
  247. set_debugreg(0, 7);
  248. set_debugreg(vcpu->arch.eff_db[0], 0);
  249. set_debugreg(vcpu->arch.eff_db[1], 1);
  250. set_debugreg(vcpu->arch.eff_db[2], 2);
  251. set_debugreg(vcpu->arch.eff_db[3], 3);
  252. }
  253. trace_kvm_entry(vcpu->vcpu_id);
  254. // 调用架构相关的run接口(vmx_vcpu_run),进入Guest模式
  255. kvm_x86_ops->run(vcpu);
  256. // 此处开始,说明已经发生了VM-exit,退出了Guest模式
  257. /*
  258. * If the guest has used debug registers, at least dr7
  259. * will be disabled while returning to the host.
  260. * If we don't have active breakpoints in the host, we don't
  261. * care about the messed up debug address registers. But if
  262. * we have some of them active, restore the old state.
  263. */
  264. if (hw_breakpoint_active())
  265. hw_breakpoint_restore();
  266. /*记录Guest退出前的TSC时钟*/
  267. vcpu->arch.last_guest_tsc = kvm_x86_ops->read_l1_tsc(vcpu,
  268. native_read_tsc());
  269. // 设置模式
  270. vcpu->mode = OUTSIDE_GUEST_MODE;
  271. smp_wmb();
  272. /* Interrupt is enabled by handle_external_intr() */
  273. kvm_x86_ops->handle_external_intr(vcpu);
  274. ++vcpu->stat.exits;
  275. /*
  276. * We must have an instruction between local_irq_enable() and
  277. * kvm_guest_exit(), so the timer interrupt isn't delayed by
  278. * the interrupt shadow. The stat.exits increment will do nicely.
  279. * But we need to prevent reordering, hence this barrier():
  280. */
  281. barrier();
  282. // 计算虚拟机的退出时间,其中还开中断了?
  283. kvm_guest_exit();
  284. preempt_enable();
  285. vcpu->srcu_idx = srcu_read_lock(&vcpu->kvm->srcu);
  286. /*
  287. * Profile KVM exit RIPs:
  288. */
  289. // Profile(采样计数,用于性能分析和调优)相关
  290. if (unlikely(prof_on == KVM_PROFILING)) {
  291. unsigned long rip = kvm_rip_read(vcpu);
  292. profile_hit(KVM_PROFILING, (void *)rip);
  293. }
  294. if (unlikely(vcpu->arch.tsc_always_catchup))
  295. kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
  296. if (vcpu->arch.apic_attention)
  297. kvm_lapic_sync_from_vapic(vcpu);
  298. /*
  299. * 调用vmx_handle_exit()处理虚拟机异常,异常原因及其它关键信息
  300. * 已经在之前获取。
  301. */
  302. r = kvm_x86_ops->handle_exit(vcpu);
  303. return r;
  304. cancel_injection:
  305. kvm_x86_ops->cancel_injection(vcpu);
  306. if (unlikely(vcpu->arch.apic_attention))
  307. kvm_lapic_sync_from_vapic(vcpu);
  308. out:
  309. return r;
  310. }

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

  311. /*
  312. * 运行虚拟机,进入Guest模式,即non root模式
  313. */
  314. static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu)
  315. {
  316. struct vcpu_vmx *vmx = to_vmx(vcpu);
  317. unsigned long debugctlmsr;
  318. /* Record the guest's net vcpu time for enforced NMI injections. */
  319. // nmi注入?跟nmi_watchdog相关?
  320. if (unlikely(!cpu_has_virtual_nmis() && vmx->soft_vnmi_blocked))
  321. vmx->entry_time = ktime_get();
  322. /* Don't enter VMX if guest state is invalid, let the exit handler
  323. start emulation until we arrive back to a valid state */
  324. if (vmx->emulation_required)
  325. return;
  326. if (vmx->nested.sync_shadow_vmcs) {
  327. copy_vmcs12_to_shadow(vmx);
  328. vmx->nested.sync_shadow_vmcs = false;
  329. }
  330. // 写入Guest的RSP寄存器信息至VMCS相关位置中
  331. if (test_bit(VCPU_REGS_RSP, (unsigned long *)&vcpu->arch.regs_dirty))
  332. vmcs_writel(GUEST_RSP, vcpu->arch.regs[VCPU_REGS_RSP]);
  333. // 写入Guest的RIP寄存器信息至VMCS相关位置中
  334. if (test_bit(VCPU_REGS_RIP, (unsigned long *)&vcpu->arch.regs_dirty))
  335. vmcs_writel(GUEST_RIP, vcpu->arch.regs[VCPU_REGS_RIP]);
  336. /* When single-stepping over STI and MOV SS, we must clear the
  337. * corresponding interruptibility bits in the guest state. Otherwise
  338. * vmentry fails as it then expects bit 14 (BS) in pending debug
  339. * exceptions being set, but that's not correct for the guest debugging
  340. * case. */
  341. // 单步调试时,需要禁用Guest中断
  342. if (vcpu->guest_debug & KVM_GUESTDBG_SINGLESTEP)
  343. vmx_set_interrupt_shadow(vcpu, 0);
  344. atomic_switch_perf_msrs(vmx);
  345. debugctlmsr = get_debugctlmsr();
  346. // vmx->__launched用于判断当前VCPU是否已经VMLAUNCH了
  347. vmx->__launched = vmx->loaded_vmcs->launched;
  348. // 执行VMLAUNCH指令进入Guest模式,虚拟机开始运行
  349. asm(
  350. /* Store host registers */
  351. /*将相关寄存器压栈*/
  352. "push %%" _ASM_DX "; push %%" _ASM_BP ";"/*BP压栈*/
  353. /*为guest的rcx寄存器保留个位置,所以这里压两次栈*/
  354. "push %%" _ASM_CX " \n\t" /* placeholder for guest rcx */
  355. "push %%" _ASM_CX " \n\t"
  356. /*
  357. * %c表示用来表示使用立即数替换,但不使用立即数的语法,at&t汇编中表示立即数的语法前面有一个$,而用了%c后,就去掉了这个$。
  358. * 主要是用在间接寻址的情况,这种情况下如果直接使用$立即数的方式的话,会报语法错误。
  359. * [host_rsp]是后面输入部分定义的tag,使用%tag方式可以直接引用,%0是后面输入输出部分中的第一个操作数,即vmx,这里是间接寻址
  360. * %c[host_rsp](%0)整体来看就是vmx(以寄存器ecx传入)中的host_rsp成员。
  361. * 所以,如下语句的整体含义就是比较当前SP寄存器和vmx->host_rsp的值。
  362. */
  363. /*如果当前RSP和vmx->rsp相等,那就不用mov了,否则将当前RSP保存到vmx中*/
  364. "cmp %%" _ASM_SP ", %c[host_rsp](%0) \n\t"
  365. "je 1f \n\t"
  366. "mov %%" _ASM_SP ", %c[host_rsp](%0) \n\t"
  367. /*执行ASM_VMX_VMWRITE_RSP_RDX指令,当出现异常时直接重启,由__ex()实现*/
  368. __ex(ASM_VMX_VMWRITE_RSP_RDX) "\n\t"
  369. "1: \n\t"
  370. /* Reload cr2 if changed */
  371. /*比较当前CR2寄存器和vmx中保存的CR2寄存器内容,如果不相等,就从vmx中重新CR2内容到当前CR2寄存器中*/
  372. "mov %c[cr2](%0), %%" _ASM_AX " \n\t"
  373. "mov %%cr2, %%" _ASM_DX " \n\t"
  374. "cmp %%" _ASM_AX ", %%" _ASM_DX " \n\t"
  375. "je 2f \n\t"
  376. "mov %%" _ASM_AX", %%cr2 \n\t"
  377. "2: \n\t"
  378. /* Check if vmlaunch of vmresume is needed */
  379. /*判断vcpu_vmx->__launched,确认是否需要执行VMLAUNCH*/
  380. "cmpl $0, %c[launched](%0) \n\t"
  381. /* Load guest registers. Don't clobber flags. */
  382. /*加载guest寄存器,其实就是从vmx中加载*/
  383. "mov %c[rax](%0), %%" _ASM_AX " \n\t"
  384. "mov %c[rbx](%0), %%" _ASM_BX " \n\t"
  385. "mov %c[rdx](%0), %%" _ASM_DX " \n\t"
  386. "mov %c[rsi](%0), %%" _ASM_SI " \n\t"
  387. "mov %c[rdi](%0), %%" _ASM_DI " \n\t"
  388. "mov %c[rbp](%0), %%" _ASM_BP " \n\t"
  389. #ifdef CONFIG_X86_64
  390. "mov %c[r8](%0), %%r8 \n\t"
  391. "mov %c[r9](%0), %%r9 \n\t"
  392. "mov %c[r10](%0), %%r10 \n\t"
  393. "mov %c[r11](%0), %%r11 \n\t"
  394. "mov %c[r12](%0), %%r12 \n\t"
  395. "mov %c[r13](%0), %%r13 \n\t"
  396. "mov %c[r14](%0), %%r14 \n\t"
  397. "mov %c[r15](%0), %%r15 \n\t"
  398. #endif
  399. "mov %c[rcx](%0), %%" _ASM_CX " \n\t" /* kills %0 (ecx) */
  400. /* Enter guest mode */
  401. "jne 1f \n\t"
  402. /* 执行VMLAUNCH指令,进入Guest模式*/
  403. __ex(ASM_VMX_VMLAUNCH) "\n\t"
  404. "jmp 2f \n\t"
  405. /* 如果已经曾经加载过VM了,执行VMRESUME指令,快速重新启动VM*/
  406. "1: " __ex(ASM_VMX_VMRESUME) "\n\t"
  407. "2: "
  408. /* Save guest registers, load host registers, keep flags */
  409. "mov %0, %c[wordsize](%%" _ASM_SP ") \n\t"
  410. "pop %0 \n\t"
  411. "mov %%" _ASM_AX ", %c[rax](%0) \n\t"
  412. "mov %%" _ASM_BX ", %c[rbx](%0) \n\t"
  413. __ASM_SIZE(pop) " %c[rcx](%0) \n\t"
  414. "mov %%" _ASM_DX ", %c[rdx](%0) \n\t"
  415. "mov %%" _ASM_SI ", %c[rsi](%0) \n\t"
  416. "mov %%" _ASM_DI ", %c[rdi](%0) \n\t"
  417. "mov %%" _ASM_BP ", %c[rbp](%0) \n\t"
  418. #ifdef CONFIG_X86_64
  419. "mov %%r8, %c[r8](%0) \n\t"
  420. "mov %%r9, %c[r9](%0) \n\t"
  421. "mov %%r10, %c[r10](%0) \n\t"
  422. "mov %%r11, %c[r11](%0) \n\t"
  423. "mov %%r12, %c[r12](%0) \n\t"
  424. "mov %%r13, %c[r13](%0) \n\t"
  425. "mov %%r14, %c[r14](%0) \n\t"
  426. "mov %%r15, %c[r15](%0) \n\t"
  427. #endif
  428. "mov %%cr2, %%" _ASM_AX " \n\t"
  429. "mov %%" _ASM_AX ", %c[cr2](%0) \n\t"
  430. "pop %%" _ASM_BP "; pop %%" _ASM_DX " \n\t"
  431. "setbe %c[fail](%0) \n\t"
  432. ".pushsection .rodata \n\t"
  433. ".global vmx_return \n\t"
  434. "vmx_return: " _ASM_PTR " 2b \n\t"
  435. ".popsection"
  436. : : "c"(vmx), "d"((unsigned long)HOST_RSP),
  437. [launched]"i"(offsetof(struct vcpu_vmx, __launched)),
  438. [fail]"i"(offsetof(struct vcpu_vmx, fail)),
  439. /*[host_rsp]是tag,可以在前面以%[host_rsp]方式引用*/
  440. [host_rsp]"i"(offsetof(struct vcpu_vmx, host_rsp)),
  441. [rax]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RAX])),
  442. [rbx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBX])),
  443. [rcx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RCX])),
  444. [rdx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDX])),
  445. [rsi]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RSI])),
  446. [rdi]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDI])),
  447. [rbp]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBP])),
  448. #ifdef CONFIG_X86_64
  449. [r8]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R8])),
  450. [r9]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R9])),
  451. [r10]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R10])),
  452. [r11]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R11])),
  453. [r12]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R12])),
  454. [r13]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R13])),
  455. [r14]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R14])),
  456. [r15]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R15])),
  457. #endif
  458. [cr2]"i"(offsetof(struct vcpu_vmx, vcpu.arch.cr2)),
  459. [wordsize]"i"(sizeof(ulong))
  460. : "cc", "memory"/*clobber list,cc表示寄存器,memory表示内存*/
  461. #ifdef CONFIG_X86_64
  462. , "rax", "rbx", "rdi", "rsi"
  463. , "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
  464. #else
  465. , "eax", "ebx", "edi", "esi"
  466. #endif
  467. );
  468. // 运行到这里,说明已经发生了VM-exit,返回到了root模式
  469. /* MSR_IA32_DEBUGCTLMSR is zeroed on vmexit. Restore it if needed */
  470. if (debugctlmsr)
  471. update_debugctlmsr(debugctlmsr);
  472. #ifndef CONFIG_X86_64
  473. /*
  474. * The sysexit path does not restore ds/es, so we must set them to
  475. * a reasonable value ourselves.
  476. *
  477. * We can't defer this to vmx_load_host_state() since that function
  478. * may be executed in interrupt context, which saves and restore segments
  479. * around it, nullifying its effect.
  480. */
  481. /*重新加载ds/es段寄存器,因为VM-exit不会自动加载他们*/
  482. loadsegment(ds, __USER_DS);
  483. loadsegment(es, __USER_DS);
  484. #endif
  485. vcpu->arch.regs_avail = ~((1 << VCPU_REGS_RIP) | (1 << VCPU_REGS_RSP)
  486. | (1 << VCPU_EXREG_RFLAGS)
  487. | (1 << VCPU_EXREG_CPL)
  488. | (1 << VCPU_EXREG_PDPTR)
  489. | (1 << VCPU_EXREG_SEGMENTS)
  490. | (1 << VCPU_EXREG_CR3));
  491. vcpu->arch.regs_dirty = 0;
  492. // 从硬件VMCS中读取中断向量表信息
  493. vmx->idt_vectoring_info = vmcs_read32(IDT_VECTORING_INFO_FIELD);
  494. vmx->loaded_vmcs->launched = 1;
  495. // 从硬件VMCS中读取VM-exit原因信息,这些信息是VM-exit过程中由硬件自动写入的
  496. vmx->exit_reason = vmcs_read32(VM_EXIT_REASON);
  497. trace_kvm_exit(vmx->exit_reason, vcpu, KVM_ISA_VMX);
  498. /*处理MCE异常和NMI中断*/
  499. vmx_complete_atomic_exit(vmx);
  500. vmx_recover_nmi_blocking(vmx);
  501. vmx_complete_interrupts(vmx);
  502. }

kernel 3.10代码分析--KVM相关--虚拟机创建\VCPU创建\虚拟机运行相关推荐

  1. kernel 3.10代码分析--KVM相关--虚拟机运行

    1.基本原理 KVM虚拟机通过字符设备/dev/kvm的ioctl接口创建和运行,相关原理见之前的文章说明. 虚拟机的运行通过/dev/kvm设备ioctl VCPU接口的KVM_RUN指令实现,在V ...

  2. 【高通SDM660平台 Android 10.0】(10) --- Camera Sensor lib 与 Kernel Camera Probe 代码分析

    [高通SDM660平台 Android 10.0]Camera Sensor lib 与 Kernel Camera Probe 代码分析 一.libmmcamera_imx258.so 代码分析 1 ...

  3. ernel 3.10内核源码分析--KVM相关--虚拟机运行

    1.基本原理 KVM虚拟机通过字符设备/dev/kvm的ioctl接口创建和运行,相关原理见之前的文章说明. 虚拟机的运行通过/dev/kvm设备ioctl VCPU接口的KVM_RUN指令实现, 在 ...

  4. LCM在Kernel中的代码分析

    lcm的分析首先是mtkfb.c 1.mtk_init中platform_driver_register(&mtkfb_driver)注册平台驱动 panelmaster_init(); DB ...

  5. 【高通SDM660平台 Android 10.0】(12) --- Camera Chromatix 代码分析

    [高通SDM660平台 Android 10.0]Qcom Camera Daemon 代码分析 一.chromatix_imx258_lc898217xc 目录 1.1 例:imx258_lc898 ...

  6. Exp4 恶意代码分析 20164303 景圣

    Exp4 恶意代码分析 实验内容 实验点一:系统运行监控 (1)使用如计划任务,每隔一分钟记录自己的电脑有哪些程序在联网,连接的外部IP是哪里.运行一段时间并分析该文件,综述一下分析结果.目标就是找出 ...

  7. 20165310 NetSec2019 Week6 Exp4 恶意代码分析

    20165310 NetSec2019 Week6 Exp4 恶意代码分析 一.实验要求 1.系统运行监控 使用如计划任务,每隔一分钟记录自己的电脑有哪些程序在联网,连接的外部IP是哪里.运行一段时间 ...

  8. xss平台模块代码分析--默认模块

    这里写目录标题 攻击实列 代码分析 攻击实列 1. 首先创建一个模块 首次分析,就只选用一个模块,点击下方下一步后. 项目已经创建完毕,下方也有详细的使用教程. 2.先进行使用看下效果. 就使用第一个 ...

  9. Linux kernel 3.10内核源码分析--slab原理及相关代码

    1.基本原理 我们知道,Linux保护模式下,采用分页机制,内核中物理内存使用buddy system(伙伴系统)进行管理,管理的内存单元大小为一页,也就是说使用buddy system分配内存最少需 ...

最新文章

  1. wordpress插件翻译不生效_新的恶意软件利用wordpress插件漏洞攻击数百万网站
  2. iOS设计模式 ——单例模式详解以及严格单例模式注意点
  3. 还在用Swagger?我推荐这款零代码侵入的接口管理神器!
  4. A2W和W2A :很好的多字节和宽字节字符串的转换宏
  5. 现代信号处理——参数估计理论(最大似然估计)
  6. Android仿虾米音乐播放器之专辑图片模糊处理
  7. Java中汉字生成拼音首拼和五笔码实例
  8. xshell5 Xshell6 商业版的破解版
  9. 上位机和下位机笔记总结
  10. 单片机编程语言——C语言(1)
  11. 重构kz-admin
  12. 如何安装浏览器插件,一篇文章全搞定
  13. StreamWiki: Enabling Viewers of Knowledge SharingLive Streams to Collaboratively Generate Archival
  14. vue2响应式通过数组下标赋值响应式问题
  15. 思科模拟器DHCP配置-CCNA
  16. 怎样linux下修复u盘,怎样Linux下修复U盘驱动器
  17. SpringBoot 国际化实现跨地区的多语言切换
  18. STC89C52RC单片机额外篇 | 04 - 认识头文件<intrins.h>与_nop_函数
  19. 云原生运行时的下一个五年
  20. .text‘ will not fit in region `ROM‘, region `ROM‘ overflowed by 3056 bytes问题记录

热门文章

  1. 突然关机导致git文件受损,如何处理
  2. Spring MVC控制层的返回类型--String类型与Bean类型
  3. MySQL存储过程的创建及调用
  4. javascript中的正则匹配函数exec(),test(),match()
  5. iOS高级教程:处理1000张图片的内存优化
  6. hdu2846(2009多校第四场) 字典树
  7. Win7开机蓝屏错误atikmpag.sys的解决方法
  8. spring MVC - Inteceptors(拦截器)
  9. golang go-restful RESTful框架 简介
  10. python requests 重新定向错误 requests.exceptions.TooManyRedirects: Exceeded xx redirects 解决方法