KVMとQEMUによる仮想マシンの実行モデル
なぜKVMだけでVMが動かず、QEMUと組むのか。カーネルがVMX/SVMを握り、QEMUがデバイスを演じる分業を、vCPUスレッドとvirtioまで原理で押さえ、仮想化の性能勘所を掴めます。
- 1.KVMはカーネルモジュールで、VT-x/AMD-Vの制御(VMCS/VMCB操作、VM entry/exit)だけを担い、デバイスのエミュレーションは持たない。
- 2.QEMUはユーザー空間プロセスで、vCPUを各々スレッド化してKVMのioctlでゲストを走らせ、I/O起因のVM exitを受け取ってデバイスをエミュレートする。
- 3.virtioはゲスト・ホスト共有のリングバッファでI/Oをまとめ、VM exitと割り込みを削減する準仮想化デバイスで、vhostでデータ経路をカーネルへ降ろせる。
なぜKVMとQEMUは分業するのか
Linux の代表的な仮想化スタックは、しばしば「KVM/QEMU」と一語で語られますが、実体は責務の異なる二つの部品です。KVM(Kernel-based Virtual Machine)はカーネルモジュールで、VT-x/AMD-Vの制御という、カーネル特権でしかできない仕事だけを引き受けます。QEMU はユーザー空間のプロセスで、ディスク・ネットワーク・割り込みコントローラといった周辺デバイスを丸ごとエミュレートします。
この分割には理由があります。VMX root/non-root モードの遷移、VMCS の VMREAD/VMWRITE、VMLAUNCH/VMRESUME は特権命令で、カーネルでしか実行できません。一方デバイスエミュレーションは複雑かつ多種で、バグや脆弱性も入り込みやすい。これをカーネルに置けば攻撃面が膨らみます。そこで最小限の特権処理(CPU/メモリ仮想化)だけをカーネルに残し、肥大しやすいデバイス模倣はユーザー空間へ追い出すという、マイクロカーネル的な切り分けを採っています。
KVM 単体では VM は完成しません。KVM が提供するのは「ゲストを non-root で走らせ、VM exit を受け取って返す」という素のループだけです。ディスクが見える・NICが繋がるといった「マシンらしさ」は QEMU(あるいは別のVMM、例: cloud-hypervisor)が与えます。KVM は機構、QEMU は政策、という分担です。
/dev/kvm:ioctlで組み立てる仮想マシン
QEMU は KVM をキャラクタデバイス /dev/kvm への ioctl として使います。システムコールの一種である ioctl が、ユーザー空間 QEMU とカーネル KVM の唯一の境界です。VM 構築の骨格はおおむね次の順序になります。
kvm = open("/dev/kvm", O_RDWR);
vmfd = ioctl(kvm, KVM_CREATE_VM, 0); // VM を1つ作る
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &mem); // ゲスト物理メモリを登録
vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, 0); // vCPU を1つ作る
run = mmap(..., vcpufd, 0); // vCPU と共有する制御領域
for (;;) {
ioctl(vcpufd, KVM_RUN, 0); // ← VM entry。戻ると VM exit
switch (run->exit_reason) { // 共有領域に理由が入る
case KVM_EXIT_IO: /* QEMUがI/Oをエミュレート */ break;
case KVM_EXIT_MMIO: /* QEMUがMMIOをエミュレート */ break;
case KVM_EXIT_HLT: /* ゲストが停止 */ break;
}
}
要は KVM_RUN の ioctl がそのまま VM entry に対応します。カーネル内で KVM は VMCS をロードして non-root に入り、ゲストを走らせ、VM exit が起きると ioctl から戻ります。なぜ exit したかは mmap で共有する kvm_run 構造体の exit_reason に書かれ、QEMU はそれを見て分岐します。ゲストのメモリは QEMU プロセスの仮想アドレス空間の一部として確保され(KVM_SET_USER_MEMORY_REGION でゲスト物理→ホスト仮想の対応を登録)、EPT/NPT はこの登録に基づいて構成されます。
vCPUスレッドモデル
ここが実行モデルの核心です。KVM の vCPU は専用ハードウェアではなく、QEMU プロセス内の1本のスレッドです。ゲストの各 CPU に対し QEMU はスレッドを1つ起こし、そのスレッドがひたすら KVM_RUN を呼ぶループを回します。
QEMU プロセス(ユーザー空間)
├─ メインループスレッド … タイマ・モニタ・I/Oイベント処理
├─ vCPU スレッド 0 → ioctl(KVM_RUN) ⇄ [カーネル: non-rootでゲスト実行]
├─ vCPU スレッド 1 → ioctl(KVM_RUN) ⇄ [カーネル: non-rootでゲスト実行]
└─ vCPU スレッド N → ...
この設計の含意は大きいものがあります。第一に、vCPU はホストの通常スレッドなので、ホストのスケジューラ(CFS/EEVDF)がそのまま物理コアへ割り付ける。vCPU の優先度・CPU アフィニティ・cgroup 制限は、普通のプロセスと同じ手段で効きます。第二に、ゲストが KVM_RUN で non-root にいる間、そのスレッドはカーネル内で「ゲストを実行中」という状態にあり、CPU 時間としてはゲストに食われます。第三に、ゲストが多数の vCPU を持っても物理コアが少なければ、ホストスケジューラが時分割するためオーバーコミットが成立します。
vCPU スレッドが VM exit を起こすと、いったんユーザー空間 QEMU に戻ってエミュレートし、再び KVM_RUN に入り直します。この往復はVT-xの解説で触れたとおり高コストで、I/O が多いゲストでは exit 回数が性能を直接左右します。後述の virtio は、まさにこの exit を減らすための仕組みです。
トラップ&エミュレートの分担
VM exit の理由ごとに、誰が処理するかは分かれています。カーネルの KVM 内で完結させた方が速いものはカーネルに、複雑なデバイス模倣はユーザー空間 QEMU に、という配分です。
| exitの種類 | 処理する場所 | 理由 |
|---|---|---|
| CPUID・一部MSR | KVM(カーネル) | 短く頻繁で、即値を返すだけ |
| ローカルAPIC・タイマ | KVM(in-kernel irqchip) | 割り込み配送が高頻度・低遅延要求 |
| ポートI/O・MMIO | QEMU(ユーザー空間) | 対象デバイスの状態機械が複雑 |
| virtio通知 | QEMU/vhost | リング処理。vhostならカーネルで完結 |
割り込みコントローラ(APIC)を KVM 内に置く in-kernel irqchip は典型例です。割り込みのたびにユーザー空間へ戻っていては遅すぎるため、APIC エミュレーションと割り込み注入をカーネルで完結させ、さらに APICv/AVIC が効けば exit すら起こさず配送します。逆に、エミュレートする NIC やディスクコントローラの細かなレジスタ挙動は QEMU が引き受けます。**「速さが要る単純なものはカーネル、複雑なものはユーザー空間」**という一貫した基準で線が引かれています。
virtio:準仮想化I/Oで往復を減らす
エミュレートデバイス(例: e1000 NIC)は実機のレジスタを忠実に真似るため、1パケット送るだけで何度も MMIO アクセス→VM exit を招き、遅くなります。virtio はこの非効率を、ゲストに「自分は仮想環境だ」と認めさせる準仮想化で解きます。実機の模倣をやめ、仮想化に最適化した抽象デバイスをゲスト・ホストで合意するのです。
中核は virtqueue という共有リングバッファです。ゲストとホストが同じ物理メモリ領域を見て、I/O 要求の記述子をそこへ積みます。
ゲスト 共有メモリ(virtqueue) ホスト(QEMU/vhost)
│ 記述子を複数まとめてリングへ積む ─────► available ring
│ kick(1回のVM exit/通知) ─────► まとめて取り出し処理
│ ◄───── used ring に完了を積む
│ 1回の割り込みでまとめて回収 ◄───── interrupt(注入)
肝はバッチ化です。多数の I/O を共有リングへ積んでから1回だけ「kick」で相手に知らせるため、1要求あたりの VM exit と割り込みを劇的に減らせます。記述子はゲスト物理アドレスを指し、ホストはそれを自分の空間へ写してデータに触れます(ゼロコピーに近い経路も組める)。
virtio の処理を QEMU(ユーザー空間)で回すと、kick のたびにユーザー空間へ戻る往復が残ります。vhost-net / vhost-blk はこのデータ経路をカーネルのカーネルスレッドへ移し、QEMU を介さずホストカーネル内で virtqueue を処理します。これにより通知も完了割り込みもカーネル内で閉じ、ユーザー空間への戻りが消えてスループットが伸びます。制御(セットアップ)は QEMU、データ転送は vhost、という二段の分業です。
KVM=CPU/メモリ仮想化の機構(/dev/kvm の ioctl、VMCS操作、VM entry/exit)。QEMU=デバイスエミュレーションとVM組み立て(vCPUスレッド、I/O exit処理)。virtio=準仮想化I/O(virtqueueでバッチ化しexitと割り込みを削減、vhostでデータ経路をカーネルへ)。この三層で押さえると混同しません。
まとめ
- KVM はカーネルモジュールで VT-x/AMD-V の制御だけを担い、QEMU はユーザー空間でデバイスをエミュレートする。特権処理を最小化し攻撃面を絞る分業。
- QEMU は
/dev/kvmへの ioctl で VM を組み立て、KVM_RUNが VM entry に対応する。ゲストメモリは QEMU の仮想空間の一部として登録される。 - vCPU は QEMU 内の通常スレッドで、ホストスケジューラがそのまま物理コアへ割り付ける。だからアフィニティや cgroup が効き、オーバーコミットも成立する。
- virtio は共有 virtqueue で I/O をバッチ化し、VM exit と割り込みを減らす準仮想化デバイス。vhost を使えばデータ経路をカーネル内で完結できる。
OS Article
KVMとQEMUによる仮想マシンの実行モデルを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
仮想化
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 5
導入後に効く点
QEMUはユーザー空間プロセスで、vCPUを各々スレッド化してKVMのioctlでゲストを走らせ、I/O起因のVM exitを受け取ってデバイスをエミュレートする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 5
判断チェックリスト
- 自社の用途が「仮想化 / KVM」に近いか確認する。
- 強みである「KVMはカーネルモジュールで、VT-x/AMD-Vの制御(VMCS/VMCB操作、VM entry/exit)だけを担い、デバイスのエミュレーションは持たない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。