ネスト VMX
概要
Intel プロセッサ上で、KVM は Intel の VMX (Virtual-Machine eXtensions) を 使ってゲストオペレーティングシステムを簡単かつ効率的に実行する。通常、 これらのゲストは自身がハイパーバイザとなって自分のゲストを動かすことは できない。VMX ではゲストが VMX 命令を使えないためである。
「ネスト VMX」機能は、この欠けていた能力、すなわち VMX を使うゲスト ハイパーバイザをそのネストゲストとともに動かす能力を追加する。これは、 ゲストに VMX 命令の使用を許可し、ハードウェアで利用できる単一レベルの VMX を使ってそれらを正しく効率的にエミュレートすることで実現する。
ネスト VMX 機能の背景にある理論、その実装、性能特性については、OSDI 2010 の論文「The Turtles Project: Design and Implementation of Nested Virtualization」で詳しく説明している。論文は次で入手できる。
用語
単一レベルの仮想化には、ホスト (KVM) とゲストの 2 つのレベルがある。 ネスト仮想化では 3 つのレベルがある。ホスト (KVM) を L0、ゲスト ハイパーバイザを L1、そのネストゲストを L2 と呼ぶ。
ネスト VMX の実行
ネスト VMX 機能は Linux カーネル v4.20 以降、既定で有効である。それより 古い Linux カーネルでは、kvm-intel モジュールに "nested=1" オプションを 渡すと有効にできる。
ユーザ空間 (qemu) の変更は不要である。ただし、qemu の既定のエミュレート CPU タイプ (qemu64) は "VMX" CPU 機能を列挙しないため、次のいずれかの オプションを qemu に渡して明示的に有効にする必要がある。
cpu host (エミュレート CPU が実 CPU のすべての機能を持つ)
cpu qemu64,+vmx (名前付き CPU タイプに vmx 機能だけを追加する)
ABI
ネスト VMX は、ゲストハイパーバイザが使うための標準的で (最終的には) 完全に機能する VMX 実装を提供することを目指している。そのため、ここで 提供する ABI の公式仕様は Intel の VMX 仕様、すなわち「Intel 64 and IA-32 Architectures Software Developer's Manual」の volume 3B である。現在、 VMX のすべての機能が完全にサポートされているわけではないが、最終的には すべてをサポートすることが目標であり、まず一般的なハイパーバイザ (KVM など) が実際に使う VMX 機能から始める。
VMX 実装として、ネスト VMX は L1 に VMCS 構造を提示する。仕様で定められて いるとおり、revision_id と abort の 2 つのフィールドを除き、この構造は 利用者にとって*不透明*であり、利用者はその内部構造を知る必要も気にする 必要もない。代わりに、この構造には 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;
};
作者
- これらのパッチを書いたのは次の人たちである。
Abel Gordon, abelg <at> il.ibm.com
Nadav Har'El, nyh <at> il.ibm.com
Orit Wasserman, oritw <at> il.ibm.com
Ben-Ami Yassor, benami <at> il.ibm.com
Muli Ben-Yehuda, muli <at> il.ibm.com
- 貢献したのは次の人たちである。
Anthony Liguori, aliguori <at> us.ibm.com
Mike Day, mdday <at> us.ibm.com
Michael Factor, factor <at> il.ibm.com
Zvi Dubitzky, dubi <at> il.ibm.com
- 貴重なレビューをしてくれたのは次の人たちである。
Avi Kivity, avi <at> redhat.com
Gleb Natapov, gleb <at> redhat.com
Marcelo Tosatti, mtosatti <at> redhat.com
Kevin Tian, kevin.tian <at> intel.com
その他多数。