カーネルが「物理メモリ上のページテーブルそのもの」へどうアクセスするか、という仮想アドレス空間レイアウトの設計問題。[[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]])。
  • VA→PA 変換は各段のテーブルを辿るだけ。x86_64::structures::paging::Translate::translate_addr 相当。PhysFrame がページに対応する物理領域。
  • TLB: 変換結果のキャッシュ(transparent: 利用者は存在を意識しない)。カーネルがページテーブルを書き換えたら invlpg で TLB をフラッシュする。GLOBAL フラグ付きエントリはアドレス空間切替でもフラッシュされない。
  • [[concepts/x86-interrupt-handling]] の page fault はこの変換失敗で起きる。

マッピング戦略

カーネルがページテーブル(物理アドレス)を操作するには、その物理アドレスを指す仮想アドレスが要る。方式の比較:

  1. 恒等マッピング (identity): VA = PA。実装は単純だが、ページテーブルが物理メモリ中に散在すると仮想アドレス空間が散らかる。
  2. 固定オフセットのマッピング: VA = PA + 定数オフセット。xv6 はこの方式。オフセットをページテーブル毎に変えれば断片化を抑えられる。
  3. 物理メモリ全体をマップ: 物理メモリ全域を連続した仮想領域に張る。x86-64 の 2MiB huge page(HUGE_PAGE フラグ, L2/L3 で許可)を使えば安価。例: 32GiB を L3×1, L2×32 で覆い、132KiB のテーブル占有で済む。
  4. 一時的オフセット: (L4,L3,L2)=(0,0,0) は L1 テーブル全体(2MiB)の恒等マッピングと等価。そこのエントリの Frame に書き込むことで一時マッピングを511個作れる。
  5. 再帰的ページテーブル (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)。