userfaultfdによるユーザー空間ページフォルト処理
ページフォルトの解決をカーネルからユーザー空間へ肩代わりさせる仕組みで、ライブマイグレーションのダウンタイムを劇的に縮められます。
- 1.userfaultfdはページフォルトをカーネルではなくユーザー空間ハンドラへ委譲するメカニズムで、監視対象アドレス範囲をfdとして登録し、フォルト情報をread(2)で受け取る。
- 2.ライブマイグレーションでは転送前のページへのアクセスをUFFDIO_COPYで遅延充足し、post-copy方式でダウンタイムをメモリ量に依存させない設計にできる。
- 3.CRIUのlazy-restoreはこの仕組みで復元プロセスを即座に起動しつつ、実際に触れられたページだけを後から流し込む。
userfaultfdが解く問題──フォルト処理をカーネルの外に出す
通常、ページフォルト はCPU例外としてカーネルに落ち、カーネルのフォルトハンドラがページテーブルを埋めて復帰します。この処理は完全にカーネル内で完結しており、ユーザー空間はフォルトが起きたことすら知りません。
userfaultfd はこの前提を崩します。特定のアドレス範囲について「フォルトが起きたらカーネルが自動処理せず、ユーザー空間プロセスに通知して判断を委ねる」よう登録できる仕組みです。userfaultfd(2) システムコールがファイルディスクリプタを返し、以後の流れは次のようになります。
1. fd = userfaultfd(O_CLOEXEC | O_NONBLOCK)
2. ioctl(fd, UFFDIO_API, ...) // APIバージョンをネゴシエート
3. ioctl(fd, UFFDIO_REGISTER, {range, mode}) // 監視対象範囲とモードを登録
4. 別スレッドがフォルト処理ループへ:
read(fd, &msg, sizeof(msg)) // フォルトイベントを受け取る(ブロック)
msg.arg.pagefault.address // フォルトが起きたアドレス
msg.arg.pagefault.flags // 読み取り/書き込み等の属性
... ページ内容を用意する(他ホストから転送・ゼロ埋め等)...
ioctl(fd, UFFDIO_COPY, {dst, src, len}) // ページを書き込みつつフォルトを解消
フォルトを起こしたスレッドは、ハンドラが UFFDIO_COPY(または UFFDIO_ZEROPAGE 等)を発行するまでその場でブロックし続けます。カーネルは「誰かがこのアドレスを埋めてくれるまで待つ」だけの役に回り、ページの中身をどこから持ってくるかは完全にユーザー空間の裁量です。
マルチレベルページテーブル を埋める作業自体は最終的にカーネルが行いますが、「どのデータで埋めるか」を決める部分がユーザー空間へ移譲される点がuserfaultfdの本質です。デマンドページングがディスク上のファイルやゼロページを前提にするのに対し、userfaultfdはネットワーク越しの転送や任意のロジックをフォルト解決に挟めます。
登録モードと通知の粒度
UFFDIO_REGISTER では、どの種類のフォルトを捕捉するかをモードで指定します。
| モード | 捕捉するイベント | 主な用途 |
|---|---|---|
| UFFDIO_REGISTER_MODE_MISSING | ページがまだ存在しない(不在フォルト) | ライブマイグレーションのpost-copy、遅延ロード |
| UFFDIO_REGISTER_MODE_WP | 書き込み保護フォルト(write-protect) | ダーティページ追跡、CRIUのpre-copy高速化、CoWに似た用途 |
| UFFDIO_REGISTER_MODE_MINOR | ページキャッシュ上にはあるがプロセスにマップされていない場合のフォルト | 共有ファイルバックのメモリでのハンドラ介入(比較的新しい拡張) |
MISSINGモードが最も基本的で、後述のライブマイグレーションやlazy-restoreはこれを使います。WPモードは、ページを実際に不在にせずとも「書き込みだけ捕捉したい」場合に使え、フォルトの粒度を「読めるが書けない」まで細かく制御できます。
ライブマイグレーションへの応用──post-copyによる遅延ページング
VMやコンテナをホスト間で移動するライブマイグレーションでは、伝統的にpre-copy(先に全メモリを転送し、差分を繰り返し送ってから最後に切り替える)が使われます。しかしメモリが大きく更新頻度が高いワークロードでは、差分収束に時間がかかり停止時間が伸びがちです。
post-copyはこれを逆転させます。先に実行を新ホストへ切り替え、メモリは触られた時に初めて転送する方式です。userfaultfdはこの遅延転送を実装する土台になります。
新ホスト側の流れ:
1. VMのメモリ領域全体をuserfaultfdで監視登録(MISSINGモード)
2. VCPUを新ホストで即座に起動(メモリはまだほぼ空)
3. VCPUが未転送ページへアクセス → ページフォルト → ハンドラへ通知
4. ハンドラが旧ホストへ「このページをくれ」と要求 → ネットワーク経由で受信
5. UFFDIO_COPYで受信データをページに書き込み、フォルトを解消してVCPUを再開
6. バックグラウンドで残りのページも先回りして順次転送(プリフェッチ)
ポイントは、ダウンタイムが「実行を切り替える一瞬」だけで済み、メモリ量に比例して伸びないことです。pre-copyのダウンタイムは「最後に残った差分の転送時間」に左右されメモリサイズや更新率に引きずられますが、post-copyは制御を渡した直後から新ホストで動き出し、以降のページ取得はフォルトドリブンで背後に隠せます。
post-copyは移行直後のダウンタイムを縮める一方、新ホストが旧ホストのメモリに依存し続ける期間が生まれます。この間に旧ホストがクラッシュ・ネットワーク分断すると、新ホストは未転送ページを永久に取得できずVMが停止します。pre-copyにはこの依存がありません(切り替え時点で全データが既に手元にある)。また、頻繁にフォルトが起きる初期はページ取得のレイテンシがそのままアプリケーションの体感遅延に乗るため、多くの実装はpre-copyとpost-copyを組み合わせるハイブリッド方式を採ります。
QEMU/KVMのpost-copyマイグレーションはこの仕組みの代表例で、KVM/QEMUのアーキテクチャ が提供するハードウェア仮想化支援 の上で、ゲスト物理メモリをuserfaultfdで監視してVCPU実行と並行にページを充足します。
CRIUでの利用──lazy-restoreによる即時復元
CRIU(Checkpoint/Restore In Userspace)はプロセスの実行状態をチェックポイント(スナップショット)として保存し、後で復元するツールです。通常の復元は、保存されたメモリイメージを全て読み込んでからプロセスを再開しますが、メモリが大きいと復元完了までの待ち時間が無視できません。
lazy-restoreはこれをuserfaultfdで解決します。
1. 復元対象のメモリ領域をuserfaultfdで監視登録
2. プロセスをほぼ即座に起動(実際のページ内容はまだロードしていない)
3. プロセスが未ロードのページへアクセス → フォルト
4. CRIUのページサーバー(別プロセス/別ホスト)からそのページだけを取得
5. UFFDIO_COPYで充足し実行継続
これはライブマイグレーションのpost-copyと構造的に同一で、「巨大なメモリイメージの復元」を「触れたページから順次埋める遅延ロード」に変換します。コンテナのコールドスタート短縮や、コンテナランタイムの内部動作 と組み合わせた高速チェックポイント/リストアの文脈で使われます。
性能上の注意点──フォルト経路のコストとスケーラビリティ
userfaultfdは「遅い経路をユーザー空間へ移す」代償として、通常のページフォルトより段数が増えます。
- コンテキストスイッチの往復: フォルトを起こしたスレッドはブロックし、別スレッドで動くハンドラが
read()で起床し処理してからioctl(UFFDIO_COPY)を呼ぶまで、最低でも2回のスケジューリング判断が挟まります。カーネル内で完結する通常のフォルトに比べ、レイテンシは明確に増加します。 - シリアライズの懸念: 単一のuffdとハンドラスレッドに監視範囲を集約すると、多数のVCPU/スレッドが同時にフォルトした場合にハンドラがボトルネックになります。実運用では複数ハンドラスレッドで範囲を分割するか、非同期I/Oでネットワーク待ちと処理を重ねる設計が必要です。
- プリフェッチとの併用が前提: フォルト都度のネットワーク往復(マイグレーションの場合)は無視できないため、実装は「触れられそうなページを先読みで転送しておく」バックグラウンド転送と組み合わせるのが通例です。フォルトドリブンの充足は「間に合わなかった場合の保険」として機能します。
userfaultfdは元々特権操作(CAP_SYS_PTRACE 相当)を要求していましたが、後にsysctl vm.unprivileged_userfaultfd で非特権プロセスからの利用を許可できるようになりました。これは同時に攻撃面を広げます。フォルトハンドラがページ充足までの間、意図的に応答を遅延させれば、カーネル内の特定コードパスの実行タイミングを外部から引き延ばせるため、これを悪用してレースコンディションの成立確率を上げる攻撃(あるページへのアクセスとカーネル側の別処理との間にフォルト待ちの遅延を挟み込み、通常なら成立しにくい競合状態を意図的に広げる手法)が知られています。非特権環境でuserfaultfdを有効にする場合は、この「フォルト応答を任意に遅らせられる」性質がもたらすリスクを踏まえた上で許可範囲を判断する必要があります。
- userfaultfdはページフォルトの解決をユーザー空間ハンドラへ委譲する仕組み。カーネルはフォルトの通知と最終的なページテーブル更新だけを担う。
- 登録モードは主にMISSING(不在フォルト)とWP(書き込み保護フォルト)。マイグレーションやlazy-restoreはMISSINGを使う。
- ライブマイグレーションのpost-copyは「先に実行を切り替え、メモリはフォルト駆動で後から転送」する方式で、ダウンタイムをメモリ量に依存させないのが利点。旧ホストへの依存継続がトレードオフ。
- CRIUのlazy-restoreも同じ発想で、プロセスを即起動しつつ触れたページだけを後から充足する。
- 性能面はフォルト経路のコンテキストスイッチ往復増、セキュリティ面はフォルト応答遅延を利用したタイミング攻撃が主な注意点。
まとめ──フォルトという「待ち」をユーザー空間の裁量に開く
userfaultfdの本質は、ページフォルトという本来カーネル内で完結する処理に外部からの充足という選択肢を持ち込むことです。監視対象範囲を登録し、フォルトが起きるとハンドラがそれを検知して任意のデータで埋め、UFFDIO_COPY 等で解消する——この一連の流れが、ライブマイグレーションのpost-copy(実行を先に切り替え、メモリは触れられた分だけ遅延転送)や、CRIUのlazy-restore(プロセスを即座に起動し実メモリは後追いでロード)という、いずれも「巨大な状態転送を待たずに動き出す」ユースケースを支えます。代償はフォルト経路のレイテンシ増とスケジューリングの複雑化であり、非特権利用時には応答遅延を利用したタイミング攻撃にも注意が必要です。関連して、mmapとページフォルトの結合 がフォルトの基本的な発生源を、copy-on-write が同じくページテーブル操作でフォルトを利用する別の代表例を補強します。
OS Article
userfaultfdによるユーザー空間ページフォルト処理を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
userfaultfd
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
ライブマイグレーションでは転送前のページへのアクセスをUFFDIO_COPYで遅延充足し、post-copy方式でダウンタイムをメモリ量に依存させない設計にできる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「userfaultfd / ページフォルト」に近いか確認する。
- 強みである「userfaultfdはページフォルトをカーネルではなくユーザー空間ハンドラへ委譲するメカニズムで、監視対象アドレス範囲をfdとして登録し、フォルト情報をread(2)で受け取る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。