OSごとに実行環境を分ける

OSごとに割り当てるCPU、メモリ、I/O

用途ごとに物理サーバーを用意すると、使われないCPUやRAMが残り、台数に応じて設置、電力、更新の管理も増えます。開発や検証では、異なるOSやバージョンを短時間だけ動かしたい場合もあります。

そこで、物理コンピューターのCPU、RAM、デバイスを、OSごとに独立した環境として割り当てます。この環境を仮想マシン仮想マシン一台の物理コンピュータ上で動く、別のコンピュータのような環境。と呼びます。物理コンピューター側をホスト、仮想マシン内で動くOSをゲストと呼びます。

06.1
仮想マシンを動かす技術の変化

ソフトウェアでの再現に、CPUの支援が加わる

QEMUQEMU仮想マシンのメモリ、起動環境、デバイスを用意するユーザー空間のプログラム。は、CPU、メモリ、デバイスをソフトウェアで再現し、OSに変更を加えずに動かせるマシンエミュレータとして開発されました。ゲストOSを仮想環境向けに書き換えずに動かす構成を完全仮想化完全仮想化ゲストOSを修正せずに動かせるよう、CPUやデバイスの環境を用意する方式。と呼びます。QEMUは異なる種類のCPUにも対応でき、実行を止めて状態を調べられる一方、仮想マシン内のOSやアプリの命令をホストCPU向けに変換する処理には時間がかかります。

2000年代半ば、IntelとAMDは、ゲストOSの命令を物理CPUで実行しながら、ホストへ制御を戻せる機能をx86 CPUへ加えました。QumranetのAvi Kivityらは、この機能をLinuxから使うためにKVMKVMLinuxをハイパーバイザとして使うための、カーネル内の仮想化サブシステム。を開発しました。

KVMとCPUの仮想化支援を使うと、多くの通常命令はQEMUによる命令変換を通らず、物理CPUで直接実行されます。命令変換に使うCPU時間が減るため、QEMUだけでCPUをエミュレーションする構成よりも、仮想マシン内のプログラムを速く実行できます。

2005年の設計論文

QEMUがコンピューターの動作をソフトウェアで再現する方法

Fabrice Bellardは、異なるCPUの命令を実行時に変換し、OSを変更せずに動かすQEMUの設計を発表しました。OSの検証、状態の保存と復元、組み込み機器の開発にも使える構成でした。

2006-10-19

KVMがLinuxカーネルへ提案される

Avi Kivityは、Intelの仮想化支援をユーザー空間へ公開する/dev/kvmをLKMLへ投稿しました。仮想マシンをプロセス、仮想CPUをスレッドとして扱う設計です。

2007–2008

KVMがLinuxへ入り、QEMUから利用できるようになる

KVMはLinux 2.6.20へ入りました。当初は変更を加えたQEMUを併用し、2008年には基本的なKVM対応がQEMU本体へ取り込まれました。

KVMは、Linuxをハイパーバイザハイパーバイザ仮想マシンへCPUやメモリを割り当てる仕組み。として使うためのカーネル内の仮想化サブシステムです。仮想マシンと仮想CPUの状態、ゲストメモリの対応、仮想マシン内の命令実行、割り込みコントローラやタイマーの一部を管理します。仮想CPUはホスト上のスレッドとして動くため、Linuxのスケジューラが物理CPUへ割り当てます。

QEMUは、ゲストRAMに使うメモリを確保し、起動用ファームウェアと仮想デバイスを用意します。KVM APIを使う構成では、メモリをKVMへ登録し、仮想CPUの状態を設定して実行を開始します。現在もQEMUはKVMを使わずにCPUをエミュレーションでき、KVMもQEMU以外のユーザー空間プログラムから利用できます。

仮想マシンの内側ゲスト
実行する処理アプリ
OSの中心ゲストカーネル
I/Oを要求デバイスドライバ
物理コンピューター側ホスト
ユーザー空間

QEMU

構成を読み、ゲストRAMに使うメモリを確保し、起動用ファームウェアとユーザー空間のデバイスモデルを用意します。

ioctlで操作するKVM API/dev/kvmKVM_CREATE_VM · KVM_CREATE_VCPU · KVM_RUN
カーネル空間

KVM

VMと仮想CPUの状態、ゲストメモリの対応、仮想マシン内の命令実行、割り込みとタイマーの一部を管理します。

Linuxカーネルの既存機能スケジューラメモリ管理ファイルシステムとネットワークデバイスドライバアクセス制御
命令を実行物理CPU
ゲストRAMの実体物理RAM
通信・保存物理デバイス
Linuxカーネル内のKVM

仮想マシンを制御するAPIとカーネル内の処理

/dev/kvmを通してVM、仮想CPU、ゲストメモリ、一部の仮想デバイスを設定します。MMU仮想化、VM exit、割り込み、タイマーなどをカーネル内で処理します。

KVMベースの仮想化基盤

Linux、QEMU、管理ツールまで含む構成

製品資料では、Linuxのスケジューラ、メモリ管理、ストレージ、ネットワーク、セキュリティと、QEMUやlibvirtを組み合わせた全体を広く「KVM」と呼ぶ場合があります。

KVM APIでは、ユーザー空間のプログラムが/dev/kvmを開きます。KVM_CREATE_VMで仮想マシンを作り、KVM_SET_USER_MEMORY_REGIONでゲストが使うメモリを登録し、KVM_CREATE_VCPUで仮想CPUを作ります。仮想CPUを担当するスレッドがKVM_RUNを呼ぶと、KVMは設定済みのCPU状態から仮想マシン内のOSやアプリの命令を実行します。ユーザー空間で処理すべきVM exitが起きると、KVM_RUNから呼び出し元へ制御が返ります。

06.2
仮想マシン内のOSやアプリを物理CPUで動かす

Intel VT-xとAMD-VがCPUの実行状態を分ける

OSのカーネルは、ページテーブルの変更や割り込み制御など、強い権限が必要な処理を行います。複数のゲストカーネルを安全に動かすには、ゲストへOSとしての実行環境を見せながら、物理コンピューター全体の制御はホストに残す必要があります。

x86 CPUには、この切り替えをハードウェアで支援する機能があります。IntelはVT-xを実現する命令体系をVMX、AMDはAMD-Vを実現する命令体系をSVMと呼びます。KVMはこれらを使い、ゲスト用のCPU状態を保ちながら命令を実行します。

図の対象

仮想CPUでOSやアプリの命令を実行する流れ

仮想マシンと仮想CPUの作成が終わり、ゲストOSが動いている場面を扱います。図は仮想CPUごとの処理を追います。QEMUのスレッドがKVM_RUNを呼ぶと、物理CPUはその仮想CPUへ割り当てられたOSやアプリの命令を実行します。VM exitが起きた後は、KVMまたはQEMUが原因に応じた処理を行い、次の命令実行へ進みます。

実行が続く範囲物理CPUは、VM exitが起きるまで仮想マシン内のOSやアプリの命令を実行し続けます。処理後の流れKVM内で処理した場合はVM entryへ進みます。QEMUへ処理を返した場合は、処理後に再びKVM_RUNを呼びます。
ホストのユーザー空間QEMUスレッド仮想CPUを担当
ホストのカーネル空間KVMCPU状態とVM exitを管理
ハードウェア物理CPUVT-x / AMD-V
ユーザー空間の処理が必要な場合QEMUデバイスモデルなど
KVM API

仮想CPUを担当するスレッドがKVM_RUNを呼ぶ

QEMUでは、通常、仮想CPUごとにホストスレッドを用意します。そのスレッドがKVM_RUNを呼び、カーネル空間のKVMへ実行を移します。

複数の仮想CPUがある場合:VM exitの間、該当する仮想CPUではOSやアプリの命令実行が進みません。ほかの仮想CPUは、別の物理CPUで動き続ける場合があります。

CPUの仮想化

VT-x / VMX・AMD-V / SVM

仮想CPUを物理CPUで実行し、ホストの処理が必要なイベントでKVMへ制御を戻します。

I/Oメモリの保護

VT-d・AMD-Vi

デバイスが読み書きできる物理メモリの範囲をIOMMUで制御します。デバイスを仮想マシンへ直接割り当てるときにも使います。

06.3
物理ハードウェアとの対応

ゲストがCPU、RAM、I/Oを使うまで

仮想マシンの中では、ゲストOSが自分専用のCPU、RAM、デバイスを使っているように動きます。実際のCPU、RAM、デバイスはホスト上のほかの処理と共有するため、ホストはゲストへ見せる状態と物理ハードウェアの状態を対応させます。

この対応には、仮想マシン内のOSやアプリの命令を物理CPUで実行する処理、ゲストOSが使うアドレスを物理RAM上の位置へ変換する処理、ゲストOSのI/O要求をホスト側へ渡す処理があります。以下の図では、それぞれに何が加わり、ハードウェア支援やvirtioがどの処理を減らすのかを順に示します。

01 CPU

仮想マシン内の命令を実行する

ソフトウェアで命令を変換するか、物理CPUの仮想化支援を使います。

VT-x / AMD-V
02 メモリ

物理RAM上の位置を調べる

ゲスト内とホスト側のアドレス対応を順にたどります。

EPT / NPT
03 I/O

読み書きの要求を渡す

ゲストの要求を、ホスト側のデバイス処理へ渡します。

virtio

仮想マシン内のプログラムが使う命令も、最後は物理CPUが実行します。CPUをソフトウェアで再現する構成では、QEMUが仮想マシン内の命令をホストCPU用の命令へ変換します。VT-xまたはAMD-VをKVMから使う構成では、多くの通常命令を物理CPUで直接実行できます。

ソフトウェアでCPUをエミュレーション

命令を変換してから実行する

  1. ゲスト命令のまとまり
  2. QEMU TCG命令を読み、ホスト用の命令へ変換
  3. ホストCPU変換済みの命令を実行

変換結果はキャッシュして再利用できます。それでも、命令変換とソフトウェアによるアドレス変換の処理が加わります。

KVMとVT-x / AMD-V

多くの命令を物理CPUで実行する

  1. ゲスト通常の計算命令
  2. 物理CPUゲスト用の実行状態で命令を実行
  3. ホスト側の処理が必要な時VM exitでKVMの処理へ移る

KVMは、どのイベントでVM exitするかをCPUへ設定します。VM exitには状態の保存と復元が伴うため、回数が性能へ影響します。

TCPU≈Tguest+Nexit(Cexit+Chandle+Centry)T_{CPU} \approx T_{guest} + N_{exit}(C_{exit} + C_{handle} + C_{entry})

N_exitはVM exitの回数です。括弧内は、ゲスト用の実行状態を抜け、原因を処理し、再びVM entryを行うまでの時間を表します。VM exitの回数と、一回ごとの処理時間が増えるほど、仮想マシン内の命令実行以外に使うCPU時間も増えます。

06.4
CPUとは別に、通信とストレージの経路を選ぶ

ゲストのI/Oをホストへ渡す三つの構成

ゲストがネットワークへ送信したり、仮想ディスクを読み書きしたりするときは、I/O要求を物理デバイスまで届けます。主な構成は、デバイスエミュレーションデバイスエミュレーションQEMUなどが、既存の物理デバイスと互換の動作を再現する方式。、virtioによる準仮想化I/O準仮想化ゲストOSが仮想環境向けのインターフェースを使う方式。、デバイスの直接割り当てです。既存デバイスとの互換性、ホスト側での共有と制御、処理経路の短さのどれを重視するかによって選びます。

互換性を優先

デバイスエミュレーション

ゲストが知っている物理デバイスの動作を、QEMUが再現します。

  1. ゲスト既存デバイス用ドライバ
  2. ホストQEMUのデバイスモデル
  3. 実際のI/Oホストのドライバ
仮想環境向けのI/O

virtio

ゲストとホストがvirtio仕様に沿って、virtqueueで要求と完了を渡します。

  1. ゲストvirtioドライバ
  2. 共有メモリvirtqueue
  3. ホストQEMUまたはvhost
デバイスを割り当てる

VFIO・SR-IOV

物理デバイスまたはVirtual Functionを、IOMMUで保護しながら仮想マシンへ割り当てます。

  1. ゲスト割り当てたデバイス用ドライバ
  2. 保護と割り当てVFIO・IOMMU
  3. ハードウェア物理機能またはVF
06.5
完全仮想化と準仮想化をI/O経路で比べる

既存デバイスを再現するか、virtioのqueueを使うか

ゲストOSが既存のデバイス用ドライバを使えるように、ホストがデバイスの動作を再現するのが完全仮想化のI/Oです。ゲストOSが仮想環境向けのドライバを使い、ホストと協調してI/Oを渡す方法を準仮想化I/O準仮想化ゲストOSが仮想環境向けのインターフェースを使う方式。と呼びます。

現在のKVM仮想マシンでは、CPUをVT-xやAMD-Vで仮想化し、I/Oにはvirtioを使う構成が広く使われています。仮想マシン全体を二者択一で分類するのではなく、CPUとI/Oで異なる仕組みを組み合わせています。

virtiovirtio仮想マシン向けに標準化されたI/Oデバイスの仕様。は、仮想マシン向けに標準化されたI/Oデバイスの仕様です。ゲストはvirtio用ドライバを使い、ホスト側のデバイス実装と対応機能を確認してから通信します。

virtqueuevirtqueuevirtioドライバとデバイス実装が、I/O要求と完了を受け渡すキュー。は、ゲスト側ドライバがI/Oに使うメモリ領域を登録し、ホスト側が処理済みの領域を返すためのキューです。virtio-netはネットワーク、virtio-blkはブロックデバイスというように、用途ごとのデバイス仕様が同じ基本構造を使います。

既存デバイスをエミュレーションする構成では、ゲストOSのドライバが行うレジスタ操作をホスト側で捕捉します。操作のたびにVM exitが起こる場合があり、デバイスモデルが状態を更新している間は、その仮想CPUで仮想マシン内のOSやアプリを動かせません。virtioは、複数のI/O要求をメモリ上のqueueへ記述し、まとめて通知できる形式を定めています。これにより、デバイス固有のレジスタ操作と、それに伴う切り替えを減らせます。

同じ処理を二つの経路で比べるゲストOSが、3件の読み込みをホストへ依頼する
既存デバイスとの互換性を優先

ドライバのレジスタ操作を、QEMUがデバイスの仕様に沿って処理する

ゲストOSは、実在するデバイス用のドライバを使います。ドライバがデバイスのレジスタへ書き込むと、QEMUのデバイスモデルは書き込まれた値を解釈し、対象デバイスの状態を更新します。

ゲストOSのドライバゲストとホストの境界QEMUのデバイスモデル
  1. 読み込み Aデバイス固有のレジスタへ読み込み条件を書くレジスタアクセスを捕捉
    VM exitが起こり得る
    レジスタの値を解釈し
    読み込みAを処理する
  2. 読み込み Bデバイス固有のレジスタへ読み込み条件を書くレジスタアクセスを捕捉
    VM exitが起こり得る
    レジスタの値を解釈し
    読み込みBを処理する
  3. 読み込み Cデバイス固有のレジスタへ読み込み条件を書くレジスタアクセスを捕捉
    VM exitが起こり得る
    レジスタの値を解釈し
    読み込みCを処理する
デバイスエミュレーションで必要な処理

既存デバイスのレジスタ動作を再現する

ゲストは実在するデバイス用のドライバを使います。QEMUは、そのドライバが行うレジスタの読み書き、割り込み、データ転送を、対象デバイスの仕様に沿って処理します。

virtioで必要な処理

メモリ上のI/O要求を読み取る

ゲスト側ドライバは、読み書きするメモリ領域をdescriptorで示し、利用可能な要求をvirtqueueへ登録します。ホスト側は完了した要求を同じキューへ記録します。仮想環境向けに単純化した形式なので、既存デバイスの細かな動作を再現する処理を減らせます。

virtioへホスト側の並列処理を組み合わせる

virtqueueとホスト側の処理スレッド

ネットワークやストレージへ多数の要求が届く構成では、要求を渡す形式だけでなく、ホスト側で何本の処理を同時に進められるかも性能に影響します。virtio-netとvirtio-blkは複数のvirtqueueを使うmultiqueueに対応しています。

ホストでは、QEMUのスレッドが一つの仮想CPUを担当します。このスレッドがデバイス処理まで行う構成では、VM exitから処理が終わるまで、その仮想CPUで仮想マシン内のOSやアプリを動かせません。

IOThreadIOThreadQEMUで、I/Oのevent loopをmain loopとは別に動かすスレッド。は、QEMUでI/Oを処理するevent loop用のスレッドです。I/O要求をIOThreadへ渡せる構成では、仮想CPUを担当するスレッドが先に仮想マシン内の命令実行を再開し、デバイス処理を別のホストCPUで進められます。vhostを使う構成では、virtioのデータ処理をホストカーネル側へ移せます。

ホスト側の時間軸

VM exitの後、仮想CPUを担当するスレッドはいつ仮想マシン内の処理を再開できるか

再生すると、ホスト上の各スレッドが処理を進める時間を縦線が移動します。

デバイスエミュレーションで生じやすい経路

VM exit後のデバイス処理が終わるまで、その仮想CPUではOSやアプリの命令を実行できない

ゲストOSのドライバによるレジスタ操作を捕捉すると、物理CPUは仮想マシン内の命令実行を中断し、ホスト側の処理へ移ります。ホスト側でデバイスの状態更新やI/O処理まで行う構成では、その処理がVM entryより前に続きます。

仮想CPU
スレッド
OSやアプリの命令VM exitデバイス処理
データ転送を待つ
VM entryOSやアプリの命令
切り替えレジスタ操作ごとにVM exitが起こり得る
待ち時間ホスト側の処理中は、この仮想CPUで仮想マシン内のOSやアプリを動かせない
要求が集まる場所

1本のキューを単一の処理スレッドが担当する構成

複数の仮想CPUがI/Oを出しても、要求は同じvirtqueueと同じ処理スレッドを通ります。前の要求に時間がかかると、後の要求はそこで待ちます。

ゲスト仮想CPU 0仮想CPU 1仮想CPU 2仮想CPU 3
ゲストメモリvirtqueue 0要求 0 要求 1 要求 2 要求 3
ホストmain loopまたはIOThread 0
ユーザー空間で処理

QEMUのvirtioデバイス

QEMUプロセスがvirtqueueの要求を読み、ホストの機能へ渡します。デバイスモデルと仮想マシンの管理を一つのプロセスにまとめられます。

カーネルで処理

vhost

vhostvhostvirtioのデータ処理をホストカーネルで行う仕組み。は、virtioのデータ処理をホストカーネルで行う仕組みです。要求のたびにQEMUへ戻る経路を減らします。

別プロセスで処理

vhost-user

virtioのデータ処理をQEMUとは別のユーザー空間プロセスで行います。QEMUとはUnix domain socketなどを通して設定をやり取りします。

06.6
物理デバイスを仮想マシンへ割り当てる

仮想マシンから、ホストに接続されたデバイスを使う

ここまでは、QEMUが再現するデバイスやvirtioを通して、ゲストOSのI/O要求をホスト側へ渡す構成を見てきました。

一方、ホストに接続されたネットワークカードやGPUそのものを仮想マシンへ割り当て、ゲストOSのドライバから使う構成もあります。この構成では、デバイスの操作を仮想マシンへ渡すだけでなく、デバイスがアクセスできるRAMも仮想マシンごとに制限する必要があります。

ここでは、ネットワークカード(Network Interface Controller、NIC)、NVMe(Non-Volatile Memory Express)対応SSD、GPU(Graphics Processing Unit)を例にします。これらのデバイスは、大量のデータを転送するたびにCPUへコピーさせるのではなく、デバイス自身でRAMを読み書きできます。この機能をDirect Memory Access(DMA)DMAデバイスがCPUを介さず、RAMへ直接データを転送する機能。と呼びます。DMAはCPUの処理を減らせますが、デバイスへ物理RAM全体の読み書きを許すと、ホストや別の仮想マシンのメモリまでアクセスできてしまいます。

CPUは、Memory Management Unit(MMU)MMUCPUが仮想アドレスを物理アドレスへ変換するための回路。を使って、プログラムが指定した仮想アドレスを物理RAM上の位置へ変換します。デバイスのDMAにも同様の変換と保護を加えるハードウェアが、Input-Output Memory Management Unit(IOMMU)IOMMUデバイスのDMAアドレスを変換し、アクセスできるRAMの範囲を制限するハードウェア。です。IOMMUは、デバイスが使うI/O仮想アドレスを変換し、デバイスごとに読み書きできるRAMの範囲を制限します。

RAMへ到達する二つの経路

CPUのアドレス変換はMMU、デバイスのメモリアクセスはIOMMUが管理する

プログラムの命令CPU仮想アドレスで読み書き
CPU側の変換MMUMemory Management Unit
ページテーブルを参照
物理メモリRAMCPUが使える範囲
通信・保存・計算NIC・NVMe・GPUDirect Memory Access(DMA)で
RAMを読み書き
デバイス側の変換と保護IOMMUInput-Output Memory Management Unit
I/Oページテーブルを参照
物理RAM割り当てた範囲ほかの範囲へのDMAを拒否

Linuxは、PCI Expressデバイスの機能を、個別に設定できるPCI Functionとして認識します。ホスト上のPCI Functionを仮想マシンへ直接割り当てる構成をPCIパススルーPCIパススルーホストのPCIデバイスまたはPCI Functionを、仮想マシンへ直接割り当てる構成。と呼びます。Virtual Function I/O(VFIO)VFIOIOMMUで保護したデバイスを、ユーザー空間から安全に扱うためのLinuxカーネルの枠組み。は、IOMMUで保護したデバイスのレジスタ、割り込み、DMAを、QEMUなどのユーザー空間プログラムから扱うためのLinuxカーネルの枠組みです。

Single Root I/O Virtualization(SR-IOV)SR-IOV一つのPCI Expressデバイスを、複数の独立した機能として見せるハードウェアの機能。は、一つのPCI Expressデバイスを、管理を担うPhysical Function(PF)と、割り当て可能な複数のVirtual Function(VF)として見せるハードウェアの機能です。VFは独立したPCI Functionとして見えるため、VFIOを使って仮想マシンへパススルーできます。

物理デバイスをどの単位で使うか

一台の仮想マシンへ専有させる構成と、対応機器を分けて使う構成

PCI passthrough

ホストのPCI Functionを、仮想マシンへ割り当てる

PCI Functionは、PCI Express上で個別に認識されるデバイスの機能です。NIC、GPU、NVMeコントローラなどのFunctionをホスト用ドライバから外し、VFIOを通してQEMUへ渡します。

物理ハードウェアPCI FunctionNIC・GPU・NVMeなど
LinuxカーネルVFIOレジスタと割り込みを公開
DMAの保護IOMMU許可したメモリだけへ変換
仮想マシンゲストOSのドライバ割り当てた実デバイスを操作
パススルーQEMUのデバイスモデルをデータ経路から外す

ゲストOSのドライバが、割り当てられたFunctionのレジスタ、割り込み、DMAを使います。

排他的な所有割り当て中は、原則としてホスト用ドライバから使わない

VFIOではIOMMU groupが安全に分離できる最小単位です。同じgroupのデバイスをまとめて扱う必要がある場合があります。

DMAデバイス自身がRAMを読み書きする

IOMMUは、デバイスが使うI/O仮想アドレスを物理RAMへ変換し、読み書きできる範囲を制限します。

性能とホスト側の制御

データ経路を短くすると、ホストで処理できる場所も変わる

パススルーでは、QEMUのデバイスモデルやvirtioバックエンドをデータ経路から外せます。ホストの仮想switch、フィルタ、監視を通したい場合は、virtio、SR-IOV representor、SmartNICなど、要件に合う経路を選びます。

移動と復旧

デバイスの内部状態を移動先で復元できるか

live migrationlive migration動作中の仮想マシンの状態を別のホストへ転送し、実行先を切り替える処理。では、RAMと仮想CPUに加えてデバイスの状態も転送します。VFIOで割り当てたデバイスを移動できるのは、デバイスとドライバが状態の保存・復元に対応する場合です。対応しないデバイスを含む仮想マシンは、動かしたまま別ホストへ移せません。