このページの目次
ページ回収とスワップ
カバー範囲: LRU (匿名/ファイル) → kswapd + direct reclaim → スワップ機構 → 逆マップ (rmap) → ページキャッシュ回収 → 断片化解消 カーネルバージョン: 2.6 ~ 6.x。multi-gen LRU (6.1+) を重点的に注釈付きで解説
概要
物理メモリが不足すると、カーネルは一部のページを回収(解放)しなければなりません。回収対象は以下の2種類に分類されます。
- ファイルページ (file-backed): クリーンな場合はそのまま破棄(ディスクにコピーがあるため)、ダーティな場合は書き戻し
- 匿名ページ (anonymous): 回収するにはスワップ領域へ書き込む必要がある
回収戦略の中核は LRU (Least Recently Used: 最古使用優先) です。これは「冷たい」ページを外部へ追い出し、「熱い」ページを保持するという考え方です。しかし、数百GB規模のワークセットにおいて「熱さ」と「冷たさ」を判断することは極めて困難です。Linux は従来の双方向リストによる LRU から進化し、6.1 で導入された Multi-Gen LRU (MGLRU) により、大規模なワークセットやスキャン嵐(scanning storm)への対応が大幅に改善されました。
LRU リスト
従来の2つの LRU (2.6 ~ 5.x)
// mm/vmscan.c
// 各 memcg および各ノードに、一組の LRU リストが存在する:
;
// ページは active と inactive の間で移動する:
// 初回マップ → inactive
// 再アクセス → active へ昇格
// 長時間未アクセス → inactive へ降格
// inactive 内のページ → 回収候補
回収の優先度
// 回収順序 (ファイルページが匿名ページより優先される):
// 1. クリーンなファイルページ (drop, 最速)
// 2. ダーティなファイルページ (書き戻し)
// 3. 匿名ページ (スワップアウト)
//
// なぜファイルページが優先されるのか?
// クリーンなファイルページ: 直接破棄可能、I/O 0
// 匿名ページ: スワップへ書き込む必要あり → I/O オーバーヘッド
// ダーティなファイルページ: 書き戻す必要あり → I/O オーバーヘッド
//
// /proc/sys/vm/swappiness はこの優先度を制御する:
// 0: 可能な限りファイルページを回収し、やむを得ない場合のみスワップ
// 100: ファイルと匿名を同等に扱う
// デフォルト: 60
Multi-Gen LRU (6.1+)
従来の LRU の問題点:
- active/inactive の2世代のみ → グランularity(粒度)が粗い
- 熱さの判断は referenced bit と定期的なスキャンに依存
- 大規模ワークセットではスキャンコストが極めて高い(「スキャン嵐」)
MGLRU のアプローチ:
ページは世代(generation)ごとにグループ化され、各世代に独立した LRU が存在する
新世代 = アクティブ(直近でアクセスあり)
旧世代 = 冷たい(回収候補)
回収時は最も古い世代から開始 → 自然に熱いページが除外される
全ページをスキャンして熱さを判断する必要がない → スキャン量が大幅に削減
実装: mm/vmscan.c (MGLRUビルド時) + /sys/kernel/mm/lru_gen
Google および Meta ともに、CPU使用量の大幅な削減を報告
(スキャンオーバーヘッドの 50%〜90% 削減)
kswapd と Direct Reclaim
kswapd: バックグラウンド回収
// mm/vmscan.c
// 各 NUMA ノードに独自の kswapd カーネルスレッドが存在する
// 起動条件:
// 空きページ数が high watermark を下回ると kswapd が起動
// kswapd は空きページ数が high watermark を超えるまで継続的に回収を行う
static int
Direct Reclaim: 同期回収
// kswapd が追いつかない場合(メモリ割り当てが速すぎる場合)、
// 割り当て側が自らページを回収する:
→
→ →
→ →
→ →
// kswapd と同じ回収ロジックだが、同期的に実行
// 割り当て側は利用可能なページが得られるまでここでブロックされる
direct reclaim は割り当てレイテンシの急増を引き起こすため、「メモリプレッシャー」指標が注目される理由となっています。
スワップ
スワップパーティション/ファイル
# 設定
# プライオリティ (複数のスワップデバイスがある場合)
スワップアウト (匿名ページ → スワップ)
// mm/vmscan.c
// shrink_page_list() で匿名ページを回収する際:
if
// スワップキャッシュ:
// ページがスワップとメモリ間の遷移状態にある際のキャッシュ
// スワップアウトとスワップインの間に競合が発生した場合、
// スワップキャッシュが一貫性を保証する
スワップイン (ページフォルト → スワップ)
// mm/memory.c
// do_swap_page(): プロセスがスワップアウトされたアドレスにアクセスした
├─ // スワップキャッシュをチェック(メモリ内にある場合あり)
│ └─ 存在する場合: 直接マッピング(I/O 不要!)
│
└─ // スワップデバイスから先読み
└─
→ // I/O をサブミット
→ ページをスワップキャッシュに追加
→ PTE をマッピング
→ ユーザープロセスの再開
逆マップ (rmap)
// mm/rmap.c
// 逆マップが答える質問: 「特定の物理ページを、どのプロセスのどの仮想アドレスがマップしているか?」
// 匿名ページの場合:
// anon_vma + VMA のインターバル木を用いて、すべてのマッピングを走査
// page->mapping → anon_vma → anon_vma_chain → VMA
// ファイルページの場合:
// address_space + page->index を用いて検索
// page->mapping → address_space → i_mmap (インターバル木)
// rmap_walk(page, func):
// page のすべての PTE マッピングを走査
// 各マッピングに対して func を呼び出す → 例: try_to_unmap
// → PTE をスワップエントリとしてマークするか、クリア
// → TLB をフラッシュ
// try_to_unmap() はスワップの前提条件:
// すべての PTE 参照を見つけ、解除してからでなければページを回収できない
Compaction (断片化解消)
// mm/compaction.c
// 問題: バディアロケータは連続したページを必要とする(例: order 4 = 16ページ = 64KB)
// しかしメモリが断片化しているため、連続ブロックを割り当てられない
// compaction: 使用中のページを「移動」させ、連続した空き領域を確保する
// 1. スキャン: 空きページ(ターゲット)と使用中ページ(ソース)を検索
// 2. 移行: 使用中ページの内容を空きページへコピー
// 3. ページテーブルの更新: すべての PTE を新しい位置へ向ける
// 4. 繰り返し: 十分な連続空間が確保されるまで
// トリガー方法:
// 1. kcompactd: バックグラウンドスレッド(kswapd に類似)
// 2. direct compaction: 割り当て側が slowpath で自発的に断片化解消を実行
// compaction の状態確認:
cat /proc/vmstat | grep compact
デバッグと観測
# メモリプレッシャーの概要
# SwapTotal/SwapFree: スワップの使用状況
# Active/Inactive(anon/file): LRU 内のページ数
# Dirty/Writeback: 書き戻し待ちのページ
# 回収統計
|
# pgsteal_*: 回収されたページ数
# pgscan_*: スキャンされたページ数
# scan >> steal → LRU スキャンの効率が低い(MGLRU で改善)
# MGLRU が有効かどうかの確認
&&
# kswapd の動作確認
|
# compaction
|
# 手動での回収トリガー(テスト用、本番環境では注意して使用)
参考資料と拡張読み物
- カーネルドキュメント:
Documentation/admin-guide/sysctl/vm.rst,Documentation/mm/multigen_lru.rst - LWN:
- "Multi-generational LRU" (lwn.net/Articles/856932/)
- "The case for MGLRU" (lwn.net/Articles/916777/)
- "Swap and the page cache" (lwn.net/Articles/358953/)
- ソースファイル:
mm/vmscan.c— kswapd + LRU スキャン + direct reclaimmm/swap_state.c— スワップキャッシュmm/swapfile.c— スワップパーティション/ファイル管理mm/rmap.c— 逆マップmm/compaction.c— 断片化解消mm/page_io.c— スワップ I/O
キーワード: LRU, MGLRU, kswapd, direct reclaim, swap, swappiness, rmap, try_to_unmap, compaction, page cache reclaim