Windows の限界に挑む: 仮想メモリ

翻訳元: Pushing the Limits of Windows: Virtual Memory (英語)

「Windows の限界に挑む」の最初の投稿では、物理メモリの制限 (ライセンス、実装、ドライバーの互換性に対する制限など) について説明しました。今回は、もう 1 つの基本リソースである仮想メモリに注目します。仮想メモリによって、プログラムはシステムの物理メモリとは別にメモリを使用できるようになります。プログラムのコードとデータを物理メモリに格納するタイミングと状況、およびそれらをファイルに格納するタイミングは、オペレーティング システムが決定します。仮想メモリの大きな利点は、物理メモリで処理できる以上のプロセスを、同時に実行できるということです。

その一方で、仮想メモリには物理メモリの制限からくる制限があるほか、それ以外のさまざまな要因から生じる制限があり、そうした制限はメモリを利用する側によって異なります。たとえば、アプリケーション、オペレーティング システム、およびシステム全体を実行するプロセスのそれぞれに適用される仮想メモリの制限があります。この投稿を読み進める際には、仮想メモリが、その名のとおり、物理メモリとは直接つながっていないことを忘れないようにしてください。Windows が仮想メモリの一定量をファイル キャッシュに割り当てる場合に、物理メモリに実際にキャッシュされるファイルのデータ量が認識されているわけではありません。まったくデータ量がない場合もあれば、仮想メモリから割り当て可能な量を超える場合もあります。

プロセスのアドレス空間

各プロセスには、仮想メモリ (アドレス空間と呼ばれる) が割り当てられ、実行するコードと、コードが参照し操作するデータがそこにマップされます。32 ビットのプロセスでは、32 ビットの仮想メモリ アドレス ポインターが使用されます。このため、仮想メモリの上限は、32 ビット プロセスで処理が可能な 4 GB (2^32) となります。ただし、オペレーティング システム自体のコードとデータ、および現在実行中のプロセスのコードとデータを、アドレス空間を変更せずに参照できるようにするために、オペレーティング システムは、すべてのプロセスのアドレス空間でオペレーティング システムの仮想メモリを参照できるようにします。既定では、32 ビット版 Windows では、システムとアクティブなプロセスとの間でプロセスのアドレス空間が均等に分割されます。そのため、アドレス空間の上限はそれぞれ 2 GB になります。

 図 1

図 1

アプリケーションでは、ヒープ API、.NET ガベージ コレクター、または C ランタイムの malloc ライブラリを使用して仮想メモリを割り当てることができますが、実際には、これらはすべて VirtualAlloc (英語) API に依存しています。アプリケーションのアドレス空間が不足し、VirtualAlloc も足りなくなると、階層の最上位にあるメモリ マネージャーがエラーを返します (エラーは NULL アドレスで表されます)。Windows のさまざまな制限を示すために『インサイド Microsoft Windows 第 4 版』(英語) で作成した Testlimit ユーティリティは、-r スイッチを指定すると、エラーが発生するまで VirtualAlloc を繰り返し呼び出します。このため、32 ビット版 Windowsで 32 ビット版の Testlimit を実行すると、アドレス空間の 2 GB 全体が消費されます。

図 2

図 2

2010 MB は 2 GB には達していませんが、Testlimit の他のコードやデータ (実行ファイルやシステム DLL など) によって、この差異は説明が付きます。

Process Explorer の [Virtual Size] を参照すると、消費されているアドレス空間の総量を確認することができます。

図 3

図 3

SQL Server や Active Directory などの一部のアプリケーションでは、大規模なデータ構造を管理しているため、アドレス空間に同時に読み込めるデータが多くなればなるほどパフォーマンスが向上します。そのため、Windows NT 4 SP3 では、/3GB (英語) というブート オプションが導入されています。このオプションは、システムのアドレス空間のサイズを 1 GB に減らすことで、4 GB のアドレス空間のうち 3 GB を割り当てます。また、Windows XP と Windows Server 2003 には、/userva オプションが導入され、分割の境界を 2 GB から 3 GB の間の任意の位置に移動することができます。

 図 4

図 4

ただし、2 GB の制限を超えたアドレス空間を利用するには、プロセスは実行可能イメージ内で "大規模アドレス空間対応" フラグを設定する必要があります。一部のアプリケーションではアドレス空間が最大 2 GB であると想定されているため、追加の仮想メモリへのアクセスはオプトインです。2 GB 未満のアドレスを参照するポインターのハイビットは常に 0 になるため、このようなアプリケーションではポインターのハイビットをそのアプリケーション自身のデータのフラグとして使用し、当然、データを参照する前に消去します。このようなアプリケーションが 3 GB のアドレス空間で実行された場合、2 GB より大きい値を持つポインターが気付かれずに切り捨てられ、データ破損などのプログラム エラーが生じる可能性があります。

Chkdsk.exe、Lsass.exe (ドメイン コントローラーで Active Directory サービスをホスト)、Smss.exe (セッション マネージャー)、Esentutl.exe (Active Directory Jet データベース修復ツール) など、マイクロソフトのサーバー製品やデータを集中的に利用する Windows の実行ファイルはすべて、大容量アドレス空間対応フラグが設定されています。Visual Studio に付属する Dumpbin ユーティリティを使用すると、イメージにフラグが設定されているかどうかを確認できます。

図 5

図 5

Testlimit も大規模アドレス対応と設定されており、3 GB のユーザー アドレス空間でブートした場合、-r スイッチを指定して実行すると次のように表示されます。

図 6

図 6

後で簡単に説明しますが、64 ビット版 Windows のアドレス空間は 4 GB よりはるかに大きいため、Windows は 32 ビット プロセスが処理できる最大の 4 GB をプロセスに割り当て、残りをオペレーティング システムの仮想メモリに使用します。64 ビット版 Windows で Testlimit を実行すると、32 ビットで処理可能なアドレス空間全体が消費されることがわかります。

図 7

図 7

64 ビット プロセスでは、64 ビット ポインターが使用され、理論上のアドレス空間の上限は 16 エクサバイト (2^64) になります。ただし、Windows はアクティブなプロセスとシステムとの間でアドレス空間を均等には分割しません。その代わりに、プロセスに対してアドレス空間の領域を定義し、システム ページ テーブル エントリ (PTE)、ファイル キャッシュ、ページ プール/非ページ プールなどのさまざまなシステム メモリ リソースに残りを定義します。

プロセス用のアドレス空間のサイズは、Windows の IA64 と x64 の各バージョンで異なります。アドレス空間のサポートに必要なオーバーヘッドのメモリ コスト (ページ テーブルのページおよび変換ルック アサイド バッファー (TLB) のエントリ) とアプリケーションで必要なメモリのバランスを考慮して、サイズが選択されます。x64 では、8192 GB (8 TB)、IA64 では、7168 GB (7 TB - x64 との 1 TB の相違は、IA64 の最上位ページ ディレクトリには Wow64 マッピング用のスロットが予約されるため) になります。Windows の IA64 と x64 の両バージョンで、各種リソース用のアドレス空間領域のサイズは 128 GB になります (例: 非ページ プールには 128 GB のアドレス空間が割り当てられます)。ただし、ファイル キャッシュについては例外で、1 TB が割り当てられます。したがって、64 ビット プロセスのアドレス空間は次のようになります。

図 8

図 8

この図の比率は正確ではありません。実際に表すとしたら、128 GB はもちろん 8 TB も銀色の細い線で表すことになります。つまりは、私たちの世界と同様、64 ビット プロセスのアドレス空間には広大なスペースがあるということです。

Testlimit の 64 ビット版 (Testlimit64) を -r スイッチを指定して 64 ビット版 Windows で実行すると、8 TB が消費されることがわかります。これは、このプログラムが管理できるアドレス空間部分のサイズです。

図 9

図 9

図 10

図 10

公開: 2008 年 11 月 17 日 月曜日 5:41 PM 投稿者: markrussinovich (英語)

ページのトップへ

共有

ブログにコピー: ([Ctrl] + [C] でコピーしてください)