スワッピングとページング機構の内部
メモリ不足でも動き続ける裏側を、匿名ページのスワップアウトからkswapdのリクレイム、swappinessの効き方、zram/zswapの圧縮スワップまで内部動作で理解できます。
- 1.スワップは、当面使われない匿名ページをストレージへ退避して物理メモリを空ける機構。PTEにはスワップ位置を示すエントリが書かれ、再アクセス時のメジャーフォルトでスワップインされる。
- 2.リクレイムはkswapdが背景で、足りなければ直接リクレイムが前景で行う。LRUのactive/inactiveリストとswappiness値が、ファイルページと匿名ページのどちらを優先回収するかを決める。
- 3.zramはメモリ内の圧縮RAMディスクをスワップ先にし、zswapはディスクスワップ前段の圧縮キャッシュとして働く。I/Oをメモリ内圧縮に置き換え、実効容量を増やしながらレイテンシを下げる。
スワップの役割:匿名ページの退避先
物理メモリが逼迫したとき、カーネルは当面使われそうにないページを ストレージへ追い出して(スワップアウト) フレームを空けます。ここで対象になるのは主に 匿名ページ(anonymous page) です。ヒープ・スタック・MAP_ANONYMOUS のように、ディスク上に元データを持たない ページがそれにあたります。
対比すると分かりやすいです。実行ファイルや mmap したファイルの ファイルバックドページ は、元データがファイルにあるため、ダーティでなければ単に捨てるだけで回収でき(再アクセス時はファイルから読み直す)、ダーティならファイルへ書き戻せば済みます。これは ページキャッシュとライトバックの仕組み の領域です。一方、匿名ページは捨てると中身が永久に失われるため、退避先=スワップ領域 が必要になります。これがスワップの存在理由です。
ファイルページにとってのバッキングストアがファイル本体なら、匿名ページにとってのバッキングストアがスワップ領域です。スワップを設けて初めて、匿名ページも「捨てて後で復元する」回収の土俵に乗れます。スワップが無い環境では匿名ページは回収不能なので、メモリ逼迫時はファイルページの回収と OOM killer に追い込まれやすくなります。
スワップアウトの手順とスワップエントリ
ページをスワップアウトする流れは次の通りです。
swap_out(page):
slot = スワップ領域から空きスロットを確保
if page がダーティ:
ストレージ(slot)へpage内容を書き出す # I/O
各PTEを「スワップエントリ」に書き換える # not present + slotを記録
対応するTLBエントリを無効化(flush)
物理フレームを解放してフリーリストへ返す
鍵は PTE(ページテーブルエントリ)の書き換え です。スワップアウト後、その仮想ページの PTE は not present(無効) になりますが、空にするのではなく スワップエントリ を埋め込みます。これは「どのスワップデバイスのどのスロットに退避したか」を符号化した値で、present ビットが立っていないので CPU から見れば通常の無効ページですが、フォルトハンドラはこのビット列を読んで スワップインすべきだ と判定できます。
再アクセスが起きると、無効 PTE へのアクセスとして ページフォルト が発生します。ハンドラはスワップエントリを解読してスロットを特定し、空きフレームを割り当てて スワップイン I/O を発行、読み終わったら PTE を present に戻して命令を再実行させます。このスワップインは必ずストレージ I/O を伴うため メジャーフォルト です。フォルト処理の全体像は デマンドページングとページフォルト処理 を参照してください。
スワップアウトはバックグラウンドで進められますが、スワップインは そのページを今まさに必要としているスレッドをブロック します。レイテンシは I/O 待ちそのもの(HDD でミリ秒、SSD でも数十マイクロ秒)であり、頻発するとアプリの体感が一気に悪化します。スワップは「容量の保険」であって「速度」ではない、という性質がここに表れます。
スワップキャッシュ:二重 I/O と競合の回避
スワップ周りで見落とされがちなのが スワップキャッシュ(swap cache) です。これは page cache の一部を使い、スワップスロットと物理ページの対応 を一時的に保持する仕組みです。目的は二つあります。
第一に 共有ページの整合性。同じ匿名ページを fork 後に親子で共有している場合など、複数の PTE が同一スワップスロットを指すことがあります。一方がスワップインしたとき、その物理ページをスワップキャッシュに登録しておけば、もう一方がフォルトしたときに 再度 I/O せず同じページに繋ぐ だけで済みます。
第二に 書き戻しの省略。スワップインしてもスワップスロットを即解放せず、ページとスロットの対応をキャッシュに残しておくと、その後ページが変更されないまま再びスワップアウトされる場合に 既にスロット上にある内容と同一 なので書き戻し I/O を省けます。ページがダーティになった時点でこの「スロットと同一」の前提が崩れ、スロットは無効化されて次回は書き戻しが必要になります。
| 状態 | PTEが指す先 | 意味 |
|---|---|---|
| resident | 物理フレーム | 通常どおりメモリ上に居住 |
| スワップキャッシュ内 | 物理フレーム(かつslot対応を保持) | メモリ上にあるが、スロット上の複製も生きている |
| スワップアウト済み | スワップエントリ(not present) | メモリ上には無く、スロットにのみ存在 |
kswapd とリクレイム:背景回収と直接回収
空きフレームを作る作業を リクレイム(reclaim, ページ回収) と呼びます。これは二つの経路で起こります。
- kswapd(バックグラウンドリクレイム):各 NUMA ノードに常駐するカーネルスレッド。空きメモリが low ウォーターマーク を下回ると起床し、high ウォーターマーク まで回復するまで非同期にページを回収します。割り当て要求とは別スレッドで進むため、アプリを止めません。
- direct reclaim(直接回収):それでも間に合わず、割り当て要求の最中に空きが足りないと、要求したスレッド自身 がその場で回収を行います。これは割り当てパスを 同期的にブロック するので、レイテンシスパイクの主因になります。
空きメモリ
high ───────────────── kswapd 停止(十分空いている)
low ───────────────── kswapd 起床(背景回収開始)
min ───────────────── 直接回収+緊急予約(前景でブロック)
↓ それでも作れない
OOM killer
回収対象の選別には LRU 近似 が使われます。Linux はページを active リスト と inactive リスト に分け、さらに 匿名(anon) と ファイル(file) で別管理します。新しく参照されたページは active 側へ昇格し、しばらく参照されないと inactive 側へ降格、inactive の末尾から回収候補になります。この二段構成は厳密 LRU の近似で、スキャンと参照ビット確認のコストを抑えます。アルゴリズムの背景は ページ置換アルゴリズム(LRU・Clock・WSClock) を参照してください。
swappiness:匿名とファイル、どちらを削るか
リクレイム時の根本的な判断は 「ファイルページを捨てるか、匿名ページをスワップアウトするか」 です。これを左右するのが vm.swappiness(0〜200、既定 60)です。
直感的には 大きいほど匿名ページのスワップアウトに積極的、小さいほどファイルページの回収を優先します。重要なのは、これが「スワップする/しない」のオンオフではなく、回収時に anon と file のどちらの LRU をどれだけスキャンするかの重み だという点です。カーネルは swappiness に加えて、両者の 最近の回収コスト(refault 距離=追い出した直後に再要求された度合い) も観測し、無駄の多い側を避けるよう配分を動的に調整します。
スワップ先が高速(zram/NVMe)なら swappiness を上げてコールドな匿名ページを積極退避し、ファイルキャッシュを温存するのが有利になりがちです。逆に低速 HDD スワップでは下げてファイルページ回収を優先します。swappiness=0 でも、空きが min ウォーターマークを割れば緊急回避として匿名ページはスワップアウトされ得ます。0 は「最後まで避ける」であって「絶対しない」ではありません。
zswap と zram:圧縮スワップの原理
スワップの弱点は ストレージ I/O の遅さ です。これを メモリ内の圧縮 に置き換えるのが圧縮スワップで、二つの方式があります。役割が異なるので混同しないでください。
| 項目 | zram | zswap |
|---|---|---|
| 正体 | 圧縮されたブロックデバイス(RAMディスク) | スワップデバイス前段の圧縮ライトバックキャッシュ |
| スワップ先の有無 | それ自体がスワップ先(バッキングストア不要) | 背後に通常のスワップデバイスが必要 |
| あふれた時 | デバイス容量を使い切ると以降は割当失敗 | 圧縮率の悪いページを背後のスワップへ書き出す |
| 典型用途 | ディスクスワップを持たない端末・組込み・WSL等 | ディスクスワップを高速化する加速層 |
zram は、メモリの一部を 圧縮された仮想ブロックデバイス として見せ、それをスワップ領域に指定します。ページをスワップアウトすると、ストレージへ書くのではなく LZ4 や zstd で圧縮してメモリ内に格納 します。スワップインは伸長(解凍)するだけで、ディスク I/O は発生しません。実質「メモリを圧縮して詰め込み直す」操作なので、I/O 待ちが消える代わりに CPU 時間と圧縮率分の実効容量増 を得ます。
zswap は、ディスクスワップの 手前に置く圧縮キャッシュ です。スワップアウト対象をまず圧縮してメモリ上のプール(zswap pool)に溜め、プールが一杯になったり圧縮率が悪いページは 背後の本物のスワップデバイスへ書き出します。多くのページがプール内で完結すれば遅いディスク I/O を避けられ、あふれた分だけディスクに逃がす、という二段構えです。
現代の CPU では LZ4 等の伸長は数 GB/s に達し、ページ 1 枚(4KB)の伸長は マイクロ秒未満。一方ディスク(SSD でも)からの読み出しは桁が大きい。つまり「圧縮メモリへの退避+伸長」は「ストレージ I/O」より速い、という非対称が圧縮スワップの本質です。引き換えに、圧縮されたページは伸長するまで実行に使えないため CPU を消費し、メモリ帯域も食います。
(1)スワップ対象が主に匿名ページである理由(ファイルページは元データがあるため捨てる/書き戻すで済む)。(2)kswapd=背景・非同期、direct reclaim=前景・同期でブロック、という違い。(3)swappiness は anon と file の回収配分の重みであり、オンオフではないこと。(4)zram は「スワップ先そのもの」、zswap は「ディスクスワップ前段のキャッシュ」という役割の差。この 4 点が頻出です。
まとめ
- スワップは 匿名ページの退避先。スワップアウト時に PTE を スワップエントリ に書き換え、再アクセス時の メジャーフォルト でスワップインする。
- スワップキャッシュ はスロットと物理ページの対応を保持し、共有ページの二重 I/O と無変更ページの再書き戻しを省く。
- リクレイムは kswapd(背景・非同期) と direct reclaim(前景・同期でブロック) の二経路。active/inactive × anon/file の LRU で候補を選ぶ。
- swappiness は匿名とファイルの回収配分の重み。zram は圧縮 RAM をスワップ先にし、zswap はディスクスワップ前段の圧縮キャッシュとして I/O をメモリ内圧縮へ置き換える。
前提として 仮想記憶のアドレス変換とMMU/TLBの内部 と デマンドページングとページフォルト処理、回収の選別は ページ置換アルゴリズム(LRU・Clock・WSClock)、ファイルページ側の挙動は ページキャッシュとライトバックの仕組み も合わせて読むと全体像が繋がります。
OS Article
スワッピングとページング機構の内部を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
スワップ
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
リクレイムはkswapdが背景で、足りなければ直接リクレイムが前景で行う。LRUのactive/inactiveリストとswappiness値が、ファイルページと匿名ページのどちらを優先回収するかを決める。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「スワップ / ページング」に近いか確認する。
- 強みである「スワップは、当面使われない匿名ページをストレージへ退避して物理メモリを空ける機構。PTEにはスワップ位置を示すエントリが書かれ、再アクセス時のメジャーフォルトでスワップインされる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。