- Linuxのプロセスメモリ構造を実際の動作レベルで説明し、仮想アドレス空間と物理メモリの関係を段階的に解説
- ページテーブル、VMA、mmap、page fault、CoW などの中核メカニズムを中心に、プロセスがメモリをどのように所有しアクセスするかを具体的に説明
/proc ファイルシステムを通じて プロセスごとのメモリ状態 を観察する方法と、pagemap、kpageflags など高度な診断ツールの役割を紹介
- Transparent Huge Pages (THP)、userfaultfd、PAGEMAP_SCAN など最新カーネル機能による性能最適化とユーザー空間ダーティトラッキング手法を扱う
- Meltdown対策のPTI、TLBフラッシュ、W^Xポリシー などセキュリティと性能に関わるカーネル設計原則もあわせて説明し、Linuxメモリ管理の全体像への理解を提供
プロセスメモリの基本構造
- プログラムが実行されると巨大な連続メモリがあるように見えるが、実際には Linuxカーネルがページ単位で動的に構成 している
- CPUはページテーブルを参照して仮想アドレスを物理フレームへ変換
- マッピングがなければ ページフォルト(page fault) が発生し、カーネルが新しいページを割り当てるかエラーを返す
- 物理RAMが不足すると、カーネルは未使用ページをディスクへ退避したり、ファイルページを削除したりして空間を確保
/proc はカーネルがメモリ上に構成した 仮想ファイルシステム で、プロセスやカーネルの状態をファイルとして公開する
アドレス空間とVMA
- 各プロセスは 1つのアドレス空間オブジェクト を持ち、その内部は複数の VMA(Virtual Memory Area) で構成される
- VMAは同じ権限(R/W/X)と同じバックエンド(匿名メモリまたはファイル)を持つ連続アドレス範囲
- ページテーブルはハードウェアが参照する構造で、仮想ページと物理ページ間のマッピング情報(PTE) を保存する
- アドレス空間の変更は3つのシステムコールで行われる
mmap: 新しい領域を作成
mprotect: 権限を変更
munmap: マッピングを削除
- ページは 4KiBの基本単位 で、一部のシステムは2MiB・1GiBの大きなページもサポートする
/proc/self/mapsで見るメモリ構成
cat /proc/self/maps コマンドでプロセスのメモリマップを確認できる
- 実行ファイルのコード・データ・bss、ヒープ、匿名マッピング、共有ライブラリ、スタックなどが表示される
[vdso] と [vvar] 領域は、カーネルがマッピングした 高速システムコール用のコードとデータ である
mmap の動作原理
mmap は実際のメモリ割り当てではなく、アドレス空間に対する約束を記録する もの
- ファイルマッピングでは
offset はページ境界に揃っている必要があり、ファイル終端を超えるアクセスでは SIGBUS が発生する
MAP_SHARED はファイルに直接反映され、MAP_PRIVATE は 書き込み時コピー(CoW) により独立したページを生成する
MAP_FIXED_NOREPLACE は指定アドレスにすでにマッピングがある場合に失敗させることで安全性を確保する
最初のアクセスとページフォルト
- 新しいマッピングへの最初のアクセス時、CPUがページテーブルを見つけられないと ページフォルト が発生する
- カーネルはアドレスの妥当性、アクセス権、存在有無を検査する
- 匿名マッピングなら0で埋めた新しいページを割り当て、ファイルマッピングならページキャッシュから読み込む
- minor fault はデータがすでにRAM上にある場合、major fault はディスクI/Oが必要な場合である
- スタックは ガードページ で保護されており、下方向へ行き過ぎたアクセスでは
SIGSEGV が発生する
fork() と MAP_PRIVATE のCopy-on-Write
fork 時には親と子が 同じ物理ページを共有 し、両方とも読み取り専用としてマークされる
- 書き込み時点でのみ新しいページが複製され、独立性が保たれる
MAP_PRIVATE のファイルマッピングも同じ原理で動作する
- 関連オプション
vfork: 親のアドレス空間を共有
clone(CLONE_VM): スレッドを生成
MADV_DONTFORK, MADV_WIPEONFORK: 子プロセスでマッピングを除外、または0で初期化
権限変更とTLB無効化
mprotect でページ権限を変更すると、カーネルは VMAの分割とページテーブルの修正 を行い、その後 TLB無効化 を実施する
- W^Xポリシー により、ページは同時に書き込み・実行可能にはできない
- TLB(Translation Lookaside Buffer)は最近のアドレス変換を保持するキャッシュで、無効化時には一時的な遅延が発生する
/proc を通じた詳細観察
/proc/<pid>/maps、smaps、smaps_rollup で領域ごとの権限・RSS・HugePage使用量を確認できる
/proc/<pid>/pagemap はページ単位の状態(存在、スワップ、PFNなど)を提供するが、PFNは一般ユーザーには非公開
/proc/kpagecount、/proc/kpageflags はPFNごとのマッピング数とページ属性(匿名、ファイル、dirty など)を表示する
mincore、SEEK_DATA/SEEK_HOLE により疎ファイルのデータ/ホール領域を識別できる
PAGEMAP_SCAN と userfaultfd を組み合わせることで ユーザー空間ダーティトラッキング を実装できる
Transparent Huge Pages (THP) と mTHP
- THP は頻繁にアクセスされるメモリを自動的に大きなページ(2MiBなど)へまとめ、TLB効率を向上 させる
khugepaged スレッドが隣接ページをマージする
- mTHP は16KiB・64KiBなど、さまざまなサイズの 可変大型ページ(folio) をサポートする
/proc/self/smaps の AnonHugePages、FilePmdMapped で使用有無を確認できる
/sys/kernel/mm/transparent_hugepage/ でシステム全体の設定を管理する
MADV_HUGEPAGE、MADV_NOHUGEPAGE で領域単位の制御が可能
ユーザー空間ダーティトラッキング
userfaultfd と PAGEMAP_SCAN を使うことで 変更されたページだけをコピー できる
- カーネルが1回の原子的操作でスキャンと書き込み保護を行う
- スナップショット、ライブマイグレーションなどで効率的
TLBフラッシュのメカニズム
- x86におけるTLB無効化には2つの方式がある
INVLPG: 単一ページを無効化
- ページテーブルルートの再読み込みによる全体フラッシュ
PCID と INVPCID は プロセスごとのTLBタグ管理 により不要なフラッシュを減らす
tlb_single_page_flush_ceiling は、カーネルがページ単位フラッシュと全体フラッシュのどちらを選ぶかのしきい値である
Meltdown対策: Page Table Isolation (PTI)
- Meltdown は投機実行中にカーネルデータがキャッシュ経由で漏えいしうる脆弱性である
- Linuxは PTI(Page Table Isolation) によりユーザー・カーネルのアドレス空間を分離する
- エントリ時に
CR3 を切り替えてカーネル専用ページテーブルを使う
PCID を活用してTLBフラッシュを最小化する
- デフォルトで有効化されており、
nopti で無効化できる
カーネルの安全なマッピング変更手順
- マッピング変更時の順序
- キャッシュ規則を処理
- ページテーブルを修正
- TLBを無効化
- カーネル内部マッピング(
vmap、vmalloc)でもI/Oの前後でキャッシュ・TLB同期を行う
- 一部アーキテクチャではコードコピー後に 命令キャッシュのフラッシュ が必要になる
x86のスタックと呼び出し構造
- 64ビットモードでは RIP、RSP、RBP レジスタを使用し、スタックは下方向へ成長する
- System V AMD64 ABIに従い、引数は RDI、RSI、RDX、RCX、R8、R9、戻り値は RAX で受け渡される
- ユーザーモードは ring 3、カーネルは ring 0 で、システムコールや割り込みはゲートを通じて切り替わる
エラー状況と診断
mmap → EINVAL: ファイルオフセットの整列エラー
mmap → ENOMEM: 仮想空間不足または overcommit 制限
- ファイルマッピングアクセス時の
SIGBUS: EOF超過アクセス
mprotect(PROT_EXEC) → EACCES: noexec マウントまたは W^Xポリシー
fork() 後のRSS増加: CoWによるページ複製
MAP_FIXED で既存マッピングを上書き → MAP_FIXED_NOREPLACE 推奨
実務向けチェックリスト
- 即時にメモリを確保:
mmap + PROT_READ|PROT_WRITE + MAP_PRIVATE|MAP_ANONYMOUS
- コード生成時: W^Xを維持し、
mprotect(PROT_READ|PROT_EXEC) を使う
- ファイルマッピング時:
offset をページ境界に揃え、EOF超過アクセスを禁止
- ページフォルトが多いとき:
MADV_WILLNEED または事前アクセス
- メモリ使用量分析:
/proc/<pid>/smaps_rollup → /proc/<pid>/maps
- 大規模プロセスの fork: CoWを考慮し、子では
exec を使う
- 遅延に敏感な環境: THP/mTHP、
mlock、TLBの挙動を観察
まだコメントはありません。