TL

ネストした仮想化とシャドウVMCS

VMの中でVMを動かす仕組みを原理から理解できます。物理CPUは一段しか仮想化を持たないのに、なぜ多段が成立するのか。トラップ転送、シャドウVMCS、シャドウEPTによる多段変換の畳み込みと、その性能オーバーヘッドの正体を押さえられます。

応用仮想化ネスト仮想化VMCSEPTVT-x最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.物理CPUのVMXは一段ぶんしかないため、L1ハイパーバイザがL2を直接走らせることはできず、L0がL1のVMLAUNCH/VMREADなどをVM exitで捕まえてエミュレートする「トラップ転送」でネストを成立させる。
  • 2.L1が作る仮想VMCS(VMCS12)とL0が物理CPUへ渡すVMCS(VMCS02)の二重管理がボトルネックで、VMCS shadowingはL1のVMREAD/VMWRITEをVM exitなしにHW処理させ、シャドウEPTはGPA→HPAの二段の写像を一枚に畳み込む。
  • 3.オーバーヘッドの主因は多段化で増幅するVM exitと、最悪で段数の積に膨らむアドレス変換ウォークにあり、shadowingとEPTの畳み込みで実用域まで下げている。

なぜ「一段ぶん」のCPUで多段が成立するのか

ネストした仮想化とは、仮想マシンの中でさらにハイパーバイザを動かし、その上で別の VM を走らせる構成です。クラウドの中で自前のハイパーバイザを検証したい、コンテナ内でエミュレータを回したい、といった実務要求から需要が高まりました。

ところが物理 CPU の VT-x/AMD-V が提供する VMX root/non-root は 一段ぶん しかありません。論理 CPU は同時に「root とその一つ下の non-root」しか区別できず、ハードウェアに「non-root の中のさらに non-root」という入れ子の概念は存在しません。にもかかわらずネストが動くのは、最下層のハイパーバイザがソフトウェアで入れ子を「演じる」からです。

役割を層で固定して呼びます。

L0 : 物理CPU上で唯一 VMX root 権限を持つハイパーバイザ(例 KVM)
L1 : L0 から見れば普通のゲスト。だが自身も「自分は root だ」と信じて動く
L2 : L1 が動かしているつもりのゲスト(実体は L0 が走らせる)

核心は一文に尽きます。物理 CPU の non-root で実際に走るのは L1 と L2 のどちらか一方だけで、L0 が両者を時分割し、L1 が発行する仮想化命令をすべて捕まえて辻褄を合わせる。 L1 は自分が VMLAUNCH で L2 を起動したと信じていますが、その命令は L0 への VM exit に化けます。

トラップ転送:L1の特権操作をL0が代演する

L1 は non-root モードで動くゲストです。non-root では VMX 命令(VMLAUNCHVMRESUMEVMREADVMWRITEVMPTRLD など)の実行は無条件で VM exit を起こすよう CPU が定めています。つまり L1 が「L2 を起動しよう」とした瞬間、制御は物理 root の L0 へ落ちます。L0 はこの exit を解釈し、L1 の意図を物理ハードウェアの操作に翻訳して代演します。これが トラップ転送(trap forwarding) または VMX 命令のエミュレーション です。

L2 を走らせる流れを原理的に追うと次のようになります。

L1(自分は root のつもり、実体は non-root):
  VMWRITE で「VMCS12」を構成   ← VM exit → L0 が値を控える
  VMLAUNCH                     ← VM exit → L0 が起動を代演
        ↓ ここで L0 が動く
L0(本物の root):
  VMCS12 と L0 自身の方針を統合して「VMCS02」を物理CPUへロード
  VMRESUME                     ← 物理 VM entry。L2 が non-root で走る
        ↓
  L2 が走行 → 介入が要る事象で物理 VM exit → L0 へ
  L0 は exit reason を見て
    ・L1 に見せるべき exit なら VMCS12 に詰めて L1 を再開(仮想 VM exit を注入)
    ・L0 が握るべき exit なら自分で処理して L2 を再開

ここで決定的なのは、物理 CPU から見れば L1 も L2 も同じ一段の non-root ゲストにすぎない という点です。L0 は VMCS をすげ替えることで「いま走らせているのは L1 か L2 か」を切り替えます。L2 の物理 VM exit が起きたとき、L0 はそれを「L1 が処理すべきもの」か「L0 が握るべきもの」かに振り分け、前者なら L1 に対して 仮想 VM exit を演出します。L1 はあたかも自分のゲストが exit したかのように振る舞えるわけです。

L1 は exit したことに気づかない

トラップ転送の巧妙さは、L1 から見て VMX が「普通に動いている」ように見える点です。L1 が VMLAUNCH を発行して制御が戻ってきたとき、L1 はそれを「L2 が VM exit してきた」と解釈します。実際にはその間に物理 VM exit が複数回起き、L0 が裏で全部処理しています。L1 は自分が non-root で走っていることを原理的に検知できません。

シャドウVMCS:VMCS12とVMCS02の二重管理

ネストの最初のボトルネックは VMCS の二重管理です。L1 が認識して読み書きする VMCS を VMCS12(L1 が L2 のために作る制御構造)、L0 が物理 CPU へ実際にロードする VMCS を VMCS02 と呼びます。VMCS01 は L0 が L1 のために持つものです。

VMCS01 : L0 が L1 を走らせるための、物理CPUに載る VMCS
VMCS12 : L1 が「L2 用」と信じて構成する、L0 から見れば単なるメモリ上のデータ
VMCS02 : L0 が VMCS12 を翻訳して物理CPUに載せる、L2 を実際に走らせる VMCS

L0 は VMCS12 をそのまま物理 CPU に載せられません。L1 の指定するホスト状態は L1 の世界の値で、物理 CPU が必要とするのは L0 の値だからです。そこで L0 は VMCS12 のゲスト状態は引き継ぎ、ホスト状態とコントロールは L0 の方針で上書きした VMCS02 を合成 します。問題は、L1 が VMCS を構成するために VMREADVMWRITE大量に 発行することです。素朴な実装ではその一回一回が VM exit になり、ネストの起動・exit 処理が遅くなる主因になりました。

ここで効くのが VMCS shadowing(シャドウ VMCS) です。物理 CPU に「シャドウ VMCS」を1枚割り当て、VMCS12 をそこに対応づけておくと、L1 の VMREADVMWRITEVM exit せずにシャドウ VMCS へ直接読み書き されます。

項目shadowing 無しVMCS shadowing 有り
L1 の VMREAD/VMWRITE毎回 VM exitexit なしでシャドウVMCSへ直接アクセス
どのフィールドを許すか全件 L0 が代行VMREAD/VMWRITE bitmap で個別制御
L0 介入の対象全フィールドbitmap で「要介入」とした少数のみ
主な効果起動・exit処理が重いL1由来の exit を桁で削減

どのフィールドを exit させ、どれをシャドウに任せるかは VMREAD bitmap/VMWRITE bitmap で個別に指定します。L0 が同期を握っておきたい少数のフィールドだけ exit させ、残りは L1 に直接触らせる。これにより、ネスト構成で支配的だった「L1 の VMCS 操作起因の VM exit」が大きく減ります。VMCS shadowing はハードウェア仮想化拡張で触れた exit 削減最適化の、ネスト版にあたります。

シャドウEPT:二段の写像を一枚に畳み込む

メモリ仮想化はネストでさらに厄介になります。アドレス空間が三層に増えるためです。

L2 ゲスト物理 (L2-GPA)  ── L1 の EPT が変換するつもりの一段目
        ↓
L1 ゲスト物理 (L1-GPA)  ── L1 から見た「物理メモリ」。だが本物ではない
        ↓
ホスト物理 (HPA)        ── L0 の EPT が変換する本物の物理アドレス

L1 は「L2-GPA → L1-GPA」を自分の EPT(EPT12 と呼ぶ)で変換しているつもりです。一方 L0 は「L1-GPA → HPA」を自分の EPT(EPT01)で変換します。物理 CPU の EPT 機構は 一段ぶん しか持たないため、この二段をそのまま CPU に並べることはできません。

解決策が シャドウ EPT(EPT02) です。L0 は EPT12 と EPT01 を 関数合成 し、L2-GPA → HPA を直接引ける一枚の EPT02 を作って物理 CPU に載せます。

EPT12 : L2-GPA → L1-GPA   (L1 が構成、L0 から見ればデータ)
EPT01 : L1-GPA → HPA      (L0 が構成)
EPT02 : L2-GPA → HPA      (L0 が両者を合成して物理CPUへ)  = シャドウEPT

合成された EPT02 を使えば、L2 のメモリアクセスは物理 CPU の一段 EPT で L2-GPA → HPA まで一気に解決でき、L2 のページアクセスのたびに L0 へ exit する必要がなくなります。代償は同期です。L1 が EPT12 を書き換えても CPU はそれを知らないため、L0 は EPT12 の変更を検知して EPT02 を作り直す必要があります。L0 は EPT12 を読み取り専用にしておき、L1 の書き込みで EPT violation を起こさせて追従する、といった手法を採ります。

シャドウEPTは「合成」、シャドウVMCSは「直結」

名前が似ていますが役割は逆向きです。シャドウ VMCS は L1 の VMCS アクセスを exit させずに通す「直結」装置。シャドウ EPT は二枚の EPT を一枚に畳み込む「合成」装置です。前者は exit を減らし、後者は多段アドレス変換を一段に圧縮します。混同すると性能の議論がずれます。

オーバーヘッドの原理:exit の増幅と変換ウォークの積

ネスト仮想化が重い理由は二つの軸に整理できます。

第一は VM exit の増幅 です。非ネストでも VM exit は数百〜千サイクル規模ですが、ネストでは L2 の一つの事象が「物理 exit → L0 処理 → L1 への仮想 exit → L1 処理 → L1 の VMRESUME → また物理 exit」と 複数の exit に化ける ことがあります。L0 が握れる exit はその場で処理して L1 に見せませんが、L1 が処理すべき exit は往復が増えます。ゆえにネストの性能は「L2 の事象のうち何割を L0 だけで完結できるか」で決まります。

第二は アドレス変換ウォークの積 です。非ネストの二段変換でも、ゲスト N 段 × EPT M 段で最悪 (N+1)×(M+1) - 1 回のメモリ参照になりました。ネストでは EPT02 が L2-GPA → HPA を直接引くため 物理ウォーク自体は一段 EPT のまま に保てるのがシャドウ EPT の効能ですが、EPT02 の再構築コストや TLB ミス時の代償は大きくなります。だからこそ TLB の ASID/PCID と VPID によるエントリ保持や、ヒュージページによる段数圧縮が、ネストでは非ネスト以上に効きます。

L0 が exit を握り切るほど速い

ネスト最適化の指針は一貫しています。L2 由来の事象を、できるだけ L0 だけで完結させて L1 を起こさない こと。VMCS shadowing は L1 の VMCS 操作を、シャドウ EPT は L2 のページアクセスを、それぞれ L1 を経由させずに済ませる装置です。両者が効くほど L1 への仮想 exit が減り、ネストのオーバーヘッドが実用域に収まります。KVM/QEMU の構成 でも、ネスト有効化時はこれらの機構の有無が性能を大きく左右します。

三つの構造を一行で

VMCS12=L1が作る仮想VMCS/VMCS02=L0が物理CPUに載せる実体/シャドウVMCS=L1のVMREAD/VMWRITEをexitなしで通す機構。さらに シャドウEPT(EPT02)=EPT12とEPT01を合成しL2-GPA→HPAを一段で引く 機構。この四点を分けて覚えると、ネストの性能議論で迷いません。

まとめ

  • 物理 CPU の VMX は一段ぶんしかないため、ネストは L0 が L1 の VMX 命令を VM exit で捕まえてエミュレートする トラップ転送 で成立する。L1 は自分が non-root で走っていると気づかない。
  • L0 は L1 の VMCS12 を翻訳して物理 CPU に載せる VMCS02 を合成する。VMCS shadowing は L1 の VMREADVMWRITE を exit なしでシャドウ VMCS に通し、ネストの主要なオーバーヘッドを削る。
  • メモリは L2-GPA → L1-GPA → HPA の三層になる。シャドウ EPT(EPT02) は L1 の EPT と L0 の EPT を合成し、L2-GPA → HPA を一段で引けるようにして L2 のページアクセス起因の exit を消す。
  • オーバーヘッドの主因は VM exit の増幅変換ウォークの積 であり、shadowing と EPT 合成、そして VPID・ヒュージページの活用で実用的な水準まで抑える。

OS Article

ネストした仮想化とシャドウVMCSを実務で読む

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

解決すること

仮想化

比較で見る軸

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

導入後に効く点

L1が作る仮想VMCS(VMCS12)とL0が物理CPUへ渡すVMCS(VMCS02)の二重管理がボトルネックで、VMCS shadowingはL1のVMREAD/VMWRITEをVM exitなしにHW処理させ、シャドウEPTはGPA→HPAの二段の写像を一枚に畳み込む。

先に潰すリスク

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

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

判断チェックリスト

  • 自社の用途が「仮想化 / ネスト仮想化」に近いか確認する。
  • 強みである「物理CPUのVMXは一段ぶんしかないため、L1ハイパーバイザがL2を直接走らせることはできず、L0がL1のVMLAUNCH/VMREADなどをVM exitで捕まえてエミュレートする「トラップ転送」でネストを成立させる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

仮想化ネスト仮想化VMCSEPTVT-x仮想化ネスト仮想化VMCS