Dev Study
OSと低レイヤのしくみ コース

7. TLB — なぜ毎回ページテーブルを辿らずに済むのか

TLB(Translation Lookaside Buffer)が仮想アドレス変換の結果をキャッシュする仕組みを、図で追っていきましょう。

① TLBがないと1回のロードが4回のメモリ参照になる

Sv39(RISC-V, MODE=8)のページウォーク: 3段テーブル + 実データ
lw a0, (a1)仮想アドレス1個を読みたいだけ
satp.PPN からルートを取得
Level 2 テーブルVPN[2] = 9ビットで索引
メモリ参照 1回目
PTE の PPN を辿る
Level 1 テーブルVPN[1] = 9ビットで索引
メモリ参照 2回目
PTE の PPN を辿る
Level 0 テーブルVPN[0] = 9ビットで索引
メモリ参照 3回目
葉PTE から物理ページ番号が確定
実データやっと本来読みたかった4バイト
メモリ参照 4回目
  • ・Sv39 は 3段構成、ページサイズ 4KiB、PTE は 8バイト(RISC-V Privileged Spec)
  • ・仮想アドレス下位12ビットはページ内オフセットで、変換されずそのまま使われます
  • ・つまり素朴に実装すると、1回のロードがメモリ参照4回に化けます

アドレス変換は「テーブルを何段も辿る」処理です。段数がそのままメモリ参照回数になります。

② TLBは「VPN → PPN」の答えだけを覚える

TLBエントリが持っている情報
仮想アドレス 0x0000_3F2A_B000VPN = 上位27ビット
TLBを引く(連想検索)
TLBエントリVPN → PPN
途中の3段は記録しない
付随情報ASID / R,W,X / U / G / A,D
PPN が即座に出る
物理アドレス確定PPN << 12 | オフセット
  • ・TLBが覚えるのは「入口(VPN)と出口(PPN)」だけで、途中の経路は保存しません
  • ・葉PTEの権限ビット(R/W/X/U)も一緒に持つので、保護チェックも同時に済みます
  • ・ページ単位でキャッシュするため、4KiBページなら1エントリが4096バイト分の変換をカバーします

3段のページウォークの「結論」だけを保存します。経路を捨てて答えだけ持つのがTLBの本質です。

③ ヒットとミスで、メモリ参照回数がこれだけ変わる

同じ1回のロード命令に対する2つの経路
仮想アドレスが来る
TLBを検索
TLBヒット追加のメモリ参照 0回
TLBミスページウォーク発生
それぞれの結末
データ参照 1回で完了合計 1回
3段の走査 + データ参照合計 4回
ミス時は結果を登録
TLBにVPN→PPNを充填次回の同ページアクセスはヒット側へ
  • ・Sv39 では 3段なので、ミス時の追加メモリ参照は最大3回になります
  • ・ページ単位のキャッシュなので、同じページ内を連続アクセスするコードは2回目以降ほぼヒットします
  • ・ミスしても結果が充填されるため、局所性のあるプログラムほどTLBが効きます

ヒットなら追加参照ゼロ、ミスなら段数分。TLBの有無がメモリ帯域の消費量を直接決めます。

④ なぜ「小さくて速い」設計なのか

TLBが数十〜数千エントリしか持たない理由
要求全メモリ参照の手前に置かれる
制約になる
CPUの1サイクル内で引き終える必要があるL1キャッシュアクセスと並行して動く
だから
全連想 or 少数ウェイ全エントリを同時比較
回路量が大きい
エントリ数を絞る数十〜数千程度
面積と電力の制約
足りない分を補う
L1 TLB / L2 TLB の階層化小さく速い層 + 大きく遅い層
ラージページ1エントリで2MiB/1GiBを担当
  • ・Sv39 は葉PTEを上位の段に置くことでメガページ(2MiB)・ギガページ(1GiB)を表現できます
  • ・ラージページは1エントリのカバー範囲を広げるため、TLBの実効容量を増やす効果があります

TLBは全メモリ参照の直列パス上にあります。だから容量より速度を優先した設計になっています。

⑤ プロセス切り替えでフラッシュが要る理由と、ASID/PCIDによる回避

同じVPNが別プロセスでは別のPPNを指す
プロセスAVPN 0x1000 → PPN 0x8A2
プロセスBVPN 0x1000 → PPN 0x41F
タグなしTLBだと区別できない
A→B の切り替えで全フラッシュ残すと他プロセスのメモリを読んでしまう
切替直後は全ミス
タグを付けると
RISC-V: ASIDsatp[59:44]、Sv39ではASIDMAX=16ビット
x86: PCIDCR4.PCIDE=1 のとき CR3[11:0] の12ビット
結果
フラッシュ不要で切り替え両プロセスのエントリが同居できる
  • ・RV64のsatpは MODE[63:60] / ASID[59:44] / PPN[43:0] という構成です(RISC-V Privileged Spec)
  • ・ASIDMAX は Sv32 で 9ビット、Sv39/Sv48/Sv57 で 16ビットです
  • ・ASIDはハートごとにローカルな意味を持ち、同じASID値を別ハートで別空間に使っても構いません
  • ・ASIDを別のページテーブルに再利用するときは、satpを更新してから rs1=x0・rs2=該当ASID で SFENCE.VMA を実行します

タグがないと切り替えのたびに全フラッシュが必要です。ASID/PCIDはこの全ミス期間をなくすための仕組みです。

⑥ TLBシュートダウン — 他CPUのTLBは自分では消せない

マルチコアでページテーブルを書き換えたとき
CPU0 がPTEを無効化munmap など
しかし
CPU1 の TLB古いVPN→PPNが残存
解放済みページを読めてしまう
CPU2 の TLB同上
ハードウェアは自動で消さない
そこでIPIを送る
プロセッサ間割り込み該当CPUに無効化を依頼
各CPUが自分で無効化
RISC-V: SFENCE.VMArs1=対象VA / rs2=対象ASID
x86: INVLPG / CR3書換ページ単位 or 全体
  • ・Linuxのインタフェースは flush_tlb_mm() / flush_tlb_range() / flush_tlb_page() です
  • ・kernel.org の cachetlb ドキュメントは、SMPではこれらの副作用がシステム内の全プロセッサで起きるよう定義を拡張する、と述べています
  • ・同ドキュメントは、その空間が一度も実行されていないCPUではフラッシュを省くなどの最適化が可能だとも述べています
  • ・消したエントリは次のアクセスでページウォークし直しになるため、必要な範囲だけを狙って無効化するのが基本です

TLBはCPUごとに独立したキャッシュです。コヒーレンシがハードウェアで保証されないため、ソフトウェアが明示的に無効化を配ります。

公式ドキュメントで詳しく ↗