仮想記憶のアドレス変換とMMU/TLBの内部
アクセスのたびに走るアドレス変換が、なぜ遅くならないのか。多段ページテーブルとMMU、TLBの内部構造を原理から押さえ、性能の勘所を掴めます。
- 1.MMUは仮想アドレスの上位ビットを段ごとに切り出し、多段ページテーブルをハードウェアでウォークして物理アドレスへ変換する。
- 2.TLBは変換結果(ページ番号→フレーム番号)をキャッシュする小容量の連想メモリで、ヒットすればテーブルウォークを丸ごと省ける。
- 3.プロセス切替時のTLB全フラッシュを避けるため、各エントリにASIDを付与して文脈を区別し、再充填コストを下げる。
アドレス変換はなぜ多段なのか
仮想記憶では、CPU が出す仮想アドレスをページ単位で物理アドレスへ写像します。素朴には「仮想ページ番号→物理フレーム番号」の1枚の配列(単段テーブル)で引けますが、これは現実的ではありません。
64bit 環境で 4KB ページ(オフセット 12bit)なら、ページ番号は最大 52bit です。単段なら 2 の 52 乗エントリ分の連続領域をプロセスごとに確保する必要があり、ほとんど未使用でも巨大になります。
そこでページ番号を複数のフィールドに分割し、テーブルを木構造にします。実際に使われている範囲だけ下位の表を割り当てればよく、疎なアドレス空間を省メモリで表現できます。x86-64 の典型(4段、48bit 仮想アドレス)はこうです。
仮想アドレス(48bit 使用)
[47:39] PML4 [38:30] PDPT [29:21] PD [20:12] PT [11:0] オフセット
9bit 9bit 9bit 9bit 12bit
各フィールドは 9bit で、1段あたり 2 の 9 乗 = 512 エントリ。各エントリは 8 バイトなので、1つの表がちょうど 1 ページ(512 × 8 = 4KB)に収まる設計です。
ページテーブルウォークの実際
変換の起点はレジスタ(x86 では CR3、ARM では TTBR)が指す最上位テーブルの物理アドレスです。MMU はそこから段を下りていきます。
1. CR3 + (PML4index × 8) を物理メモリから読む → PDPTの物理ベース
2. PDPTbase + (PDPTindex × 8) を読む → PDの物理ベース
3. PDbase + (PDindex × 8) を読む → PTの物理ベース
4. PTbase + (PTindex × 8) を読む → 物理フレーム番号(PFN)+属性
5. 物理アドレス = (PFN << 12) | オフセット
ここで重要なのは、各段の参照が物理アドレスへの実メモリアクセスであることです。1回の変換に最悪4回の追加メモリ参照が発生し、それが全メモリアクセスごとに乗ります。この代償を消すのが TLB です。
x86 や ARM など多くのアーキテクチャでは、テーブルウォークを MMU がハードウェアで 行います(ハードウェアウォーク)。一方 MIPS のように TLB ミスを例外として OS のソフトウェアハンドラ に投げる設計(ソフトウェアウォーク)もあり、その場合テーブルの形式は OS が自由に決められます。
各エントリには PFN のほかに、有効ビット・読み書き・実行禁止(NX)・ユーザー/カーネル・アクセス済み(A)・ダーティ(D)などの属性が入ります。MMU はウォーク中にこれらを検査し、権限違反や未割り当てならページフォルトを起こして OS に委ねます。
TLB:変換のキャッシュ
TLB(Translation Lookaside Buffer) は「仮想ページ番号→物理フレーム番号+属性」の対応を保持する、CPU 内蔵の小容量キャッシュです。実体は**連想メモリ(CAM)**で、ページ番号をキーに全エントリを並列照合します。
TLBエントリ(概念)
[ ASID | 仮想ページ番号(タグ) | 物理フレーム番号 | 属性 | 有効 ]
メモリアクセスのたびに MMU はまず TLB を引きます。TLBヒットならテーブルウォークを丸ごと省き、1サイクル程度で物理アドレスが得られます。容量は数十〜数千エントリと小さいため、L1(速いが小)と L2(大きめ)の階層を持ち、命令用とデータ用を分ける構成も一般的です。
TLB はエントリ数が物理的に限られるため、1エントリで覆える範囲(リーチ)が性能を左右します。4KB ページではすぐ枯渇する用途で、2MB や 1GB の ヒュージページ を使うと1エントリの到達範囲が広がり、TLB ミスを大幅に減らせます。多段テーブルでは途中段のエントリを「葉」として直接大ページに対応づけます。
TLBミスとフラッシュ
TLB に該当エントリが無ければ TLBミス です。ハードウェアウォークの環境ではミスは透過的に処理され、ウォーク結果が TLB に充填されて命令が継続します(プログラムからは遅延として見えるだけ)。ソフトウェアウォークなら例外ハンドラが走ります。
問題は**フラッシュ(無効化)**です。ページテーブルを書き換えても TLB には古い対応が残るため、OS が明示的に TLB を無効化しなければ整合性が壊れます。代表的な契機を比較します。
| 契機 | 無効化の範囲 | 命令の例 |
|---|---|---|
| 特定ページのマッピング変更 | そのページ1件のみ | x86 invlpg |
| プロセス切替(ASIDなし) | TLB全体 | CR3再ロードで全フラッシュ |
| プロセス切替(ASIDあり) | 原則フラッシュ不要 | ASIDを切り替えるだけ |
| カーネル領域の変更 | 全CPUへ伝播が必要 | TLBシュートダウン |
マルチコアでは各コアが独立した TLB を持つため、あるコアでマッピングを消しても他コアの TLB には残ります。これを揃える処理が TLBシュートダウン で、プロセッサ間割り込み(IPI)で全コアに無効化を依頼します。コア数に比例して高コストになりやすい同期点です。
ページテーブル更新後に対応するエントリを無効化し忘れると、解放済み物理ページを古い権限で参照し続けられてしまい、データ破壊や情報漏えいに直結します。mprotect や munmap の実装が TLB 無効化を伴うのはこのためです。
ASIDによる文脈切替の高速化
ASID(Address Space ID。x86 では PCID)が無いと、コンテキストスイッチのたびに「前のプロセスの変換が次のプロセスに誤適用される」のを防ぐため、TLB全体をフラッシュせざるを得ません。すると切替直後は TLB が空で、しばらく全アクセスがミス=テーブルウォークになり、再充填コストが切替の隠れた代償として乗ります。
ASID は各 TLB エントリに「どのアドレス空間のものか」を表す ID を付けて解決します。照合時は ASID も含めて一致したエントリだけをヒット扱いにするため、別プロセスのエントリが混在していても誤適用が起きません。
ヒット条件: エントリのASID == 現在のASID
かつ エントリの仮想ページ番号 == 参照ページ番号
これにより切替時はフラッシュ不要で、レジスタの ASID/PCID を書き換えるだけで済みます。元のプロセスへ戻ったとき、追い出されずに TLB に残っていたエントリはそのまま再利用でき、再充填の山を避けられます。ASID のビット幅は有限なので、足りなくなれば OS が ID を再割り当て(その範囲だけ無効化)して循環利用します。
「仮想→物理の変換は MMU がハードウェアで行い、TLB がその結果をキャッシュする」「TLB ミスは多段ページテーブルのウォークで解決される」「ASID/PCID により切替時の全フラッシュを回避できる」の3点は頻出です。TLB は命令ではなくハードウェア機構である点に注意。
まとめ
- 多段ページテーブルは、仮想ページ番号を段ごとに分割した木構造で、疎なアドレス空間を省メモリに表現する。
- MMU はテーブルをウォークして物理アドレスを得るが、毎回では遅いため TLB が変換結果をキャッシュする。
- TLB ミスはウォークで充填され、マッピング変更時は明示的なフラッシュ(マルチコアでは TLB シュートダウン)が必要。
- ASID/PCID は切替時の全フラッシュを避け、再充填コストを下げる。
関連して、変換結果が無いときのページングとスワップ、変換機構を応用したメモリマップトファイルも合わせてどうぞ。
OS Article
仮想記憶のアドレス変換とMMU/TLBの内部を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
MMU
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 5
導入後に効く点
TLBは変換結果(ページ番号→フレーム番号)をキャッシュする小容量の連想メモリで、ヒットすればテーブルウォークを丸ごと省ける。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 5
判断チェックリスト
- 自社の用途が「MMU / TLB」に近いか確認する。
- 強みである「MMUは仮想アドレスの上位ビットを段ごとに切り出し、多段ページテーブルをハードウェアでウォークして物理アドレスへ変換する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。