カーネルが「物理メモリ上のページテーブルそのもの」へどうアクセスするか、という仮想アドレス空間レイアウトの設計問題。[[concepts/xv6-paging]](xv6 固有)と [[concepts/blog-os-rust-kernel]] から共通する一般論を抽出。
前提: 多段ページテーブル
- x86-64 は 64bit VA を
[48..=40]=L4, [39..=31]=L3, [30..=22]=L2, [21..=12]=L1, [11..=0]=offsetに分解(上位はサインエクステンション)。512エントリ×4段。- 1ページ=4KB=12bit、1エントリ=64bit なので 1テーブルあたり
2^(12+3)/2^6 = 9bitのインデックス。 - xv6 は 4096エントリ×2段(
[[concepts/xv6-paging]])。
- 1ページ=4KB=12bit、1エントリ=64bit なので 1テーブルあたり
- VA→PA 変換は各段のテーブルを辿るだけ。
x86_64::structures::paging::Translate::translate_addr相当。PhysFrameがページに対応する物理領域。 - TLB: 変換結果のキャッシュ(transparent: 利用者は存在を意識しない)。カーネルがページテーブルを書き換えたら
invlpgで TLB をフラッシュする。GLOBALフラグ付きエントリはアドレス空間切替でもフラッシュされない。 [[concepts/x86-interrupt-handling]]の page fault はこの変換失敗で起きる。
マッピング戦略
カーネルがページテーブル(物理アドレス)を操作するには、その物理アドレスを指す仮想アドレスが要る。方式の比較:
- 恒等マッピング (identity): VA = PA。実装は単純だが、ページテーブルが物理メモリ中に散在すると仮想アドレス空間が散らかる。
- 固定オフセットのマッピング: VA = PA + 定数オフセット。xv6 はこの方式。オフセットをページテーブル毎に変えれば断片化を抑えられる。
- 物理メモリ全体をマップ: 物理メモリ全域を連続した仮想領域に張る。x86-64 の 2MiB huge page(
HUGE_PAGEフラグ, L2/L3 で許可)を使えば安価。例: 32GiB を L3×1, L2×32 で覆い、132KiB のテーブル占有で済む。 - 一時的オフセット:
(L4,L3,L2)=(0,0,0)は L1 テーブル全体(2MiB)の恒等マッピングと等価。そこのエントリの Frame に書き込むことで一時マッピングを511個作れる。 - 再帰的ページテーブル (recursive): 同一段のエントリが自テーブルを指すことを許すと、アクセス回数で任意段のテーブルを操作できる。x86 のページテーブル方式に強く依存する技巧。
ページテーブルエントリのフラグ
PageTableFlags(下位フラグ群): PRESENT(メモリ常駐), WRITABLE(書込可。L1 で落とすと read-only), USER_ACCESSIBLE(ring3 許可), WRITE_THROUGH/NO_CACHE(キャッシュ方針), ACCESSED/DIRTY(CPU が自動セット), HUGE_PAGE, GLOBAL, NO_EXECUTE(bit63, EFER で有効化時)。
ブートローダ([[entities/rust-osdev-tooling]])が起動時に既にページングを設定済みのため、カーネルは bootloader を修正してテーブルを書き換える必要がある。
関連
[[concepts/operating-system-kernel]]/[[concepts/malloc-allocator-internals]](ヒープの mmap)。