このページの目次

QEMU と KVM の連携

概要: QEMU-KVM プロセスモデル → KVM_CREATE_VM/KVM_RUN → メモリレイアウト → vCPU スレッドループ → デバイスエミュレーション → ライブマイグレーション カーネルバージョン: 2.6.20 ~ 6.x

プロセスモデル

flowchart TD
    QEMU["1つのVM = 1つのQEMUプロセス"]

    QEMU --> MAIN["🧵 メインスレッド<br/>event loop, monitor"]
    QEMU --> IO["🧵 IOスレッド (iothread)<br/>virtioデバイスIOの処理"]
    QEMU --> VCPU["🧵 vCPUスレッド × N<br/>それぞれがKVM_RUNを実行"]
    QEMU --> MIGRATE["🧵 移行スレッド<br/>ライブマイグレーション"]

    classDef root fill:#e3f2fd,stroke:#1565c0
    classDef thread fill:#fff3e0,stroke:#ef6c00
    class QEMU root
    class MAIN,IO,VCPU,MIGRATE thread

VM作成フロー

// QEMU → KVM:
kvm_fd = open("/dev/kvm", O_RDWR);

// 1. VMの作成
vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);

// 2. ゲストRAMの割り当て (QEMUが管理):
guest_ram = mmap(NULL, ram_size, PROT_READ|PROT_WRITE,
                 MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0);

// 3. KVMへの登録:
struct kvm_userspace_memory_region region = {
    .slot = 0,              // メモリスロットID
    .guest_phys_addr = 0,   // ゲスト物理アドレスの開始位置
    .memory_size = ram_size,
    .userspace_addr = (uintptr_t)guest_ram,
};
ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &region);

// 4. vCPUの作成:
for (int i = 0; i < num_vcpus; i++) {
    vcpu_fd[i] = ioctl(vm_fd, KVM_CREATE_VCPU, i);
    mmap_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
    vcpu->kvm_run = mmap(NULL, mmap_size, ..., vcpu_fd[i], 0);
    // kvm_runは共有メモリ: KVMはここにexit_reasonとレジスタ状態を書き込む
}

vCPU実行ループ

// QEMU: kvm_cpu_exec() (accel/kvm/kvm-all.c)
int kvm_cpu_exec(CPUState *cpu) {
    struct kvm_run *run = cpu->kvm_run;
    int ret;

    do {
        // 1. 保留中の割り込みを注入 (ある場合)
        if (cpu->interrupt_request)
            kvm_arch_put_registers(cpu);

        // 2. ゲストに入る!
        ret = ioctl(cpu->kvm_fd, KVM_RUN, 0);

        // 3. VM exit → 処理:
        switch (run->exit_reason) {
        case KVM_EXIT_IO:
            kvm_handle_io(run->io.port, run->io.direction, run->io.size, run->io.count,
                          run->io.data_offset);
            break;
        case KVM_EXIT_MMIO:
            address_space_rw(run->mmio.phys_addr, ...);
            break;
        case KVM_EXIT_IRQ_WINDOW_OPEN:
            // 割り込みを注入可能
            break;
        case KVM_EXIT_SHUTDOWN:
            // ゲストのトリプルフォールト → 停止
            return -1;
        }
    } while (ret == 0);

    return ret;
}

IOスレッドとvirtio処理

// QEMU iothread: 独立したevent loop
//   → eventfd (virtio kick) でのepoll
//   → QEMUがvirtioリクエストを処理:
//       virtio-blk: read/write → ホストファイル (qcow2/raw) へのマッピング
//       virtio-net: tap fd → Linux tun/tap の読み書き
//
// vhost (QEMUのバイパス):
//   ioeventfd + irqfd → カーネルが直接処理
//   → QEMUは設定のみを行い、データパスには関与しない

ライブマイグレーション

QEMU + KVMの連携:
  1. ソース側: すべてのゲストRAMをread-onlyとしてマーク
     → 以降の書き込みでライトフォールトが発生 → ダンティページを記録
  2. 反復コピー: RAMをプリコピーでターゲットへ転送
  3. ダンティページ収束: 各ラウンドでdirty rateが減少 → 閾値に到達
  4. stop-and-copy: ソースVMを一時停止 → 最後のダンティページとレジスタ状態をコピー → ターゲットで起動
  5. ダウンタイムは約100ms (ワークロードによる)

参考

  • ソースコード⁠: QEMU accel/kvm/kvm-all.c (KVMアクセラレーションのコア), Linux virt/kvm/kvm_main.c
  • カーネルドキュメント⁠: Documentation/virt/kvm/api.rst (ioctl APIリファレンス)

キーワード: KVM_RUN, kvm_run, ioctl, KVM_CREATE_VM, vCPUスレッド, iothread, ライブマイグレーション