カーネルプリエンプションとレイテンシ
カーネル内部のどこまで横取りを許すかでシステムの応答性が決まります。4つのプリエンプションモデルとリアルタイム性・スループットの設計判断を原理から押さえられます。
- 1.カーネルプリエンプションとは、カーネルコードの実行中でも、より高優先度のタスクが横取り(横入り)できるかどうかの設計です。
- 2.非プリエンプティブ/自発的/フルプリエンプティブ/PREEMPT_RTの順に最悪レイテンシは縮みますが、切り替え増でスループットは下がります。
- 3.preempt_count(プリエンプションカウンタ)が0でないと横取りは保留され、これが応答遅延の主因の一つになります。
プリエンプションが決めるのは「応答の速さ」
プリエンプション(preemption、横取り)とは、実行中のタスクを途中で止めて、より優先すべき別タスクへ CPU を移すことです。ユーザー空間のコードは古くから横取りできました——タイマー割り込みでタイムスライスを切れば、/os/scheduling/ が次のタスクを選んで /os/context-switch/ を起こせます。
問題は カーネルの中で実行している最中 です。プロセスがシステムコールを発行してカーネルコードを走らせている間、より高優先度のタスクが起床したとき、ただちに横取りしてよいのか。それとも今のカーネル処理が一区切りつくまで待たせるのか。この一点の設計判断が、システムの 最悪応答レイテンシ と スループット を正反対の方向へ引っ張ります。
ここでのレイテンシは「イベント(割り込みやタスク起床)が発生してから、本来走るべき高優先度タスクが実際に CPU を得るまでの遅れ」です。平均値ではなく 最悪値(worst-case) が問題になります。動画再生が時々カクつく、産業制御で締め切りを1回でも落とすと不良品になる——いずれも最悪値が支配します。
4つのプリエンプションモデル
Linux はビルド時の設定(CONFIG_PREEMPT_*)で、カーネル内の横取り可否を4段階から選びます。応答性を取るかスループットを取るかの段階的なトレードオフです。
| モデル | カーネル内の横取り | 主な用途 | 最悪レイテンシ |
|---|---|---|---|
| 非プリエンプティブ (SERVER) | 不可。カーネルを抜けるか自発的に譲るまで継続 | バッチ・大量計算サーバー | 大(ミリ秒級〜) |
| 自発的 (VOLUNTARY) | 明示的な譲渡ポイントでのみ可 | デスクトップ寄りの汎用 | 中 |
| フルプリエンプティブ (PREEMPT) | 原則どこでも可(保持区間を除く) | 低遅延デスクトップ・組込み | 小 |
| PREEMPT_RT | ほぼ全域。ロックや割り込みも横取り可へ作り替え | リアルタイム制御 | 極小(数十マイクロ秒級) |
非プリエンプティブ(CONFIG_PREEMPT_NONE)
カーネルコードに入ったタスクは、自分でカーネルを抜ける(ユーザー空間へ戻る)か、明示的にブロックするまで横取りされません。長いシステムコールの最中に緊急タスクが起きても、その処理が終わるまで待たされます。横取りチェックや余計な切り替えが無い分、キャッシュ局所性が保たれ スループットは最大。一括処理に徹するサーバー向けです。
自発的プリエンプション(CONFIG_PREEMPT_VOLUNTARY)
非プリエンプティブを土台に、カーネルの随所へ 明示的なチェックポイント を仕込みます。Linux ではこれが might_resched() / cond_resched() です。長く走るループの区切りで「今、より優先すべきタスクがあるなら譲る」と能動的に問い合わせる方式です。
/* 長いカーネル処理ループの中に置く譲渡ポイント */
for (i = 0; i < huge_count; i++) {
do_some_work(i);
cond_resched(); /* 必要なら自発的に横取りを許す */
}
横取りできる地点が「あらかじめ仕込んだ場所」に限られるため、最悪レイテンシは チェックポイント間の最長区間 で決まります。スループットへの悪影響を抑えつつ、突出した遅延だけを削るバランス型です。
フルプリエンプティブ(CONFIG_PREEMPT)
保持区間(後述)でない限り、カーネルコードの任意の地点で横取りを許します。高優先度タスクが起きれば、割り込みからの復帰時などにただちにスケジューラが走ります。最悪レイテンシは小さくなりますが、横取りチェックと切り替えが増える分、/os/context-switch/ のオーバーヘッドが乗り、スループットはやや落ちます。
任意の地点で横取り可といっても、後述の preempt_count が0でない区間(スピンロック保持中・割り込みハンドラ実行中など)では横取りは保留されます。さらに割り込みを止める区間もある。フルプリエンプティブの最悪レイテンシを支配するのは、これら「横取りできない最長区間」です。「どこでも切れる」のは原則であって例外がある、と正確に捉えてください。
PREEMPT_RT
リアルタイムパッチ群(長らく out-of-tree、近年メインラインへ統合進行)。フルプリエンプティブでも残る「横取りできない区間」そのものを潰しにいきます。具体的には次のような作り替えを行います。
- スピンロックの多くをスリープ可能なミューテックスに変換し、ロック保持中でも横取り可能にする(優先度継承付き)。
- 割り込みハンドラをカーネルスレッド化(threaded IRQ)し、ハードIRQで止める区間を最小化、本処理をスケジュール可能なスレッドへ追い出す。
これにより最悪レイテンシを 数十マイクロ秒級 まで詰めます。代償は、ロックがスリープ化することによる平均コスト増とスループット低下。遅延の予測可能性(determinism)をスループットで買う のが PREEMPT_RT の本質です。
preempt_count ── 横取りの可否を握る数
「いつ横取りしてよいか」を実際に判定するのが、CPU ごとに持つ プリエンプションカウンタ(preempt_count) です。これが 0 のときだけ横取りを許可 します。
preempt_count は1つの整数に複数の意味を詰めたビットフィールド:
[ NMI | ハードIRQ | ソフトIRQ | preemptネスト深さ ]
preempt_disable() → preemptネスト部を +1
preempt_enable() → preemptネスト部を -1(0になった瞬間に再スケジュール判定)
preempt_count != 0 → 横取り保留(atomic context)
preempt_count == 0 → 横取り許可
スピンロックの取得は内部で preempt_disable() を呼ぶため、ロック保持中は自動的に preempt_count が上がり、横取りされません。割り込み(/os/interrupt-io/)に入ればハードIRQ部のカウントが立ち、これも非ゼロになります。
横取り要求は即実行されるのではなく、まずタスクに TIF_NEED_RESCHED フラグが立つだけです。そして preempt_count が 0 に戻る瞬間(preempt_enable() や割り込み復帰路)に 保留されていた再スケジュールがまとめて実行 されます。
スピンロック保持中は preempt_count が非ゼロ=横取り不可(atomic context)。ここで眠る(スケジュールを呼ぶ)処理を行うと、その CPU は二度と戻れずデッドロックします。「スピンロック中に sleep してはいけない」「割り込みハンドラ内でブロッキング処理を呼んではいけない」という鉄則は、すべて preempt_count が非ゼロな区間だから、と一本で説明できます。PREEMPT_RT がスピンロックをミューテックス化できるのは、この前提を作り替えているからです。
トレードオフを設計判断に落とす
4モデルは「優劣」ではなく 要求の違いへの最適化 です。判断軸を整理します。
| 要求 | 向くモデル | 理由 |
|---|---|---|
| 総処理量を最大化したい | 非プリエンプティブ | 切替最少でキャッシュ局所性が保たれる |
| 対話の体感を良くしたい | 自発的/フル | 突出遅延を削りつつ全体効率も維持 |
| 締め切りを絶対に守りたい | PREEMPT_RT | 最悪レイテンシの上限を保証的に小さくできる |
決定的なのは「平均が速いか」ではなく「最悪が保証されるか」です。スループット最適のシステムは平均応答が良くても、運悪く長いカーネル処理に重なった瞬間に大きく遅れます。リアルタイム用途が嫌うのはまさにこの テールレイテンシ(tail latency) で、平均を多少犠牲にしてでも上限を縮める PREEMPT_RT が選ばれます。
逆に、機械学習の学習や大規模バッチでは1回の遅延より総スループットが効くため、横取りを増やすのは損です。「何を最適化対象に置くか」を先に決め、それに合うプリエンプションモデルを選ぶ——順序を逆にしてはいけません。
従来は4モデルをビルド時に固定していましたが、近年は preempt_count を活用した動的プリエンプション(preempt= 起動オプションや debugfs での切替)が進み、再ビルドせず none/voluntary/full を選べる方向にあります。設計判断を運用時まで遅延できるようになりつつある、と押さえておくと実務で役立ちます。
まとめ
- カーネルプリエンプションは カーネルコード実行中の横取りをどこまで許すか の設計で、最悪レイテンシとスループットを正反対に動かす。
- 非プリエンプティブ→自発的→フルプリエンプティブ→PREEMPT_RT の順に最悪レイテンシは縮み、切替増でスループットは下がる。
- preempt_count が0のときだけ横取り可。スピンロック保持中や割り込み中は非ゼロになり、横取りは
preempt_enable()や割り込み復帰時まで保留される。 - 選定基準は平均ではなく 最悪値の保証。最適化対象(総処理量か締め切り遵守か)を先に定めてモデルを選ぶ。
- 関連して、切り替えそのもののコストは /os/context-switch/、横取りで選び直す側の仕組みは /os/cfs-scheduler-internals/ も参照。
OS Article
カーネルプリエンプションとレイテンシを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
プリエンプション
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
非プリエンプティブ/自発的/フルプリエンプティブ/PREEMPT_RTの順に最悪レイテンシは縮みますが、切り替え増でスループットは下がります。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「プリエンプション / リアルタイム」に近いか確認する。
- 強みである「カーネルプリエンプションとは、カーネルコードの実行中でも、より高優先度のタスクが横取り(横入り)できるかどうかの設計です。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。