割り込み処理の二段構え(上半分/下半分)
割り込みハンドラを軽くするだけで応答性とスループットが両立できる仕組みがわかります。上半分で最小限だけ受け、重い処理を下半分へ逃がす設計を原理から押さえられます。
- 1.上半分(hardirq)は割り込み無効で走る最小区間。デバイス確認とACK、下半分の起動予約だけを行い即座に抜ける。
- 2.下半分はsoftirq/tasklet/ワークキューに分かれる。softirqは並行・非眠り、taskletは同型の直列化、ワークキューだけがプロセス文脈で眠れる。
- 3.IRQ affinityで割り込みをコアに固定し、NAPIは割り込みを止めてポーリングに切り替えることで高負荷時の割り込み嵐を消す。
なぜ二段に分けるのか
ハードウェア割り込みハンドラ(上半分、Linux では hardirq 文脈)は、原則として割り込みを無効化した状態で走ります。少なくともそのコアの同じ IRQ 線、設計によっては全 IRQ が抑止されます。この区間が長引くと、後続の割り込みが取りこぼされたり遅延し、タイマやネットワークの応答が崩れます。さらに hardirq 文脈ではスケジューラを呼べないため、眠る処理(メモリ確保で待つ、ロック待ちで眠る、ユーザー空間とのコピー)は一切できません。
そこで「最小限だけ即座にこなし、重い仕事は後回し」にします。前者が上半分、後者が下半分(bottom half)です。この発想は /os/interrupt-io/ で触れた「ISR は短く」の具体的な実装でもあります。
上半分が実際にやること
上半分は次の最小限に徹します。
1. 自分のデバイスが割り込んだか確認(共有IRQの切り分け)
2. デバイスに割り込みをACK(要因レジスタのクリア等)
3. 受信データの所在をメモ、または下半分の起動を予約
4. すぐ return(数マイクロ秒以内が理想)
ポイントは「処理しない、予約だけする」ことです。たとえば NIC なら、上半分はパケット本体を触らず「下半分よ、後でリングバッファを取りに来い」とだけ伝えて抜けます。
hardirq 文脈で mutex や msleep、kmalloc(GFP_KERNEL) のような眠りうる API を呼ぶと、スケジュール不能な文脈でスケジューラを呼ぶことになり破綻します。ここで使えるのは spinlock のみで、しかも下半分や他コアと共有するなら割り込み版(spin_lock_irqsave)が要ります。詳細は /os/kernel-locking-primitives/ を参照。
下半分の三つの実装
Linux の下半分は、性質の違う三つの仕組みに分かれます。どれを選ぶかは「眠れる必要があるか」「並行して走ってよいか」で決まります。
| 観点 | softirq | tasklet | ワークキュー |
|---|---|---|---|
| 実行文脈 | ソフト割り込み文脈(非眠り) | softirq上で動く(非眠り) | カーネルスレッド(眠れる) |
| 眠れるか | 不可 | 不可 | 可(mutex・GFP_KERNEL可) |
| 並行性 | 同種が複数コアで同時実行 | 同一taskletは全体で1つだけ直列 | ワーカ次第で並行も直列も可 |
| 数の制限 | 種類は静的・少数(固定枠) | 動的にいくつでも生成可 | 動的に生成可 |
| 典型用途 | ネットワーク・タイマなど高頻度 | ドライバの軽い遅延処理 | 重い・眠る後処理 |
- softirq:最も低レベルで高速。種類は
NET_RXNET_TXTIMERなどコンパイル時に固定された少数枠だけで、ドライバが勝手に増やせません。同じ softirq が複数コアで同時に走りうるため、内部のデータは自前でロックや per-CPU 化して守る必要があります。 - tasklet:softirq の上に作られた使いやすい層。同一の tasklet はシステム全体で同時に一つしか走らないことが保証されるので、ロックを省ける場面が多い。ただし直列化ゆえスケールはしません。
- ワークキュー:唯一**プロセス文脈(カーネルスレッド)**で走るため、眠ってよい。メモリ確保で待つ、ユーザー空間へコピーする、ミューテックスを取る、といった重い後処理はここに置きます。
softirq は上半分の出口や local_bh_enable の直後に処理されますが、負荷が高く処理しきれない(おおむね一巡で捌けない)と、専用カーネルスレッド ksoftirqd がプロセス文脈で残りを引き取ります。これにより softirq が CPU を独占し続けて他タスクを餓死させる事態を防ぎます。
どこで処理するかは性能を左右する
下半分は便利ですが、割り込みが入ったコアでそのまま下半分も走るのが基本です。したがって特定の IRQ が一つのコアに集中すると、そのコアだけが過負荷になり、キャッシュ局所性も崩れます。これを制御するのが次の二つです。
IRQ affinity(割り込み親和性)
各 IRQ をどのコアに配送するかは /proc/irq/<N>/smp_affinity で固定できます。
# IRQ 24 をコア2,3だけに配送する(ビットマスク 0b1100 = 0xc)
echo c > /proc/irq/24/smp_affinity
狙いは二つ。第一に割り込み負荷の分散、第二に局所性の確保です。たとえば NIC の各受信キューを担当コアに固定すれば、そのコアのキャッシュにフロー状態が居座り、コア間でキャッシュ行を奪い合うコヒーレンシのコストを減らせます。irqbalance デーモンは自動でこれを調整しますが、低レイテンシ用途では手動固定が定石です。
softirq や tasklet は割り込みを受けたコアで走るため、affinity を変えると上半分だけでなく下半分の実行コアも一緒に動きます。逆に言えば、affinity を無秩序にするとフローが毎回別コアに飛び、キャッシュミスとロック競合を量産します。
NAPI:割り込みを止めてポーリングへ
10Gbps 級の NIC では、パケット 1 個ごとに割り込みを上げると**割り込み嵐(interrupt storm)**になり、上半分の出入りだけで CPU が溶けます。これを解く緩和(interrupt mitigation)の仕組みが NAPI です。
1. パケット到着で受信割り込みが上がる
2. 上半分はその IRQ を「無効化」し、NAPI poll をスケジュール
3. 以後は割り込みでなく softirq(NET_RX) が、リングを「ポーリング」で一括回収
4. 1回の poll で budget(既定 64 個など)まで取る
5. もう来ない(空になった)ら割り込みを再び有効化して 1 に戻る
要点は「忙しい間は割り込みを切ってまとめて取り、暇になったら割り込みに戻す」という適応的な切り替えです。高負荷時はポーリングのスループット効率を、低負荷時は割り込みの低レイテンシを得る、いいとこ取りの設計と言えます。budget は他の softirq を餓死させない上限としても働きます。
「上半分でできないこと」を問われたら、眠ること(スケジュール)と時間のかかる処理、と即答できるように。tasklet と softirq の差は「同一 tasklet は全体で直列、同一 softirq は複数コアで並行」が決め手。ワークキューだけがプロセス文脈で眠れる、も頻出です。
関連する設計判断
二段構えは、応答性とスループットを両立させるためのレイテンシ設計そのものです。上半分を短く保つことは、より高優先度タスクの横取りを早く許すことにつながり、/os/kernel-preemption/ のレイテンシ最小化と同じ方向を向いています。また NAPI の「適応的にポーリングへ切り替える」発想は、高並行サーバーが多数の接続をまとめてさばく /os/epoll-io-uring/ のイベント駆動と本質的に同じ「まとめて処理する」最適化です。
まとめ
- 上半分(hardirq):割り込み無効・非眠りの最小区間。ACK と下半分の予約だけして即抜ける。
- 下半分:softirq(並行・高速・固定枠)、tasklet(同型は直列)、ワークキュー(プロセス文脈で眠れる)の三択。
- IRQ affinity:割り込みをコアに固定し、負荷分散とキャッシュ局所性を両立。下半分の実行コアも一緒に動く。
- NAPI:高負荷時は割り込みを止めてポーリングで一括回収し、暇になったら割り込みに戻す割り込み緩和。
OS Article
割り込み処理の二段構え(上半分/下半分)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
割り込み
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
下半分はsoftirq/tasklet/ワークキューに分かれる。softirqは並行・非眠り、taskletは同型の直列化、ワークキューだけがプロセス文脈で眠れる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「割り込み / softirq」に近いか確認する。
- 強みである「上半分(hardirq)は割り込み無効で走る最小区間。デバイス確認とACK、下半分の起動予約だけを行い即座に抜ける。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。