TL

virtioとパラ仮想化I/Oの仕組み

ゲストI/Oが遅い原因はVM exitの乱発。virtioが共有リングでI/Oをまとめ、vhostでデータ経路をカーネルへ降ろす原理を押さえれば、仮想マシンの性能勘所が掴めます。

応用仮想化virtiovhostI/OLinux最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.virtioは実機の模倣をやめ、ゲストとホストが共有メモリ上のvirtqueue(記述子テーブル+available/used ring)でI/Oをやり取りする準仮想化デバイスである。
  • 2.ゲストは記述子をリングへ積んでから1回だけkickで通知し、ホストはまとめて処理してused ringへ完了を積む。これでVM exitと割り込みをバッチ化して削減する。
  • 3.vhost-net/vhost-blkはvirtqueueの処理をホストカーネル内のスレッドへ降ろし、QEMUへの往復を消す。通知はeventfd(kick/call)、データはDMA的にカーネルが直接触る。

なぜエミュレートデバイスは遅いのか

KVM/QEMUが e1000 のような実在 NIC をエミュレートすると、ゲストは本物のハードウェアと同じ手順でデバイスを叩きます。送信ディスクリプタの設定、レジスタ書き込み、ステータスの読み出し——その一つひとつが MMIO アクセスとなり、VT-x/AMD-Vの VM exit を引き起こします。VM exit はゲストの実行を止め、CPU 状態を退避してホストへ制御を戻す高コストな遷移です。1 パケット送るだけで何度も exit が発生すれば、スループットは出ません。

問題の根は「ゲストが自分を実機だと思い込んでいる」点にあります。準仮想化(パラ仮想化)はこの前提を捨て、ゲストに「自分は仮想環境だ」と認めさせることで、仮想化に最適化した I/O 経路を設計します。virtio はその標準化された枠組みで、実機レジスタの忠実な模倣をやめ、ゲスト・ホスト間で合意した抽象デバイスを使います。

virtioは「デバイスの抽象規格」

virtio は特定の実装ではなく、デバイス検出(virtio-pci など)、設定、データ転送の作法を定めた規格です。virtio-net(NIC)、virtio-blk(ブロックデバイス)、virtio-scsi、virtio-gpu などが同じ転送機構を共有します。共通の心臓部が次に述べる virtqueue です。

virtqueue:共有メモリ上のリングバッファ

virtio の核心は virtqueue という共有リングです。ゲスト物理メモリ上に確保され、ホスト(QEMU や vhost)も同じ領域を参照します。split virtqueue は次の三つの部分からなります。

構成要素書く側役割
記述子テーブルゲストバッファのゲスト物理アドレス・長さ・読み書き方向・連結
available ringゲストホストに処理してほしい記述子の先頭indexを並べる
used ringホスト処理を終えた記述子のindexと書き込み長を返す

流れは一方通行の二本のリングで表せます。ゲストはデータバッファを記述子テーブルに登録し、その先頭 index を available ring へ積みます。記述子は next で連結でき、1 つの論理要求を複数バッファ(ヘッダ+データ+ステータス)に分けて表現します。ホストは available ring を読んで記述子をたどり、バッファに対して読み書きし、完了したら index と実際の転送長を used ring へ書き戻します。ゲストは used ring を見て完了を回収します。

ゲスト                          virtqueue(共有メモリ)            ホスト(QEMU/vhost)
  │ バッファを記述子に登録
  │ 先頭indexを積む ───────────►  available ring
  │ kick(1回の通知)─────────►                ───► 記述子をたどり処理
  │                              used ring  ◄───── 完了index+長さを書く
  │ used ringから回収 ◄─────────
  │ 完了割り込み(call) ◄──────────────────────────  interrupt注入

肝は バッチ化 です。多数の I/O 記述子を available ring へ積んでから、1 回だけ kick(通知) で相手に知らせます。これにより 1 要求あたりの VM exit が償却され、ホスト側も複数要求をまとめて処理できます。完了側も同様で、used ring に複数積んでから 1 回の割り込みで回収すれば、割り込み回数を抑えられます。

kickとcall:通知をいかに減らすか

リングへ積むだけでは相手は気づきません。ゲスト→ホストの通知が kick、ホスト→ゲストの通知が call です。kick は典型的には virtio-pci の通知レジスタへの書き込みで、これが VM exit(または vhost では eventfd シグナル)になります。call はホストからゲストへの割り込み注入です。

通知は高コストなので、virtio は両方向で抑制機構を持ちます。ゲストは VRING_AVAIL_F_NO_INTERRUPT で「当面、完了割り込みは要らない」とホストへ伝え、ホストは used ring を積んでも割り込みを送りません。逆に ホストは VRING_USED_F_NO_NOTIFY で「kick は要らない」とゲストへ伝え、ゲストはポーリングで処理が進んでいる間 kick を省きます。さらに新しい event_idx 機能では、「ここまで進んだら 1 回だけ通知して」という閾値を相手に渡し、通知を必要最小限にします。

高負荷時ほど通知が減るのが理想

これは NIC の割り込み合体(interrupt coalescing)や blk-mq のポーリングと同じ思想です。負荷が低いときは即座に通知して低遅延を保ち、負荷が高いときは相手がすでに処理ループを回しているので通知を省いてスループットを稼ぐ。トラフィックが増えるほど 1 要求あたりの通知コストが下がる、という性質が望ましいのです。

virtio-netとvirtio-blkのデータフロー

転送機構は共通でも、virtqueue の使い方はデバイスごとに異なります。virtio-net は最低 2 本のキュー、受信用(RX)と送信用(TX)を持ちます。送信ではゲストが virtio_net_hdr(チェックサムオフロードや GSO の指示を含む)とパケット本体を記述子に積んで kick し、ホストが取り出してカーネルのネットワークデータパスへ流します。受信では、ゲストがあらかじめ空バッファを available ring に並べて「ここへ書いてくれ」と提示し、ホストが届いたパケットを書き込んで used ring で返す——受信は事前にバッファを供給しておくのが要点です。マルチキュー(virtio-net-mq)では vCPU ごとに RX/TX 対を持ち、並列にさばきます。

virtio-blk は 1 本(または複数)のキューで、各記述子チェーンが 1 つの I/O 要求を表します。先頭に種別(read/write)とセクタ番号を持つヘッダ、続いてデータバッファ、末尾に 1 バイトのステータスを置く三段構成が典型です。ゲストは要求を積んで kick、ホストが実ストレージへ読み書きしてステータスとデータを返します。複数要求を同時に in-flight にできるため、深いキュー深度で並列 I/O を引き出せます。

vhost:データ経路をカーネルへ降ろす

virtqueue の処理をユーザー空間 QEMU で回すと、kick のたびにゲスト→ホストカーネル→QEMU と戻る往復が残ります。vhost-net / vhost-blk はこのデータ経路をホストカーネル内のスレッドへ移します。QEMU はセットアップ時に、virtqueue のゲスト物理アドレス、通知用の eventfd(kick 用 / call 用)、メモリ配置テーブルをカーネルへ登録するだけ。以降の実際の転送はカーネルの vhost スレッドが担います。

[制御パス] QEMU ──ioctl──► vhostカーネルモジュール
            (virtqueueアドレス・eventfd・メモリマップを登録)

[データパス] ゲストkick → kick eventfd → vhostスレッドが起床
             vhostスレッドがvirtqueueを直接処理(QEMUを経由しない)
             完了 → call eventfd → KVMがゲストへ割り込み注入

要点は KVM の irqfd / ioeventfd との連携 です。ゲストの kick は ioeventfd 経由で直接 vhost を起こし、QEMU をまったく経由しません。完了割り込みも irqfd 経由で KVM が直接ゲストへ注入します。制御(セットアップ)は QEMU、データ転送と通知はカーネルで閉じるという二段の分業で、ユーザー空間への戻りが消えてスループットが伸びます。

それでもコピーは残る

vhost はユーザー空間往復を消しますが、ゲストバッファとホスト側ソケット/ブロック層の間のデータコピーは原則残ります。これをさらに削るのが vhost-user(DPDK 等のユーザー空間データプレーンへ委譲)や、デバイスを物理 NIC へ近づける vDPA、あるいは IOMMU を使った SR-IOV パススルーです。virtio はソフトウェア準仮想化の中での最適化であり、コピーゼロの万能解ではありません。

一行整理

virtio=準仮想化I/Oの規格(実機模倣をやめ抽象デバイスを合意)。virtqueue=共有リング(記述子テーブル+available/used ringでI/Oをバッチ化)。kick/call=双方向の通知(抑制機構で高負荷ほど通知を削減)。vhost=データ経路をカーネルへ(eventfd/irqfdでQEMU往復を消す)。

まとめ

  • エミュレートデバイスはゲストが実機作法でレジスタを叩くため MMIO 由来の VM exit が乱発し遅い。virtio は準仮想化で実機模倣をやめ、専用の効率的な I/O 経路を使う。
  • virtqueue は共有メモリ上のリングで、ゲストが記述子を available ring に積み、ホストが処理して used ring へ完了を返す。複数要求を積んで 1 回 kick することで exit と割り込みをバッチ化する。
  • kick(ゲスト→ホスト)と call(ホスト→ゲスト)は抑制フラグや event_idx で削減でき、高負荷時ほど 1 要求あたりの通知コストが下がる。
  • vhost はデータ経路をカーネル内スレッドへ降ろし、kick/call を eventfd・irqfd 経由でやり取りして QEMU への往復を消す。制御は QEMU、転送はカーネルという分業である。

OS Article

virtioとパラ仮想化I/Oの仕組みを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

仮想化

比較で見る軸

難易度: advanced / カテゴリ: OS / タグ数: 5

導入後に効く点

ゲストは記述子をリングへ積んでから1回だけkickで通知し、ホストはまとめて処理してused ringへ完了を積む。これでVM exitと割り込みをバッチ化して削減する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
OS
タグ数
5

判断チェックリスト

  • 自社の用途が「仮想化 / virtio」に近いか確認する。
  • 強みである「virtioは実機の模倣をやめ、ゲストとホストが共有メモリ上のvirtqueue(記述子テーブル+available/used ring)でI/Oをやり取りする準仮想化デバイスである。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

仮想化virtiovhostI/OLinux仮想化virtiovhost