QEMU源码全解析 —— CPU虚拟化(14)

123 篇文章 36 订阅 ¥49.90 ¥99.00

接前一篇文章:

本文内容参考:

《趣谈 Linux操作系统 》 —— 刘超, 极客时间

QEMU /KVM》 源码 解析与应用 —— 李强,机械工业出版社

《深度探索 Linux 系统 虚拟化 原理与实现》—— 王柏生 谢广军, 机械工业出版社

特此致谢!

三、KVM模块初始化介绍

2. KVM模块初始化

KVM模块的初始化既要完成架构无关的部分,也要配置好上一回所讲的架构相关的数据。intel-kvm.ko的模块注册函数是vmx_init,该函数在Linux内核源码/arch/x86/kvm/vmx/vmx.c中,代码如下:

  1. static int __init vmx_init(void)
  2. {
  3. int r, cpu;
  4. if (!kvm_is_vmx_supported())
  5. return -EOPNOTSUPP;
  6. /*
  7. * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
  8. * to unwind if a later step fails.
  9. */
  10. hv_init_evmcs();
  11. r = kvm_x86_vendor_init(&vt_init_ops);
  12. if (r)
  13. return r;
  14. /*
  15. * Must be called after common x86 init so enable_ept is properly set
  16. * up. Hand the parameter mitigation value in which was stored in
  17. * the pre module init parser. If no parameter was given, it will
  18. * contain 'auto' which will be turned into the default 'cond'
  19. * mitigation mode.
  20. */
  21. r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);
  22. if (r)
  23. goto err_l1d_flush;
  24. for_each_possible_cpu(cpu) {
  25. INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
  26. pi_init_cpu(cpu);
  27. }
  28. cpu_emergency_register_virt_callback(vmx_emergency_disable);
  29. vmx_check_vmcs12_offsets();
  30. /*
  31. * Shadow paging doesn't have a (further) performance penalty
  32. * from GUEST_MAXPHYADDR < HOST_MAXPHYADDR so enable it
  33. * by default
  34. */
  35. if (!enable_ept)
  36. allow_smaller_maxphyaddr = true;
  37. /*
  38. * Common KVM initialization _must_ come last, after this, /dev/kvm is
  39. * exposed to userspace!
  40. */
  41. r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
  42. THIS_MODULE);
  43. if (r)
  44. goto err_kvm_init;
  45. return 0;
  46. err_kvm_init:
  47. __vmx_exit();
  48. err_l1d_flush:
  49. kvm_x86_vendor_exit();
  50. return r;
  51. }
  52. module_init(vmx_init);

与之对应,intel-kvm.ko的模块注销函数是vmx_exit,该函数当然也在Linux内核源码/arch/x86/kvm/vmx/vmx.c中,代码如下:

  1. static void vmx_exit(void)
  2. {
  3. kvm_exit();
  4. kvm_x86_vendor_exit();
  5. __vmx_exit();
  6. }
  7. module_exit(vmx_exit);

回到vmx_init函数。在vmx_init函数中会调用kvm_init函数,代码片段如下:

  1. /*
  2. * Common KVM initialization _must_ come last, after this, /dev/kvm is
  3. * exposed to userspace!
  4. */
  5. r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
  6. THIS_MODULE);
  7. if (r)
  8. goto err_kvm_init;

这里要说明一下:

上边KVM(即Linux内核)的版本是6.10.1。在老版本的内核KVM代码中,kvm_init函数的第1个参数为&vmx_x86_ops,但在这个新版本KVM代码中已经被去掉了。笔者看了一下引起这一变化的Linux内核代码提交时间,是今年(2024年)的4月12号。变化及提交说明如下:

这一次提交的大致意思是:

KVM使用VMX指令访问 虚拟机 控制结构(VMCS)以在VM上操作。TDX不允许VMM直接操作VMCS。而作为替代,TDX有自己的数据结构,以及用于VMM间接操作这些数据结构的TDX SEAMCALL API。这意味着必须有kvm_x86_ops的TDX版本。

现有的全局结构kvm_x86_ops已经定义了一个可以适应TDX的接口,但kvm_x86_ops是一个系统范围的结构,而不是每个VM的结构。为了允许VMX与TD共存,kvm_x86_ops回调
将有包装器“if(tdx)tdx_op()else vmx_op)”来在运行时选择vmx或TDX。

要拆分运行时开关、VMX实现和TDX实现,需要添加main.c,并移出VMX_x86_ops挂钩,为添加TDX做准备。使用“vt”作为命名方案,以向vt-x致敬,并作为VmxTdx的串联。

前文书已提到,KVM的所有虚拟化实现(Intel和AMD)都会向KVM模块注册一个kvm_x86_ops结构体(实例),而vmx_x86_ops(struct kvm_x86_ops vmx_x86_ops)表示的即是Intel VT-x具体实现的各种回调函数,是一个非常大的结构,包括具体硬件检测、虚拟机创建VCPU的实现、一些寄存器的设置、虚拟机退出的处理函数等。

这里为了便于理解,笔者还是使用之前的老版本内核代码(即没有移除vmx_x86_ops之前的代码进行讲解)先掌握第一步,再说新版本中提到的第二步即TDX。

笔者先开始找了6.7版本,但是很奇怪,虽然发布时间是早于上边的补丁提交时间,但是却已经改变了。无奈,笔者只好找了更早一些的Linux 6.1.10内核版本,如下所示:

在这个版本中,vmx_init函数的 代码如下:

  1. static int __init vmx_init(void)
  2. {
  3. int r, cpu;
  4. #if IS_ENABLED(CONFIG_HYPERV)
  5. /*
  6. * Enlightened VMCS usage should be recommended and the host needs
  7. * to support eVMCS v1 or above. We can also disable eVMCS support
  8. * with module parameter.
  9. */
  10. if (enlightened_vmcs &&
  11. ms_hyperv.hints & HV_X64_ENLIGHTENED_VMCS_RECOMMENDED &&
  12. (ms_hyperv.nested_features & HV_X64_ENLIGHTENED_VMCS_VERSION) >=
  13. KVM_EVMCS_VERSION) {
  14. /* Check that we have assist pages on all online CPUs */
  15. for_each_online_cpu(cpu) {
  16. if (!hv_get_vp_assist_page(cpu)) {
  17. enlightened_vmcs = false;
  18. break;
  19. }
  20. }
  21. if (enlightened_vmcs) {
  22. pr_info("KVM: vmx: using Hyper-V Enlightened VMCS\n");
  23. static_branch_enable(&enable_evmcs);
  24. }
  25. if (ms_hyperv.nested_features & HV_X64_NESTED_DIRECT_FLUSH)
  26. vmx_x86_ops.enable_direct_tlbflush
  27. = hv_enable_direct_tlbflush;
  28. } else {
  29. enlightened_vmcs = false;
  30. }
  31. #endif
  32. r = kvm_init(&vmx_init_ops, sizeof(struct vcpu_vmx),
  33. __alignof__(struct vcpu_vmx), THIS_MODULE);
  34. if (r)
  35. return r;
  36. /*
  37. * Must be called after kvm_init() so enable_ept is properly set
  38. * up. Hand the parameter mitigation value in which was stored in
  39. * the pre module init parser. If no parameter was given, it will
  40. * contain 'auto' which will be turned into the default 'cond'
  41. * mitigation mode.
  42. */
  43. r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);
  44. if (r) {
  45. vmx_exit();
  46. return r;
  47. }
  48. vmx_setup_fb_clear_ctrl();
  49. for_each_possible_cpu(cpu) {
  50. INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
  51. pi_init_cpu(cpu);
  52. }
  53. #ifdef CONFIG_KEXEC_CORE
  54. rcu_assign_pointer(crash_vmclear_loaded_vmcss,
  55. crash_vmclear_local_loaded_vmcss);
  56. #endif
  57. vmx_check_vmcs12_offsets();
  58. /*
  59. * Shadow paging doesn't have a (further) performance penalty
  60. * from GUEST_MAXPHYADDR < HOST_MAXPHYADDR so enable it
  61. * by default
  62. */
  63. if (!enable_ept)
  64. allow_smaller_maxphyaddr = true;
  65. return 0;
  66. }
  67. module_init(vmx_init);

下一回开始,对vmx_init()及其相关函数进行展开讲解。欲知后事如何,且看下回分解。

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他