安室 浩和(やすむろ ひろかず)

 大手コンピュータ・メーカー勤務。入社以来10数年をソフトウェア開発の現場で過ごし,その後ソフトウェア品質部へ異動。現場への技術支援や品質教育開発などを主に行っている。「APIで学ぶWindows徹底理解」(日経BP社)などを執筆。

 今を去ること十数年前,PC(パソコン)のOSの主流がDOSだったころ,アプリケーションが利用できるメモリーは,高々500Kバイトしかなかった。プログラマは,少ないメモリーをいかに有効利用するかに腐心したものだし,mallocなどのメモリー割り当て関数が,メモリー不足のためにエラーを返すのも珍しいことではなかった。OSがWindowsやLinuxなどに変わり,メモリーの大容量化や低価格化が進んだ今,少なくとも一般的なアプリケーション開発に関する限り,メモリーが確保できるかどうかを心配する開発者はほとんどいないのではないだろうか。

 しかし,それでもメモリーは有限である。アプリケーションの肥大化はもとより,マルチタスクOSになったために,必要となるトータルのメモリーは大幅に増えているはずだ。しかも,すべてのシステムが,CPUが扱える最大容量のメモリーを搭載しているとは限らない。では,なぜメモリー不足が問題にならないのだろうか。

 それは,ある特別なメモリー管理の仕組みが実装されているからである。今回から2回にわたって,このWindowsのメモリー管理の仕組みについて解説する。

物理メモリーからプロセスのメモリー空間を切り離す

 第1回で,「各プロセスのメモリー空間は独立している」という話をしたが,これもWindowsのメモリー管理の恩恵を受けている一つである。そもそも「メモリー空間」というのは,プロセスからアクセス可能なメモリーの全体を表す言葉だ。では,この「アクセス可能」というのはどういうことだろうか。

 メモリーというのは,最小単位(多くの場合は1バイト)のデータの入った箱がたくさんあるようなものである。その中のどの箱を読み書きするかを指定するのが「アドレス」だ。箱があっても,アドレスが振られていないものは,アクセスすることができない。つまり,アドレスが割り当てられているメモリーが「アクセス可能」ということになる。

 アドレスは,CPUのアーキテクチャに依存する。PentiumなどIA-32アーキテクチャでは32ビットの範囲,4Gバイトのメモリーをアクセス可能である。つまり,プロセスのメモリー空間は4Gバイトということだ。DOS時代のメモリー空間は1Mバイトだったので,なんと4000倍である。しかもDOSでは,この狭いメモリー空間を,ハードウエアやOS,常駐プログラム,アプリケーションで分け合って使用していたのである。4Gバイトものメモリー空間があれば,もうメモリーの心配はしなくて済みそうにも思える。しかし,話はそう簡単でもない。

 ちょっと考えてみて欲しい。これまで,プロセスのメモリー空間は独立だと説明してきたが,二つのプロセスがあればメモリー空間の合計は8Gバイトになる。20個あれば80Gバイトだ。しかし,そもそもそんなに大量のメモリーが,一般のPCに搭載されているだろうか。

 最近はメモリーがかなり安くなっているので,10Gバイトくらいなら5万円前後で手に入りそうだ。しかし,手にはいるのと利用できるのとは違う。IA-32アーキテクチャのCPUには,物理メモリーを指定するためのアドレス信号線が32本しかない。1本が1ビットの信号線なので,CPUがアクセス可能な物理メモリー空間は32ビット。プロセス一つのメモリー空間とぴったり同じなのである。

 最近のCore 2 Duoなどでは,36ビットの物理メモリー空間をサポートしているが,それでもたかだか64Gバイトである。しかも,これらのCPUで動作する64ビットWindowsでは,プロセスのメモリー空間が64T(テラ=1024G)バイトに拡張されている。これではもう,フルに物理メモリーを搭載したところで,1プロセス分にも足りない。

 実は,この矛盾を解明する仕組みが,Windowsにおけるメモリー管理のキーである「仮想メモリー」だ。これは,プロセスがアクセスするメモリー空間を,物理メモリー空間とは切り離して,仮想的なメモリー空間として扱う仕組みである。簡単に言ってしまえば,プロセスのプログラムからCPUに指定したアドレスに,CPUが何らかの変換を加えるものだ。そして,この変換で重要な役割を果たしているのが,次に説明する「ページング」である。

メモリー空間をページ単位で対応付ける

 「ページング」は文字通り,メモリー空間を「ページ」に分割し,ページ単位で管理することを意味する。別に1バイト単位で管理してもいいのだが,1バイトずつメモリーを利用することはまれだし,それを管理するための情報だけで膨大になってしまうため,ページという単位でまとめて管理しようというわけだ。

 ページ・サイズはCPUに依存するが,WindowsがサポートするIA-32やIA-32eアーキテクチャのCPUでは,いずれも1ページ4K(=4096)バイトである。ページ単位でまとめたとはいえ,4Gバイトのメモリー空間を4Kバイトのページで管理すると,2の20乗=約100万ページも必要になる。32ビットWindowsでいえば,プロセスのメモリー空間100万ページと,物理メモリー空間の100万ページを対応付けるページングが,仮想メモリーの基本なのである。

 具体的な仕組みは次のようになっている。仮想メモリーのアドレスを0番地からページ・サイズの4096(2の12乗)バイトごとに区切ったものが仮想メモリーのページとなる。32ビット・アドレスの場合,同じページ内のアドレスは上位20ビットが共通になるので,これがページを識別するIDと考える。ただし,20ビットでは大き過ぎて扱いにくいので,さらに半分の10ビットずつに分けて,二つの0~1023の値を得る。これらを図1のように,表のインデックスとして使用するのである。

図1●仮想アドレスから物理アドレスへの変換の仕組み
図1●仮想アドレスから物理アドレスへの変換の仕組み

 上位10ビットが対応する表をディレクトリという。ディレクトリの各エントリの内容は,対応するページ・テーブルの先頭アドレスで,このテーブルを残った下位10ビットでインデックスする。ページ・テーブルの各エントリには,対応する実メモリーのページの先頭アドレスがセットされているので,2段階のテーブルを経て,仮想メモリーのページから物理メモリーのページへ対応付けられるのだ。

 仮想アドレスの内,ページ・テーブル検索に使用されなかった下位12ビットは,ページ内のオフセットとして,仮想メモリーと物理メモリーで共通に使用される。したがって,対応するページを重ねたとすると,そのデータはぴったり一致することになる。

 ちなみに,Core 2 Duoなどの64ビットのCPUでも同じ4Kバイトページを使用するため,ページ数はさらに膨大になる。そのため,ページングの段階をもう一段増やして,3段階でマップするようになっている。