接前一篇文章:
本文内容参考:
《 QEMU /KVM》 源码 解析与应用 —— 李强,机械工业出版社
《深度探索 Linux 系统 虚拟化 原理与实现》—— 王柏生 谢广军, 机械工业出版社
特此致谢!
三、KVM模块初始化介绍
2. KVM模块初始化
KVM模块的初始化既要完成架构无关的部分,也要配置好上一回所讲的架构相关的数据。intel-kvm.ko的模块注册函数是vmx_init,该函数在Linux内核源码/arch/x86/kvm/vmx/vmx.c中,代码如下:
- static int __init vmx_init(void)
- {
- int r, cpu;
-
- if (!kvm_is_vmx_supported())
- return -EOPNOTSUPP;
-
- /*
- * Note, hv_init_evmcs() touches only VMX knobs, i.e. there's nothing
- * to unwind if a later step fails.
- */
- hv_init_evmcs();
-
- r = kvm_x86_vendor_init(&vt_init_ops);
- if (r)
- return r;
-
- /*
- * Must be called after common x86 init so enable_ept is properly set
- * up. Hand the parameter mitigation value in which was stored in
- * the pre module init parser. If no parameter was given, it will
- * contain 'auto' which will be turned into the default 'cond'
- * mitigation mode.
- */
- r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);
- if (r)
- goto err_l1d_flush;
-
- for_each_possible_cpu(cpu) {
- INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
- pi_init_cpu(cpu);
- }
-
- cpu_emergency_register_virt_callback(vmx_emergency_disable);
-
- vmx_check_vmcs12_offsets();
-
- /*
- * Shadow paging doesn't have a (further) performance penalty
- * from GUEST_MAXPHYADDR < HOST_MAXPHYADDR so enable it
- * by default
- */
- if (!enable_ept)
- allow_smaller_maxphyaddr = true;
-
- /*
- * Common KVM initialization _must_ come last, after this, /dev/kvm is
- * exposed to userspace!
- */
- r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
- THIS_MODULE);
- if (r)
- goto err_kvm_init;
-
- return 0;
-
- err_kvm_init:
- __vmx_exit();
- err_l1d_flush:
- kvm_x86_vendor_exit();
- return r;
- }
- module_init(vmx_init);
与之对应,intel-kvm.ko的模块注销函数是vmx_exit,该函数当然也在Linux内核源码/arch/x86/kvm/vmx/vmx.c中,代码如下:
- static void vmx_exit(void)
- {
- kvm_exit();
- kvm_x86_vendor_exit();
-
- __vmx_exit();
- }
- module_exit(vmx_exit);
回到vmx_init函数。在vmx_init函数中会调用kvm_init函数,代码片段如下:
- /*
- * Common KVM initialization _must_ come last, after this, /dev/kvm is
- * exposed to userspace!
- */
- r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
- THIS_MODULE);
- if (r)
- 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函数的 代码如下:
- static int __init vmx_init(void)
- {
- int r, cpu;
-
- #if IS_ENABLED(CONFIG_HYPERV)
- /*
- * Enlightened VMCS usage should be recommended and the host needs
- * to support eVMCS v1 or above. We can also disable eVMCS support
- * with module parameter.
- */
- if (enlightened_vmcs &&
- ms_hyperv.hints & HV_X64_ENLIGHTENED_VMCS_RECOMMENDED &&
- (ms_hyperv.nested_features & HV_X64_ENLIGHTENED_VMCS_VERSION) >=
- KVM_EVMCS_VERSION) {
-
- /* Check that we have assist pages on all online CPUs */
- for_each_online_cpu(cpu) {
- if (!hv_get_vp_assist_page(cpu)) {
- enlightened_vmcs = false;
- break;
- }
- }
-
- if (enlightened_vmcs) {
- pr_info("KVM: vmx: using Hyper-V Enlightened VMCS\n");
- static_branch_enable(&enable_evmcs);
- }
-
- if (ms_hyperv.nested_features & HV_X64_NESTED_DIRECT_FLUSH)
- vmx_x86_ops.enable_direct_tlbflush
- = hv_enable_direct_tlbflush;
-
- } else {
- enlightened_vmcs = false;
- }
- #endif
-
- r = kvm_init(&vmx_init_ops, sizeof(struct vcpu_vmx),
- __alignof__(struct vcpu_vmx), THIS_MODULE);
- if (r)
- return r;
-
- /*
- * Must be called after kvm_init() so enable_ept is properly set
- * up. Hand the parameter mitigation value in which was stored in
- * the pre module init parser. If no parameter was given, it will
- * contain 'auto' which will be turned into the default 'cond'
- * mitigation mode.
- */
- r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);
- if (r) {
- vmx_exit();
- return r;
- }
-
- vmx_setup_fb_clear_ctrl();
-
- for_each_possible_cpu(cpu) {
- INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
- pi_init_cpu(cpu);
- }
-
- #ifdef CONFIG_KEXEC_CORE
- rcu_assign_pointer(crash_vmclear_loaded_vmcss,
- crash_vmclear_local_loaded_vmcss);
- #endif
- vmx_check_vmcs12_offsets();
-
- /*
- * Shadow paging doesn't have a (further) performance penalty
- * from GUEST_MAXPHYADDR < HOST_MAXPHYADDR so enable it
- * by default
- */
- if (!enable_ept)
- allow_smaller_maxphyaddr = true;
-
- return 0;
- }
- module_init(vmx_init);
下一回开始,对vmx_init()及其相关函数进行展开讲解。欲知后事如何,且看下回分解。