プログラムが表示したポインタの数値は、そのままRAM上の場所を表すとは限りません。現代のOSでは通常、プロセスから見える仮想アドレスを、ハードウェアとOSが管理する対応表で物理アドレスへ変換します。この仕組みは、メモリの使い回しとプロセス間の隔離の両方に関わります。
この単元でできるようになること
ページ番号とページ内オフセットを分けてアドレス変換を計算します。また、TLBに情報がない場合と、ページが未配置・アクセス禁止である場合を区別し、ページフォルトが必ずしも異常終了やディスク読込みを意味しないと説明できるようになります。
連続した仮想領域をページに分ける
ページテーブルは、仮想ページと物理ページフレームの対応やアクセス権を管理します。ページサイズを4KiBとする例では、アドレスの下位12ビットがページ内の位置、残りがページ番号です。実際に使えるページサイズや階層の数はアーキテクチャと設定によって異なります。
ページサイズ = 4096 = 0x1000 バイト
仮想アドレス = 0x12345
仮想ページ番号 = 0x12、ページ内オフセット = 0x345
対応する物理フレーム番号 = 0xAB とすると
物理アドレス = 0xAB000 + 0x345 = 0xAB345変換してもページ内のオフセットは保たれます。同じ仮想ページを別の物理フレームへ割り当てても、プログラムが使うアドレスは変えずに済みます。逆に、ふたつのプロセスで同じ仮想アドレスを使っていても、それぞれの対応表が異なれば別のデータへ到達します。共有メモリでは、意図して同じ物理ページを複数のアドレス空間へ割り当てます。
TLBはアドレス変換の結果を覚える
TLBは、よく使うアドレス変換を覚える小さなキャッシュです。該当する変換が見つかれば、毎回ページテーブルをたどる負担を減らせます。見つからないTLBミスでは、ページテーブルの探索などによって変換を得ます。処理の詳細はCPUによって異なります。
TLBミスが起きても、必要なデータのページがRAMにないとは限りません。『対応を覚えていない』ことと『対応先へアクセスできない』ことは別です。データキャッシュも別の機能で、こちらは読み書きするデータそのものを扱います。アドレス変換が速くても、データの取得が遅い場合はあります。
ページフォルトには正常な理由もある
必要なページがまだ割り当てられていない、ディスクへ退避されている、書込み禁止のページへ書こうとしたなどの場合、CPUはページフォルトという例外を通知します。OSはそのアドレスへのアクセスが正当かを調べ、必要ならページを割り当てたりデータを読み込んだりして、元の命令を再開します。正当でないアクセスなら、LinuxではSIGSEGVなどにつながります。
大きな領域を予約しただけで、全ページに物理メモリを割り当てる必要はありません。初めて触った時点で割り当てる方式なら、使わない部分の負担を減らせます。このときのページフォルトは、ディスクI/Oを必要としないこともあります。フォルト回数が増えたという観測だけでは、原因も待ち時間も決まりません。
コピーオンライトは書くまで共有する
コピーオンライトでは、最初は同じ物理ページを読み取り用として共有し、一方が書こうとした時に独立したコピーを用意します。fork後の親子で、すぐに全メモリを複製する負担を減らすために利用できます。書込みによるフォルトをOSが処理して、各プロセスからは独立したメモリとして見えるようにします。
仮想記憶を『RAMが足りない時にSSDをRAMの代わりにする機能』だけで理解すると、保護や共有、遅延割当てを見落とします。スワップを使わない構成でも仮想記憶は働きます。性能を考える場合は、アクセスするページの数、TLBの効き方、物理メモリの圧迫、実際のI/Oを分けて確かめます。
理解を確かめる
問1:ページサイズ4KiBで仮想アドレス0x23456が物理フレーム0xCDへ対応します。物理アドレスはいくつでしょうか。ページ内オフセットは0x456なので、答えは0xCD456です。仮想ページ番号0x23をそのまま物理側へ残さないようにします。
問2:TLBミスが増えました。必ずSSD読込みが増えたといえるでしょうか。いえません。アドレス変換のキャッシュにないだけなら、RAM上のページテーブルから対応を得られます。ディスクI/Oの有無は別の観測が必要です。
キャッシュとメモリ階層と合わせて、アドレス変換とデータ取得のそれぞれにかかる負担を考えましょう。