仮想記憶の内部 ─ TLB・ページウォーク・ASID
仮想アドレス変換が遅延の温床になる理由を、多段ページウォーク・TLB階層・ASID/PCID・ヒュージページから原理で押さえ、TLBミスを抑える勘所を掴めます。
- 1.x86-64は仮想アドレスをCR3起点に4段(または5段)たどるページウォークで物理アドレスに変換し、変換結果をTLBにキャッシュして次回以降の遅延を消す。
- 2.TLBはL1/L2の階層構造で、エントリは仮想ページ番号タグと物理フレーム番号・権限・ASIDを持つ。ページウォークキャッシュが中間段の再読み込みを省く。
- 3.ASID/PCIDでアドレス空間ごとにTLBを区別し、コンテキストスイッチ時の全フラッシュを回避。ヒュージページは1エントリの被覆範囲(TLBリーチ)を広げてミスを減らす。
なぜアドレス変換が性能の急所になるのか
プロセスが触る仮想アドレスは、そのままでは主記憶を指せません。MMU(メモリ管理ユニット)がページテーブルを引いて物理アドレスへ変換します。問題は、この変換がメモリ参照のたびに必要になることです。ページテーブル自体も主記憶上にあるため、素朴に実装すると1回のロードのために何度も主記憶を読むことになり、キャッシュメモリで隠したはずのアクセス遅延が変換のたびに蘇ります。
これを救うのが TLB(Translation Lookaside Buffer) です。TLB は「仮想ページ番号 → 物理フレーム番号」の変換結果をキャッシュする小さな連想メモリで、ヒットすればページテーブルを一切引かずに変換が1サイクル程度で完了します。仮想記憶の性能は、ほぼ TLB ヒット率で決まると言ってよいほどです。
多段ページテーブルとページウォーク
x86-64 は 4KB ページ・48bit 仮想アドレスを基本とし、ページテーブルを4段に分割します。仮想アドレスの上位 36bit をページ番号として 9bit ずつ4つに切り、各段で 512 エントリ(2^9)の表を1段ずつ索引します。
[ 47:39 | 38:30 | 29:21 | 20:12 | 11:0 ]
PML4 PDPT PD PT page offset
(L4) (L3) (L2) (L1) 下位12bit
変換の手続き(ページウォーク)は次の通りです。
walk(vaddr):
base = CR3 # 最上位表の物理ベース
for level in [PML4, PDPT, PD, PT]:
idx = vaddr の該当9bit
entry = MEM[base + idx*8] # 8バイトのPTEを主記憶から読む
if entry.present == 0: ページフォルト
base = entry.physical_addr
return base + page_offset
各段のエントリ(PTE)は次段の表の物理アドレスに加え、Present/書き込み許可(R/W)/ユーザー許可(U/S)/NX(実行禁止)/Accessed/Dirty などの制御ビットを持ちます。最終段の PTE が物理フレーム番号と最終的な権限を確定させます。ページウォークがハードウェアで自動実行される点が x86 の特徴で、ソフトウェアは表を用意するだけです(一部の RISC はソフトウェア管理 TLB を採る)。
5段ページング(LA57)を有効にすると PML5 が1段加わり、仮想アドレスが 57bit に拡張されます。1段増えるたびにウォークのメモリ参照が増えるため、段数は変換コストに直結します。
4段なら TLB ミス1回につき最悪4回の主記憶アクセスが直列に発生します。各アクセスがさらにキャッシュミスすれば数百サイクルが積み上がり、5段では最悪5回に増えます。TLB ミスのコストが「ただの再計算」では済まない理由がここにあります。
TLB の階層とエントリ構造
TLB 自体もメモリ階層と同じく多層化されています。コア直近に小容量・低遅延の L1 TLB(命令用 iTLB とデータ用 dTLB に分離、各数十エントリ)を置き、その背後に大容量の L2 TLB(STLB、数千エントリ) を共有します。L1 でミスしても L2 でヒットすれば、ページウォークを回避できます。
各 TLB エントリはおおむね次を保持します。
| フィールド | 役割 |
|---|---|
| 仮想ページ番号(タグ) | 照合に使う。参照アドレスのVPNと一致でヒット |
| 物理フレーム番号(PFN) | 変換結果。これとオフセットで物理アドレス完成 |
| 権限ビット(R/W・U/S・NX) | PTE由来の保護情報。違反でフォルト |
| ページサイズ | 4KB/2MB/1GB の区別 |
| ASID/PCID | どのアドレス空間の変換かを識別 |
| 有効・グローバルビット | エントリ有効性/全空間共有(カーネル領域) |
L1 TLB はヒット経路に直結するためフルアソシアティブ(全エントリ並列照合)が多く、L2 はエントリ数が多いのでセットアソシアティブで容量とコストを両立します。連想方式の使い分けはキャッシュメモリの原理と同じトレードオフです。
ページウォークキャッシュ:中間段の再利用
TLB ミス時のウォークでも、上位段(PML4・PDPT・PD)の参照結果は近傍アドレスで共通になりがちです。たとえば同じ 2MB 領域内を歩く限り、上位3段の表は変わりません。そこで MMU は最終段 PTE とは別に、**中間段のエントリを専用の小キャッシュ(ページウォークキャッシュ、PWC/paging-structure cache)**に保持します。
これにより、TLB ミスでも上位段がキャッシュにあれば最終段だけを読めばよく、ウォークの実効メモリアクセス回数が大幅に減ります。「TLB はミスしたが、ウォーク自体は1〜2アクセスで済む」状況を作るのがページウォークキャッシュの役目です。Intel ではこれが PML4/PDPT/PD の各レベルに対応するキャッシュとして実装されています。
ASID/PCID:フラッシュ回避
素朴な TLB はアドレス空間を区別しません。すると別プロセスへ切り替えるたびに、古い変換が誤って当たらないよう TLB を全フラッシュする必要があり、切替直後はミス嵐になります。
これを避けるのが ASID(Address Space ID) で、x86 では PCID(Process Context ID, 12bit) と呼びます。各 TLB エントリに「どのアドレス空間の変換か」を表す PCID を付け、照合時に VPN と PCID の両方が一致して初めてヒットとみなします。CR3 を切り替えても PCID が異なれば旧エントリは自然に当たらないため、フラッシュ不要でコンテキストスイッチを越えて変換を温存できます。戻ってきたプロセスは以前の TLB エントリをそのまま再利用できます。
カーネル領域のように全プロセスで共通の写像は、エントリにグローバルビットを立てて PCID 照合の対象外にします。これにより、ユーザー空間だけを区別しつつ共通のカーネル変換は全空間で共有でき、無駄な再ウォークを防げます。ただし Meltdown 対策の KPTI(カーネル・ユーザーのページテーブル分離)を併用する環境では、この共有が制限される点に注意が必要です。
ヒュージページ:TLBリーチの拡大
TLB のエントリ数は有限なので、1エントリが覆える物理範囲(TLBリーチ = エントリ数 × ページサイズ)が作業集合に届かないと、いくらヒット率を上げてもミスが残ります。4KB ページで 1024 エントリでも、リーチはわずか 4MB です。
そこで中間段の PTE を「次の表を指す」のではなく「そのまま大ページを指す終端」として扱い、PD レベルで止めれば 2MB、PDPT レベルで止めれば 1GB のヒュージページになります。1エントリの被覆が 512〜26万倍に広がるため、同じエントリ数で TLB リーチが桁違いに伸び、ウォーク段数も1〜2段短縮されます。
| ページサイズ | ウォーク終端段 | 1エントリの被覆 | 1024エントリのリーチ |
|---|---|---|---|
| 4KB | L1(PT) | 4KB | 約4MB |
| 2MB | L2(PD) | 2MB | 約2GB |
| 1GB | L3(PDPT) | 1GB | 約1TB |
代償として、大ページは確保に連続した物理メモリを要し、断片化が進むと割り当てに失敗します。また小さな書き換えでも単位が大きく、内部断片化やコピーオンライト時の無駄が増えます。OS は透過的ヒュージページ(THP)で自動昇格・降格を試みますが、レイテンシ重視の用途では明示的な確保が好まれます。
「TLB ミス → ページウォーク(x86-64 は4段、LA57 で5段)」「ウォークは最悪で段数ぶんの直列メモリアクセス」「PCID/ASID で全フラッシュ回避」「ヒュージページは TLB リーチを広げ段数も縮める」は頻出です。TLB は『何を』ではなく『どこに(物理フレーム)』を覚えるキャッシュであり、データを持つ通常キャッシュとは役割が別である点を区別できると応用に強くなります。
まとめ
- 仮想→物理変換はメモリ参照ごとに必要で、TLB ヒットなら即時、ミスなら多段ページウォークが走る。
- x86-64 は CR3 起点に PML4→PDPT→PD→PT の4段(LA57 で5段)をたどり、各段の PTE が次段アドレスと権限を持つ。
- TLB は L1/L2 階層で、エントリは VPN タグ・PFN・権限・ASID を保持。ページウォークキャッシュが中間段の再読み込みを省く。
- PCID/ASID でコンテキストスイッチ時の全フラッシュを避け、ヒュージページで TLB リーチを広げてミスを抑える。
変換結果を使うキャッシュ側の設計はキャッシュメモリの原理、ミス遅延を実行側で隠す仕組みはアウトオブオーダー実行が、OS から見たページテーブル管理は多段ページテーブルとASID/PCID の内部がそれぞれ掘り下げます。
CPU/メモリ/ディスク Article
仮想記憶の内部 ─ TLB・ページウォーク・ASIDを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
仮想記憶
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 5
導入後に効く点
TLBはL1/L2の階層構造で、エントリは仮想ページ番号タグと物理フレーム番号・権限・ASIDを持つ。ページウォークキャッシュが中間段の再読み込みを省く。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「仮想記憶 / TLB」に近いか確認する。
- 強みである「x86-64は仮想アドレスをCR3起点に4段(または5段)たどるページウォークで物理アドレスに変換し、変換結果をTLBにキャッシュして次回以降の遅延を消す。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。