前回は,x86プロセッサの仮想化支援機能(Intel VT,AMD-V)について,その仕組みを紹介しました。
今回は,オープンソースの仮想マシンソフトウェアであるLinux KVMのソースコードを読み,仮想マシンソフトウェアがどのようにIntel VTやAMD-Vを利用してプロセッサを仮想化しているか,具体的に追っていきたいと思います。
Linux KVMのソースコード構成
Linux KVMは,Linux向けのカーネルモジュールとして,Linuxカーネルにマージされています。最新の開発版について興味があれば,Linux KVMの開発サイトから入手することをお勧めします。今回はLinux 2.6.38.2のソースツリーに含まれるLinux KVMのソースコードを基に解説します。
Linux KVMのソースコード ディレクトリ
Linux KVMのカーネルモジュールは,Linuxカーネルのソースコードツリーのうち,以下のディレクトリに収録されています。- virt/kvm/ ――アーキテクチャ非依存コード
- Linux KVMの実装部分のうち,APIなど,ユーザモードから見える部分については,このディレクトリ以下に収録されています。たとえば,/dev/kvmデバイスのインターフェース実装などが含まれます。
- arch/*/kvm/ ――各アーキテクチャ向けコード
-
仮想マシン機能を実現するために,x86などの各アーキテクチャによって実装が異なるハードウェア固有部分は,こちらのディレクトリ以下に,各アーキテクチャごとに収録されています。たとえばx86プロセッサ向けのコードはarch/x86/kvm/に見つかります。
Linux KVMはおもにx86で利用できますが,実際にはその他のアーキテクチャにも対応しています。現時点でLinux KVMがポートされているアーキテクチャについて整理すると,下記のとおりとなっています。
- x86(x86アーキテクチャ 32ビット, 64ビット)
- ia64(IA-64アーキテクチャ)
- s390(S/390アーキテクチャ)
- ppc(Power PCアーキテクチャ)
x86プロセッサ向け仮想化支援機能の抽象化
x86プロセッサに限って,ハードウェア固有部分には,さらにもうひとつのレイヤが存在します。これまで紹介してきたように,Linux KVMはIntel VT,およびAMD-Vの両方に対応しています。しかし両者には実装上の互換性がないため,別々に対応しなければなりません。
このため,Intel VT搭載機向けのコード,AMD-V搭載機向けのコードは別々のソースファイルに分離されています。そして,コンパイル時には,それぞれのソースファイルからkvm-intel.koおよびkvm-amd.koと2つカーネルモジュールが作られます。Linux KVMのロード時には,Linux KVM本体(kvm.ko)に加え,プロセッサの種別からkvm-intel.koもしくはkvm-amd.koのどちらかが併せてロードされる仕組みとなっています。
もっとも,Intel VTとAMD-Vの間で互換性がないといっても,これらのコードの目的はどちらも“x86プロセッサの仮想化”です。このため,実際には“どちらにも通用するコード”が存在し,この部分が切り出されたのがx86.cです。各仮想化支援機能は,x86.cはvmx.cやsvm.cのアダプタとしての役割を持っています。もし,将来,x86プロセッサ向けの新しいプロセッサ仮想化手法が登場した暁には,このインターフェースを踏襲する形で,ソースファイルの追加によっての対応も可能でしょう。
| ソースコード | 説明 |
|---|---|
| x86.c | x86アーキテクチャサポート(Intel VT/AMD-V対応) |
| vmx.c | Intel VTサポート |
| svm.c | AMD-Vサポート |
Linux KVMからのIntel VTの利用
では,Linux KVMカーネルモジュールが具体的にどのような処理を行っているのか,vmx.cの内容を読んでいきましょう。
VMCSの生成
Intel VT-xでは,論理プロセッサを実行するためにVMCSが必要です。Linux KVMは,仮想マシンに対してvcpu(仮想CPU)が作成されたタイミングで,システムメモリ上にVMCS領域を生成します。仮想マシンが2つの vcpuを持つ場合,vcpuごとに1つ,合計2つのVMCSが生成されます。プロセス,仮想マシン,プロセッサの数を図で表すと図1のような関係になります。
VMCSは,システムメモリ上のページから割り当てて使います。ページとは,プロセッサが管理するメモリ空間の単位で,x86プロセッサの場合は4KBである場合が殆どです。Linux KVMは,Linuxカーネル本体のメモリ管理ルーチンよりシステムメモリの割り当てを受け,VMCSとして内容を初期化します。
arch/x86/kvm/vmx.c
1569 static struct vmcs *alloc_vmcs_cpu(int cpu)
1570 {
1571 int node = cpu_to_node(cpu);
1572 struct page *pages;
1573 struct vmcs *vmcs;
1574
1575 pages = alloc_pages_exact_node(node, GFP_KERNEL, vmcs_config.order);
1576 if (!pages)
1577 return NULL;
1578 vmcs = page_address(pages);
1579 memset(vmcs, 0, vmcs_config.size);
1580 vmcs->revision_id = vmcs_config.revision_id; /* vmcs revision id */
1581 return vmcs;
1582 }さらに,作成されたVMCSは必要に応じてレジスタの初期値などが設定されます。また,ファイルとして保存されていた仮想マシンを復元する場合,マイグレーション機能により別の環境から移ってきた仮想マシンの場合は,その論理プロセッサの状態を復元する必要もあるでしょう。

