コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
仮想メモリ
8GB の PC で、12GB 使うプログラムが動く
タスクマネージャーを開くと、起動中のアプリの使用メモリを足した合計が、積んでいる RAM を超えていることがあります。足し算が合っていないように見えますが、どれも落ちる気配はありません。
種を明かすと、プログラムが読み書きしている番地は、RAM 上の本当の番地ではありません。C でポインタを表示すると 0x7ffd3c0a1b4c のような値が出ますが、この数字が RAM のどこを指しているのかは、プログラム自身には分かりませんし、知る必要もありません。
番地は、間に 1 枚かませて配られている
プロセスが使う番地を 仮想アドレス、RAM 上の本当の番地を 物理アドレス と呼びます。CPU に載っている MMU という回路が、メモリを触るたびに前者を後者へ翻訳しています。翻訳のための表は、OS がプロセスごとに用意します。
この層が無かった頃、プログラムは物理アドレスを直に書いていました。2 つを同時に動かすには使う番地がぶつからないよう人間が割り振る必要があり、片方が暴走すればもう片方のデータを平気で壊しました。表を 1 枚はさむのは、この面倒をまとめて OS 側へ引き取るための設計です。
この 1 枚をはさんだおかげで、3 つのことが同時に成り立ちます。
- どのプロセスも、0 番地から始まる切れ目のない広い空間を持てる。x86-64 の Linux なら、1 プロセスあたり 128TB 分の番地が配られる
- プロセス A の 0x1000 とプロセス B の 0x1000 は、別の物理アドレスへ翻訳される。表に載っていない場所はそもそも触れないので、他人のデータを覗くことができない
- 表の一部を「まだ RAM に置いていない」状態のままにしておける
冒頭の帳尻は 3 つ目で合っています。確保した領域を全部は使わないプログラムがほとんどなので、100MB の配列を宣言しても、実際に書き込んだ範囲にしか RAM は割り当てられません。使用メモリとして表示されている数字の多くは、配られた番地の量であって、占有している RAM の量ではないわけです。合計が搭載量を超えていても、それだけでは何もおかしくありません。
自分のプロセスの中を覗く
ターミナル
cat /proc/self/mapsプレーンテキスト
55e0f2c00000-55e0f2c21000 r-xp /usr/bin/cat
7f9a3c000000-7f9a3c1a5000 r-xp /usr/lib/libc.so.6
7ffd3c082000-7ffd3c0a3000 rw-p [stack]左の数字が仮想アドレスの範囲、r-xp がその範囲に許された読み・書き・実行です。コードが置かれた範囲には w が無いので、暴走したコードが自分自身の機械語を書き潰す事故は起きません。番地の範囲ごとに権限を持たせられるのも、間に 1 枚かませた効果です。
覚え方 仮想メモリとスワップは別物です。スワップは仮想メモリの上で作れる仕掛けの 1 つで、スワップ領域を持たない機械でも仮想メモリは動いています。