TL

仮想メモリとページングのプログラミング的含意

なぜmallocは速いのにメモリ確保が遅く感じる瞬間があるのか。アドレス変換・ページフォルト・コピーオンライトを押さえ、性能とメモリ共有の勘所をつかむ。

応用仮想メモリページングメモリ管理OS低レベル最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.プロセスが触る仮想アドレスはMMUがページ単位でページテーブルを引いて物理アドレスへ変換し、TLBがその変換結果をキャッシュして高速化する。
  • 2.確保した領域は最初の書き込み時に初めて物理ページが割り当てられる(デマンドページング)ため、確保コストとアクセス時のページフォルトコストが分離する。
  • 3.fork直後の親子やmmapの共有マッピングはコピーオンライトで物理ページを共有し、書き込みが起きたページだけ複製されるため省メモリかつ高速にプロセスを複製できる。

仮想アドレスと物理アドレスの分離

現代のOSでは、プログラムが扱うポインタの値(仮想アドレス)は、物理メモリ(RAM)上の実際の番地とは一致しません。各プロセスは独立した仮想アドレス空間を持ち、自分専用に全アドレス空間を占有しているかのように振る舞えます。実際の対応付けは、CPU内のMMU(メモリ管理ユニット)がアクセスのたびにハードウェアで変換します。

この間接化が成り立つのは、アドレス空間を固定サイズのページ(典型的に4KB)へ区切り、ページ単位で対応を管理するからです。仮想アドレスは上位ビットの仮想ページ番号と下位ビットのページ内オフセットに分かれ、変換はページ番号だけを物理ページ番号(フレーム番号)へ写し替え、オフセットはそのまま流用します。

仮想アドレス(48ビットの例)
┌──────────────────────────┬───────────────┐
│   仮想ページ番号 (VPN)     │  オフセット(12)│
└──────────────────────────┴───────────────┘
            │                       │
       ページテーブルで変換          そのまま
            ▼                       ▼
┌──────────────────────────┬───────────────┐
│  物理フレーム番号 (PFN)    │  オフセット(12)│
└──────────────────────────┴───────────────┘
物理アドレス

この分離が、プロセス間のメモリ保護(他プロセスの物理ページに触れない)と、物理メモリより大きなアドレス空間を扱える柔軟性を同時にもたらします。

ページテーブルとTLB

変換表であるページテーブルはメモリ上にあり、プロセスごとに用意されます。64ビット環境ではアドレス空間が広大なため、全エントリを平らに持つと巨大になりすぎます。そこで実装は多段(マルチレベル)ページテーブルを使い、仮想ページ番号を数段に分割して木構造をたどります。x86-64 では4段(または5段)で、実際に使われている範囲だけ下位の表を確保するため、疎なアドレス空間を省メモリに表現できます。

問題は、1回のメモリアクセスのたびに数段の表引き(=複数回のメモリアクセス)が必要になることです。これを救うのがTLB(変換ルックアサイドバッファ)で、直近の VPN → PFN 変換結果をCPU内にキャッシュします。TLBがヒットすれば表引きを丸ごと省略でき、変換はほぼ無コストになります。

TLBミスが性能を左右する

ランダムに広範囲のメモリを飛び回るアクセスはTLBミスを多発させ、そのたびにページテーブルを歩く(ページウォーク)コストを払います。これは メモリレイアウトとデータ局所性 で扱うキャッシュミスとは別軸の遅さで、データを連続領域にまとめる・大きなページ(ヒュージページ)を使うといった対策が効きます。

ページフォルトとデマンドページング

ある仮想ページにまだ物理フレームが割り当てられていない、あるいはアクセス権が合わない状態でアクセスすると、MMUはページフォルトという例外を上げ、OSのハンドラに制御が移ります。ページフォルトには性質の異なる種類があります。

  • マイナーフォルト — 物理ページは用意できるが、まだマッピングが張られていない。OSがフレームを割り当て、ページテーブルを更新して再実行する。比較的軽い。
  • メジャーフォルト — 必要なデータがディスク(スワップやファイル)にあり、I/Oを伴って読み込む必要がある。桁違いに重い。
  • 無効アクセス — どこにもマッピングがない番地への参照。OSはこれをセグメンテーション違反としてプロセスを止める。

この仕組みを積極的に使うのがデマンドページングです。mallocmmap で大きな領域を確保しても、その時点では物理メモリは消費されません。最初に各ページへアクセス(書き込み)した瞬間に初めてマイナーフォルトが起き、物理フレームが割り当てられます。

確保コストとアクセスコストは別物

これが「巨大な配列の確保は一瞬なのに、最初のループで触り始めると妙に遅い」現象の正体です。確保はアドレス空間の予約とページテーブル設定だけで済み、物理ページの割り当てとゼロクリアは初回アクセス時に遅延して支払われます。ベンチマークで初回と2回目の走査の差を見落とすと、確保コストを過小評価してしまいます。

mmapによるファイルとメモリの結合

mmap はファイルやデバイス、無名領域を仮想アドレス空間へ直接マッピングするシステムコールです。ファイルをマップすると、readwrite の明示的なシステムコールを介さず、ポインタ操作だけでファイル内容を読み書きできます。アクセスされたページがデマンドページングでオンデマンドに読み込まれ、変更はOSが適切なタイミングでファイルへ書き戻します。

// ファイルをメモリにマップして直接アクセス
int fd = open("data.bin", O_RDWR);
char *p = mmap(NULL, len, PROT_READ | PROT_WRITE,
               MAP_SHARED, fd, 0);
p[0] = 'X';   // 書き込みがそのままファイルへ反映される

利点は、read のようにカーネル空間からユーザー空間へデータをコピーする手間(余分なメモリコピー)を省けること、そして必要なページだけを読み込めることです。大きなファイルへのランダムアクセスや、複数プロセスでの共有に向きます。一方で、アクセスがそのままI/Oを誘発しうるため、メジャーフォルトの遅延がプログラムのどこで顔を出すか読みにくくなる難しさもあります。

コピーオンライト(COW)

仮想メモリの威力が最も鮮やかに出るのがコピーオンライトです。UNIX系で fork が子プロセスを作るとき、親のアドレス空間全体を物理的に複製するのは高価です。そこで実装は、親子に同じ物理ページを共有させ、両者のページテーブルでそれらを読み取り専用に印を付けます。

どちらかがそのページへ書き込もうとした瞬間にページフォルトが起き、OSがそのページだけを複製して書き込み側に専用のコピーを与えます。

fork 直後(共有・読み取り専用)
  親 ─┐
      ├─▶ 物理ページ P(書き込み禁止マーク)
  子 ─┘

子が書き込み → フォルト → P を複製
  親 ────▶ 物理ページ P(元のまま)
  子 ────▶ 物理ページ P'(複製、書き込み可)

結果として、書き込まれなかったページは最後まで共有されたままで、複製コストは実際に変更されたページの分だけに抑えられます。fork 直後に exec する典型パターンでは、ほとんどのページが触られないまま破棄されるため、ほぼコピーゼロでプロセスを起動できます。

共有が見えないコストを生むこともある

COWは省メモリですが、共有ページへの最初の書き込みは「フォルト+ページ複製」という隠れたコストを伴います。fork 後の子で大量のページに少しずつ書き込むと、見かけ上は単なる代入なのにフォルトが頻発します。さらに、参照カウントを更新する ガベージコレクション 言語では、GCがオブジェクトヘッダを触るだけで共有ページが次々と複製され、fork ベースの並列化が期待ほど省メモリにならない落とし穴があります。

プログラマが押さえるべき含意

仕組みもたらす利点プログラミング上の注意
アドレス変換プロセス分離と保護TLBミスの多いランダムアクセスは遅い
デマンドページング確保が軽い・実使用分だけ消費初回アクセスでフォルトコストを後払い
mmapコピー削減・ファイルを直接操作メジャーフォルトの遅延が読みにくい
コピーオンライトfork が高速・省メモリ書き込み時の隠れた複製コスト

これらはいずれも「アドレス空間の予約」と「物理メモリの実割り当て」が分離していることに根ざします。スタックとヒープの実態 で見たヒープ確保も、アロケータが要求する仮想領域と、初回タッチで割り当たる物理ページは別の話です。ポインタの値(仮想アドレス)が物理番地そのものではないことを意識すると、ポインタと参照 の挙動や、プロセス間でアドレスを共有できない理由も腑に落ちます。

まとめ

仮想メモリは、プロセスが見る連続したアドレス空間を、MMUとページテーブルがページ単位で物理メモリへ写し替えることで実現します。TLBが変換を高速化し、デマンドページングが物理割り当てを初回アクセスまで遅延させ、mmap がファイルとメモリを結び、コピーオンライトが fork を安価にします。共通する原理は「予約と実割り当ての分離」です。これを理解すると、確保が速くアクセスが遅い理由、fork が省メモリな理由、そして隠れたページフォルトコストがどこに潜むかを正しく見通せます。

プログラミング Article

仮想メモリとページングのプログラミング的含意を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

仮想メモリ

比較で見る軸

難易度: advanced / カテゴリ: プログラミング / タグ数: 5

導入後に効く点

確保した領域は最初の書き込み時に初めて物理ページが割り当てられる(デマンドページング)ため、確保コストとアクセス時のページフォルトコストが分離する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
プログラミング
タグ数
5

判断チェックリスト

  • 自社の用途が「仮想メモリ / ページング」に近いか確認する。
  • 強みである「プロセスが触る仮想アドレスはMMUがページ単位でページテーブルを引いて物理アドレスへ変換し、TLBがその変換結果をキャッシュして高速化する。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

仮想メモリページングメモリ管理OS低レベル仮想メモリページングメモリ管理