このページの目次

KVMアーキテクチャ

カバー範囲: KVMをLinuxハイパーバイザーとして使用 → VMX/SVMハードウェア仮想化 → vCPUライフサイクル → VM exit処理 → KVM + QEMU協調モデル → /dev/kvm ioctl API カーネルバージョン: 2.6.20 ~ 6.x

概要

KVM (Kernel-based Virtual Machine) は、Linuxカーネルをタイプ1ハイパーバイザーに変換します。Intel VMXまたはAMD SVMのハードウェア仮想化拡張機能を利用し、ゲストがVMX non-rootモードで物理CPU上で直接実行できるようにします。各VMはユーザー空間のQEMUプロセスであり、各vCPUはそのプロセス内のスレッドです。

アーキテクチャの階層

KVM アーキテクチャの階層: QEMUユーザー空間 · カーネル空間 KVM QEMU(ユーザー空間) デバイスエミュレーション virtio / VGA / ストレージ / ネットワーク VM管理 ライブマイグレーション / スナップショット KVM ioctl /dev/kvm ioctlシステムコールがユーザー空間からカーネル空間へ透過 カーネル(KVM) kvm.ko アーキテクチャ非依存コア kvm-intel.ko VMXサポート kvm-amd.ko SVMサポート QEMUは/dev/kvm ioctlを介してカーネルと通信するだけで、仮想化レジスタに直接アクセスしません。 kvm.koはアーキテクチャ非依存のコアであり、kvm-intel.ko / kvm-amd.koはそれぞれVMX / SVMに対応します。

VM Entry / VM Exit

VM Entry / VM Exit: ゲスト実行とカーネルによるexit処理のループ ゲスト実行 VMLAUNCH(VMX) / VMRUN(SVM) → VMX non-rootモードに入る ゲストが物理CPU上で直接実行 (VM exitが発生するまで) VM Exit(rootモード) ゲストが敏感な命令を実行 (HLT / IO / EPT violation …) KVMはexit reasonを読み取り、処理をディスパッチ: IO_INSTRUCTION → IO命令のエミュレーション EPT_VIOLATION → ゲストページテーブルの変更処理 CPUID → CPUIDのエミュレーション HLT → idleインジェクション MSR_READ/WRITE → MSRアクセスのエミュレーション 処理完了 → VMRESUMEでゲストに戻る 敏感な命令のトリガー → rootへのexit 処理完了 → VMRESUMEでゲストに戻る VM ExitはKVMの中核ループです: ゲストが敏感な操作を試みる → カーネルに陥入 → KVMがソフトウェアでエミュレーション → VMRESUMEで再開。 exit reasonを読み取ってタイプ別に処理をディスパッチするのは、仮想化におけるguest/host間の切り替えオーバーヘッドの主要な原因です。

vCPUスレッドモデル

// 各vCPU = 1つのQEMUスレッド、実行ループ:
while (running) {
    ioctl(vcpu_fd, KVM_RUN, 0);
    // 戻り値: VM exit
    switch (vcpu->kvm_run->exit_reason) {
    case KVM_EXIT_IO:       handle_io(vcpu);       break;
    case KVM_EXIT_MMIO:     handle_mmio(vcpu);     break;
    case KVM_EXIT_IRQ_WINDOW_OPEN: inject_interrupt(vcpu); break;
    // ...
    }
}

スケジューラとの連携

// KVMはpreempt notifierを介して、vCPUがプリエンプトされるタイミングを知る:
// → kvm_sched_out(): vCPUがスケジュールアウトされた
// → kvm_sched_in():  vCPUがCPUを獲得した
// 用途: steal timeの集計、TSCオフセット計算

/dev/kvmインターフェース

// VMの作成:
int kvm_fd = open("/dev/kvm", O_RDWR);
int vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);

// メモリの登録 (ゲスト物理アドレス空間):
struct kvm_userspace_memory_region region = {
    .slot = 0, .guest_phys_addr = 0, .memory_size = 1ULL << 30,
    .userspace_addr = (__u64)guest_ram_ptr, .flags = 0 };
ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &region);

// vCPUの作成:
int vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, cpu_id);

// KVMのバージョン/能力の取得:
int version = ioctl(kvm_fd, KVM_GET_API_VERSION, 0);
struct kvm_cpuid2 cpuid = { .nent = N }; 
ioctl(kvm_fd, KVM_GET_SUPPORTED_CPUID, &cpuid);

割り込みインジェクション

// KVMがゲストへ割り込みをインジェクション:
//   1. VMCS (VMX) または VMCB (SVM) の割り込み情報フィールドを設定
//   2. interrupt windowを設定 → VM entry後、ゲストは即座に応答
//   3. vCPUが実行中でない場合 (スケジュールアウトされている) → kickフラグを設定

// 割り込みのソース:
//   - virtioデバイス (msix)
//   - エミュレーションデバイス (8259A PIC, IOAPIC)
//   - IPI (ゲスト内の複数vCPU間)
//   - タイマー (hrtimer→KVMタイマー)

参考

  • ソースコード⁠: virt/kvm/kvm_main.c (コア), arch/x86/kvm/vmx/vmx.c (Intel VMX), arch/x86/kvm/svm/svm.c (AMD SVM)
  • カーネルドキュメント⁠: Documentation/virt/kvm/
  • LWN: "KVM: the Linux virtual machine monitor"

キーワード: KVM, VMX, SVM, vCPU, VM exit, VMLAUNCH, VMRESUME, /dev/kvm, KVM_RUN, 割り込みインジェクション