VMware ESXにおけるメモリ管理(2) - 仮想化インフラにおけるメモリ管理って?

(1) - 序:他のリソースとの違いはなに?
…の続きです。

他のリソースとは異なり、メモリは「確保」されるタイプのリソースです。いわばフロー(処理されていく流れ)が重要なのではなく、ストック(処理のために確保されていること)こそが重要というわけです。…でありながら、仮想化インフラにおいては搭載されているメモリは複数の仮想マシン間で融通し合わなければなりません。しかも、仮想マシンに導入されるゲストOSはそもそもは物理マシンに導入されることを前提に設計されていますから、認識したメモリはすべて自身が占有することを前提として動作します。そうしたOSを複数同時に、限られた物理メモリリソースの範囲で効率的に動作させなければならないということが、仮想化インフラにおけるメモリ管理の難しさといえます。
汎用機やUNIXシステムのようなベンダーによる垂直統合型のシステムにおける仮想化においては、ハードウェアと仮想化レイヤー(Hypervisor)、そしてOSが相互にお互いを「意識して」動作するようにベンダーが設計をすることができますが、ハードウェアもソフトウェアもマルチベンダーであり、仮想化のために仕様そのものを変えることが難しいx86/64アーキテクチャにおいてはどうしても「仮想化を意識してくれない」OSを「Hypervisor側がどうにかすることで」扱う必要があるという難しさがあります。では具体的に、ESXサーバにおけるメモリ管理はどのように行われているのでしょうか。仮想化環境におけるメモリ割り当てを図示するとこのような絵になります。

仮想マシン上に導入されたゲストOSは、HypervisorであるVMkernelが仮想マシンに対して割り当てたメモリを「自身が所有している物理メモリ」と認識します。この絵でいえば、3段の真ん中"Guest physical memory"という部分がこれに該当します。ゲストOSは自身が物理メモリを占有し制御していると認識していますが、実際にはゲストOSが物理メモリだと認識しているものは、Hypervisorにおいて仮想マシンごとに用意されたVirtual Machine Monitor (VMM) が「本当の物理メモリ」の中から割り当てた一部のメモリ空間なわけです*1
ゲストOS上で動作しているアプリケーションは、WindowsであってもLinuxであってもゲストOSによって構成された仮想メモリ空間(上図における"Guest virtual memory")を利用します。つまり、アプリケーションが実際のメモリ空間を意識せずに/管理せずにメモリ空間を利用できるようにOSがメモリ空間の仮想化をしてくれているわけです。対して、Hypervisorはオーバーヘッドを最小化するために基本的には仮想メモリは用いず物理メモリとのマッピングだけを行います*2
メモリはページという単位で区切られて管理されるので、メモリ空間はページの集合、ページテーブルとして管理されます。x86アーキテクチャにおいては1つのページは4KB*3。物理サーバの場合はOSのページテーブルによって、アプリケーションが連続的なメモリ空間として認識しているそれぞれのページ番号(Logical Page Number / LPNs)と実際の物理メモリのページ番号(Physical Page Number / PPNs)との紐付けが管理されています(下図参照)。x86アーキテクチャにおいてはLPNs→PPNsのマッピングはTranslation lookaside buffer (TLB/変換索引バッファ)という技術によって高速化が図られているわけですが…TLBの解説はWikipediaなどをご覧下さい(^_^;)。

これが仮想化レイヤー上のゲストOSとなると、メモリ管理はさらに複雑になります。アプリケーションが扱うメモリページは、ゲストOSによって「ゲストOSが物理だと思っている」メモリページとの間との紐付けをゲストOSにおけるページテーブルによって管理されてますが、ゲストOSが持っているメモリページは実際には物理メモリとは紐付いていないわけで、それはさらにHypervisor(のVMM)によって、実際の物理メモリとのメモリページの紐付け(Machine Page Number / MPNs)が管理されています。つまり物理マシンに導入されたOS上のアプリケーションが物理メモリにアクセスするためには1度のページテーブルの参照だけで済むことが、仮想マシンの場合はさらにもう1段階のページテーブルの参照が必要となるわけです。ESXホストの実装においては、ゲストOSにおけるページテーブルの更に下に、VMkernel内にpmapと呼ばれる「ゲストOSが物理だと思っている」メモリページと本当の物理メモリページ間のマッピングのためのページテーブルがあります。この2段階ページテーブル参照のオーバーヘッドは意外と大きく、この仕組みだけでは仮想化された環境におけるアプリケーションは実用的なパフォーマンス性能を発揮することができません。この問題を改善するためにESXが実装した仕組みが"Shadow Page Table"です。

Shadow Page Tableを用いると、ゲストOS上の仮想メモリ空間(LPNs)とホストOSが管理する物理メモリ空間(MPNs)を直接的にページテーブルによってマッピングすることが可能となります。VMkernel(のVMM)はShadow Page Tableという特別なページテーブルを用意し、ゲストOSがゲストメモリモードで動作している間はゲストOSと紐付いたpmapのページテーブルではなく、「ゲストOSからは透過的に」Shadow Page Tableのページテーブルを参照します(つまり、仮想マシンによって直接的にCPUが管理するMemory Management Unit (MMU)が更新されないように介在します)。当然最初はページフォルトするわけですが、その際に改めてpmapを参照することでそこから「ゲストOS上のアプリケーションがアクセスしたLPNに紐付いたPPNとMPNの紐付け」を確認することができます。これによって、結果的にLPNとMPNの直接的な紐付けをShadow Page Tableに用意することができるわけです。

Shadow Page Tableの価値は、メモリ性能のオーバーヘッド最大の原因となっているページ管理を必要最小限とすることにあります。つまりShadow Page Tableによって、TLBにおけるキャッシュを「PPNs→MPNs」ではなく「LPNs→MPNsにすることにより、アドレス変換のオーバーヘッドを不要とするようにしているわけです。この仕組みは仮想化環境におけるメモリパフォーマンスを改善しますが、必ずしもパフォーマンスが向上するわけではありません。
Shadow Page Tableがあまり有効的に機能しない例としては、ゲストOSによって頻繁にLPNsとPPNsの紐付けが更新され続けるような動作が行われる場合や、コンテキストスイッチによってメモリアドレスの紐付けが頻繁に切替られる様な場合が挙げられます。よって、仮想マシンによってはShadow Page Tableが有効に機能せずに、「仮想化したことによりメモリパフォーマンスが想定以上に低下する」ことも起こりうるということになりますので注意が必要です(多くの場合、仮想マシンに変換して動かしてみないと発覚しないわけですが…)。
このShadow Page Tableの仕組みは、メモリモードの切替を通知することができればハードウェア的な実装が可能です。そんなわけで、次回はShadow Page Tableと同じ仕組みをハードウェア的に実装する仕様あたりから続けたいと思います。

*1:実際には割り当てているメモリ空間が物理メモリの範囲内とは限らないわけですが…その点については、おいおい。

*2:スワップファイルの話はあとで。

*3:ラージページの話はあとで。