嵌套 VMX¶
概述¶
在 Intel 处理器上,KVM 使用 Intel 的 VMX (虚拟机扩展) 来轻松高效地运行客户操作系统。通常,这些客户机不能自身成为运行其自身客户机的管理程序,因为在 VMX 中,客户机不能使用 VMX 指令。
“嵌套 VMX”功能添加了这项缺失的功能——运行带其自身嵌套客户机的客户机管理程序(使用 VMX)。它通过允许客户机使用 VMX 指令,并利用硬件中可用的单层 VMX 正确高效地模拟这些指令来实现这一点。
我们将在 OSDI 2010 年的论文《海龟项目:嵌套虚拟化的设计与实现》(“The Turtles Project: Design and Implementation of Nested Virtualization”)中更详细地描述嵌套 VMX 功能的理论基础、其实现及其性能特征,该论文可在以下地址获取:
术语¶
单层虚拟化有两个级别——宿主机 (KVM) 和客户机。在嵌套虚拟化中,我们有三个级别:宿主机 (KVM),我们称之为 L0;客户机管理程序,我们称之为 L1;以及其嵌套客户机,我们称之为 L2。
运行嵌套 VMX¶
自 Linux 内核 v4.20 起,嵌套 VMX 功能默认启用。对于较旧的 Linux 内核,可以通过向 kvm-intel 模块提供“nested=1”选项来启用它。
用户空间 (qemu) 无需修改。然而,qemu 默认模拟的 CPU 类型 (qemu64) 未列出“VMX”CPU 功能,因此必须通过向 qemu 提供以下选项之一来显式启用它:
cpu host(模拟 CPU 具有真实 CPU 的所有功能)
cpu qemu64,+vmx(仅将 vmx 功能添加到指定的 CPU 类型)
ABI¶
嵌套 VMX 旨在为客户机管理程序提供一个标准且(最终)功能齐全的 VMX 实现。因此,其提供的 ABI 的官方规范是 Intel 的 VMX 规范,即其《Intel 64 和 IA-32 架构软件开发人员手册》的第 3B 卷。目前并非所有 VMX 功能都得到完全支持,但目标是最终支持所有功能,从流行管理程序(KVM 等)实际使用的 VMX 功能开始。
作为 VMX 实现,嵌套 VMX 向 L1 呈现一个 VMCS 结构。根据规范要求,除了 revision_id 和 abort 这两个字段外,该结构对其用户是不透明的,用户不应了解或关心其内部结构。相反,该结构通过 VMREAD 和 VMWRITE 指令访问。尽管如此,出于调试目的,KVM 开发人员可能对了解此结构的内部感兴趣;这就是 arch/x86/kvm/vmx.c 中的 struct vmcs12。
名称“vmcs12”指代 L1 为 L2 构建的 VMCS。在代码中,我们还有“vmcs01”,即 L0 为 L1 构建的 VMCS;而“vmcs02”是 L0 为实际运行 L2 而构建的 VMCS——这方面的做法已在上述论文中解释。
为方便起见,我们在此重复 struct vmcs12 的内容。如果此结构的内部发生变化,可能会破坏跨 KVM 版本的实时迁移。如果 struct vmcs12 或其内部 struct shadow_vmcs 发生任何更改,则应更改 VMCS12_REVISION(来自 vmx.c)。
typedef u64 natural_width;
struct __packed vmcs12 {
/* According to the Intel spec, a VMCS region must start with
* these two user-visible fields */
u32 revision_id;
u32 abort;
u32 launch_state; /* set to 0 by VMCLEAR, to 1 by VMLAUNCH */
u32 padding[7]; /* room for future expansion */
u64 io_bitmap_a;
u64 io_bitmap_b;
u64 msr_bitmap;
u64 vm_exit_msr_store_addr;
u64 vm_exit_msr_load_addr;
u64 vm_entry_msr_load_addr;
u64 tsc_offset;
u64 virtual_apic_page_addr;
u64 apic_access_addr;
u64 ept_pointer;
u64 guest_physical_address;
u64 vmcs_link_pointer;
u64 guest_ia32_debugctl;
u64 guest_ia32_pat;
u64 guest_ia32_efer;
u64 guest_pdptr0;
u64 guest_pdptr1;
u64 guest_pdptr2;
u64 guest_pdptr3;
u64 host_ia32_pat;
u64 host_ia32_efer;
u64 padding64[8]; /* room for future expansion */
natural_width cr0_guest_host_mask;
natural_width cr4_guest_host_mask;
natural_width cr0_read_shadow;
natural_width cr4_read_shadow;
natural_width dead_space[4]; /* Last remnants of cr3_target_value[0-3]. */
natural_width exit_qualification;
natural_width guest_linear_address;
natural_width guest_cr0;
natural_width guest_cr3;
natural_width guest_cr4;
natural_width guest_es_base;
natural_width guest_cs_base;
natural_width guest_ss_base;
natural_width guest_ds_base;
natural_width guest_fs_base;
natural_width guest_gs_base;
natural_width guest_ldtr_base;
natural_width guest_tr_base;
natural_width guest_gdtr_base;
natural_width guest_idtr_base;
natural_width guest_dr7;
natural_width guest_rsp;
natural_width guest_rip;
natural_width guest_rflags;
natural_width guest_pending_dbg_exceptions;
natural_width guest_sysenter_esp;
natural_width guest_sysenter_eip;
natural_width host_cr0;
natural_width host_cr3;
natural_width host_cr4;
natural_width host_fs_base;
natural_width host_gs_base;
natural_width host_tr_base;
natural_width host_gdtr_base;
natural_width host_idtr_base;
natural_width host_ia32_sysenter_esp;
natural_width host_ia32_sysenter_eip;
natural_width host_rsp;
natural_width host_rip;
natural_width paddingl[8]; /* room for future expansion */
u32 pin_based_vm_exec_control;
u32 cpu_based_vm_exec_control;
u32 exception_bitmap;
u32 page_fault_error_code_mask;
u32 page_fault_error_code_match;
u32 cr3_target_count;
u32 vm_exit_controls;
u32 vm_exit_msr_store_count;
u32 vm_exit_msr_load_count;
u32 vm_entry_controls;
u32 vm_entry_msr_load_count;
u32 vm_entry_intr_info_field;
u32 vm_entry_exception_error_code;
u32 vm_entry_instruction_len;
u32 tpr_threshold;
u32 secondary_vm_exec_control;
u32 vm_instruction_error;
u32 vm_exit_reason;
u32 vm_exit_intr_info;
u32 vm_exit_intr_error_code;
u32 idt_vectoring_info_field;
u32 idt_vectoring_error_code;
u32 vm_exit_instruction_len;
u32 vmx_instruction_info;
u32 guest_es_limit;
u32 guest_cs_limit;
u32 guest_ss_limit;
u32 guest_ds_limit;
u32 guest_fs_limit;
u32 guest_gs_limit;
u32 guest_ldtr_limit;
u32 guest_tr_limit;
u32 guest_gdtr_limit;
u32 guest_idtr_limit;
u32 guest_es_ar_bytes;
u32 guest_cs_ar_bytes;
u32 guest_ss_ar_bytes;
u32 guest_ds_ar_bytes;
u32 guest_fs_ar_bytes;
u32 guest_gs_ar_bytes;
u32 guest_ldtr_ar_bytes;
u32 guest_tr_ar_bytes;
u32 guest_interruptibility_info;
u32 guest_activity_state;
u32 guest_sysenter_cs;
u32 host_ia32_sysenter_cs;
u32 padding32[8]; /* room for future expansion */
u16 virtual_processor_id;
u16 guest_es_selector;
u16 guest_cs_selector;
u16 guest_ss_selector;
u16 guest_ds_selector;
u16 guest_fs_selector;
u16 guest_gs_selector;
u16 guest_ldtr_selector;
u16 guest_tr_selector;
u16 host_es_selector;
u16 host_cs_selector;
u16 host_ss_selector;
u16 host_ds_selector;
u16 host_fs_selector;
u16 host_gs_selector;
u16 host_tr_selector;
};