BitVisorの仮想メモリーマップ

BitVisorの仮想メモリー空間のメモリーマップについてです。

BitVisorのソースコードは https://bitbucket.org/bitvisor/bitvisor からダウンロードできます。メモリー関連については主にcore/mm.cあたりを見ることになるでしょう。

大雑把に以下のような領域にわかれています。

* プロセス
* カーネル (ヒープ含む)
* 物理アドレスの静的割り当て
* 物理アドレスの動的割り当て

## プロセス

プロセスは、32bit・64bitに関わらず、アドレス0x00000000-0x3FFFFFFFまでの1GiBの空間を使用します。

よく、IA-32用のLinuxはデフォルトで3GiBだとか、IA-32用のWindowsはデフォルトで2GiBだとか、ご存じの方も多いかと思いますが、BitVisorでは1GiBです。これは、プロセスがデバッグ用と保護ドメイン用にしか使われていないことと、そもそもVMMが奪うRAMが128MiBしかなく、1GiBでも十分なためです。

なお、この空間の中で、アドレス0x00000000-0x1FFFFFFFまでがプロセスのコードおよびデータが置かれる空間で、プロセス起動時に確定されます。また、アドレス0x3FFFF000-0x3FFFFFFFは、システムコールのためのコードが読み取り専用でマップされます。アドレス0x20000000-0x2FFFFFFFは、カーネルまたは他プロセスとのデータのやり取りのための領域で、msgpremapbufシステムコールによりマップされる領域が下位から割り当てられ、メッセージのやり取りの際に動的にマップされる領域が上位から割り当てられます。

この領域では4KiBページのみが使われます。

## カーネル (ヒープ含む)

カーネルは、32bit・64bitに関わらず、アドレス0x40000000-0x7FFFFFFFまでの1GiBの空間を使用します。

このうち、実際には128MiB、つまり、0x40000000-0x47FFFFFFまでが使用されます。

この領域では2MiBページのみが使われます。開発初期の段階でPAEを使用しない実装が行われていた時の名残で、128MiBのRAMを確保する際に4MiB単位のアラインメントを使用していますので、すべての領域を2MiBページでマップすることができます。また、この領域はグローバルページを使用しており、%cr3変更の際にTLBフラッシュされないはずです。

コードは0x40100000-になります。0x40000000-0x400FFFFFと、VMMのコードデータ等 (シンボルheadからendまで) の後ろから0x47FFFFFFまでがヒープとなります。

開発初期はアドレス0x00000000-を使用していて、その真後ろにプロセス用の空間があるという、ちょっと変則的な構成になっていました。ページングオフのまま起動してCのプログラムを開始し、途中で中身を移動してページングをオンにして、という構造にしていて、0x00000000-の都合が良かったのですが、64bit対応にあたって早い段階でページングをオンにしなければならなくなり、また、ちょっと特殊な構造になっていたのを整理するために、0x40000000に移動しました。

### なぜ0x40000000にしたか?

ご存知の通り他の選択肢もあります。例えば32bitでは0x80000000-、64bitでは0xFFFFFFFF80000000-を使用する、というのが考えられます。これだと、32bitと64bitでアドレスが大きく異なることになり、面倒そうに見えます。また、gccに-mcmodel=kernelというオプションをつけなければなりません。

32bitでも64bitでも0x80000000-を使用する、というのも考えられますが、これは良くない選択肢でした。これにはgccに-mcmodel=mediumというオプションをつけますが、AMD64の機械語では、32bitでのアドレス指定が符号拡張されることになっており、0x80000000-のアドレスにアクセスするには64bitでのアドレス指定が必須になります。よって、コードが肥大化し、キャッシュヒット率の低下による性能低下が見込まれます。

0x40000000-は、32bitと64bitで同じ範囲を使用でき、gccのデフォルトの-mcmodel=smallでコンパイルできます。

## 物理アドレスの静的割り当て

物理アドレスの静的割り当て用の領域は、32bitではアドレス0x80000000-0xEFFFFFFF、64bitでは1GiBページ未対応の場合アドレス0x8000000000-0x8FFFFFFFFF、64bitで1GiBページ対応の場合アドレス0x8000000000-0x47FFFFFFFFFが使用されます。

BitVisorで各種デバイスへのアクセスをモニターする場合、RAMの様々な領域にアクセスすることがあります。そのためにいちいちページテーブルへの書き込みとTLBフラッシュを行わずに済むよう、できるだけ多くのRAMを事前にマップしておくためにこの領域があります。32bitではアドレス空間そのものの制約によりわずかしかありませんが、64bitでは1GiBページ未対応でも8GiB、1GiBページ対応であれば512GiBもの領域が、8種類のキャッシュ設定がされた状態で、事前にマップされます。

この領域もグローバルページを使用しています。

1GiBページ対応のほうが領域が大きいのは、1GiBページ非対応だと、1GiBごとにページディレクトリーぶんの4KiBを確保しなければならないためです。

余談ですが、1GiBページ自体は2010年頃から登場していて、Intel CPUだとWestmere以降が対応している、なんて言われているものの、その次のSandy Bridge世代でもCoreシリーズは対応していないみたいです。Intel 64やVT-xも、対応と非対応が混ざり合う不思議な感じになっていましたので、ある意味Intelらしいですね。その点AMDはケチらず搭載してくれているようです。Linuxのprocfsではcpuinfoファイルのflagsのところにpdpe1gbという項目があるかどうかで確認できます。以下は、Sandy Bridgeと同時期の低消費電力APU環境での確認例です。

```
$ grep -e name -e pdpe1gb /proc/cpuinfo |head -2
model name      : AMD C-60 APU with Radeon(tm) HD Graphics
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc extd_apicid aperfmperf pni monitor ssse3 cx16 popcnt lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch ibs skinit wdt arat cpb hw_pstate npt lbrv svm_lock nrip_save pausefilter vmmcall
```

## 物理アドレスの動的割り当て

物理アドレスの静的割り当て用の領域は、32bit・64bitに関わらず、アドレス0xF0000000-0xFEFFFFFFまでの240MiBの空間を使用します。

上述のように静的割り当てできる領域には限界があります。静的割り当てできなかった部分へのアクセスに動的割り当てを使用します。

お気づきの方も多いと思いますが、プロセスを除くアドレス空間はすべての論理CPUで共通です。というのは、あるスレッドでマップしたI/O空間を別のスレッドでアクセスできない、となったら混乱の元なので、そういうことがないようにしています。そうすると、本来は他の論理CPUに対してもTLBフラッシュを行う、TLB shootdownという処理が必要になりますが、やっていません。(キリッ

実際のところ、マップしたまま変更されない場合には問題ありません。マップされていない領域をマップするぶんには、そもそもそれまでにTLBに入っていないわけですから、TLBフラッシュの必要はありません。しかし、一度マップした領域を別の論理CPUでアクセスしたうえでアンマップし、その後別の領域をマップした場合は、当然TLBフラッシュの必要があります。初期のバージョンでは、VM Entry/ExitのたびにTLBフラッシュが行われていたので事実上問題が起きなかったものと思われますが、最近のバージョンでは問題があるかも知れません。プロセスを呼び出しているスレッドがいると問題がおきない、など、変な状態になっているかも知れません。