割り込みと例外処理の原理 ─ ベクタ・優先度・MSI-X
なぜ高速NICでも割り込みでCPUが溺れないのか。ベクタとMSI-X、APIC/GICの配送・優先度・モデレーションの内部を押さえれば、レイテンシとスループットの綱引きの正体が腑に落ちます。
- 1.割り込みベクタは割り込み番号からハンドラへ飛ぶための表(IDT/ベクタテーブル)で、CPUは現在命令の境界で実行を中断しベクタ番号からディスパッチする。
- 2.例外は命令実行から同期的に発生し原因命令を特定できるのに対し、割り込みはデバイス都合で非同期に到来し直近の命令境界で受理される。
- 3.ピン共有のレガシINTxからMSI/MSI-X(メモリ書き込みで割り込む方式)へ進化し、APIC/GICが宛先CPU・優先度・アフィニティを決定、NAPIやモデレーションで高負荷時はポーリングへ切り替える。
なぜ割り込みが要るのか ─ ポーリングの限界
CPUがデバイスの状態レジスタを延々と読み続けて「データ来たか?」と問い合わせる方式(ポーリング)は、デバイスが暇なときCPU時間を無駄に食い潰します。逆にデバイス側から「準備できた」とCPUへ知らせられれば、CPUはその間まったく別の処理を回せます。これが**割り込み(interrupt)**です。
割り込みが届くと、CPUは現在実行中の命令を区切りのよい境界まで進めてから実行を中断し、レジスタ状態を退避して割り込みハンドラへ制御を移します。ハンドラが処理を終えると退避した状態を復元し、中断した地点から何事もなかったように再開します。鍵は、CPUが任意の場所で割り込まれるのではなく、命令境界という安全な点でのみ受理することです。これにより、アウトオブオーダ実行で内部が乱雑に動いていても、アーキテクチャ状態としては一貫した断面で中断・再開できます。
同期例外と非同期割り込み ─ 似て非なる2つ
CPUの正常な命令の流れを断ち切る事象には2種類あります。両者はハードウェア的に同じ「ベクタへ飛ぶ」機構を共有しますが、発生源とタイミングがまったく異なります。
| 観点 | 例外(同期) | 割り込み(非同期) |
|---|---|---|
| 発生源 | 実行中の命令そのもの | 外部デバイス・タイマ・他CPU |
| タイミング | 特定命令で必ず発生し再現する | いつ来るか予測できない |
| 原因命令 | 特定できる(その命令) | 直近の命令境界で受理するだけ |
| 代表例 | ページフォルト・ゼロ除算・無効命令 | NIC受信完了・タイマ・IPI |
| 復帰後 | 原因命令を再実行 or 後続へ | 中断点から続行 |
例外はさらに3つに分かれます。フォールト(fault)は原因命令の前に報告され、ハンドラが状況を是正したら同じ命令を再実行します。ページフォルトが典型で、欠落ページを割り当ててから再実行すれば成功します。トラップ(trap)は原因命令の後に報告され、int3(ブレークポイント)やシステムコール命令のように次の命令へ進みます。**アボート(abort)**は深刻なハードウェアエラーで、原因命令を特定できず再開も保証されません。
例外が原因命令と結びつくためには、その命令より前の命令の結果がすべて確定し、後続命令の結果がまだ可視化されていない断面が必要です。これを**正確な例外(precise exception)**と呼びます。投機実行やアウトオブオーダ実行をしてもこの性質を守るために、現代CPUはリオーダバッファでコミットを順序付けし、例外は退役(リタイア)時点で確定させます。割り込みは原因命令を持たないため、最も都合のよい命令境界に「相乗り」させて受理されます。
ベクタとディスパッチ ─ 番号からハンドラへ
割り込みや例外が起きたとき、CPUは「どのハンドラへ飛ぶか」をベクタ番号で決めます。x86では**IDT(Interrupt Descriptor Table)**という256エントリの表があり、ベクタ番号を添字にハンドラのアドレス・特権レベル・ゲート種別を引きます。0〜31番は例外用に固定(0=ゼロ除算、14=ページフォルト等)、32番以降がデバイス割り込みに使えます。
割り込み受理の流れ(x86の例)
デバイス -> APIC -> CPUへベクタ番号 v を通知
|
v
CPU: 現在命令を境界で停止 -> 現状態を退避
|
v
IDT[v] を引く -> ハンドラ・特権・スタック切替を取得
|
v
特権遷移(ユーザ -> カーネル) -> ハンドラ実行 -> iret で復帰
Armでは番号ごとの巨大な表ではなく、例外ベクタテーブルの少数の固定オフセット(同期例外・IRQ・FIQ・SError)へ分岐し、ソフトウェアが原因レジスタ(ESR)を読んで詳細を判別する設計です。いずれも本質は「ハードウェアが原因をエンコードし、決められた入口へ制御を移す」ことです。
ピン割り込みからMSI/MSI-Xへ ─ 線を捨てる
初期のPCIは物理的な割り込みピン(INTx)を使いました。デバイスがピンの電圧を変化させ(レベルトリガ)、その線を共有する複数デバイスが同じ線をワイヤードORで叩きます。問題は深刻でした。線が共有されるため、割り込みが来るとどのデバイスか分からず全員に問い合わせが要る。線の本数が足りずベクタが枯渇する。PCIeのようにポイントツーポイント化した世界に物理ピンは馴染まない。
これを解いたのがMSI(Message Signaled Interrupts)です。発想の転換は「割り込みを専用の線ではなく特定アドレスへのメモリ書き込みで表す」こと。デバイスはあらかじめ割り当てられたアドレスへ、あらかじめ決めたデータ(ベクタ番号を含む)を書き込むだけで割り込みになります。これは通常のDMAと同じメモリ書き込みトランザクションとしてバスを流れ、割り込みコントローラがそれを受けてCPUへ配送します。
| 観点 | INTx(ピン共有) | MSI | MSI-X |
|---|---|---|---|
| 伝達手段 | 専用ピンの電圧 | メモリ書き込み1種 | メモリ書き込み(複数エントリ) |
| ベクタ数 | 実質4本を共有 | 1〜32(連続) | 最大2048(個別設定) |
| 共有問題 | あり(誰の割り込みか不明) | なし(デバイス固有) | なし |
| 宛先CPU | 固定的 | エントリ単位で指定 | エントリ単位で個別指定 |
| キュー対応 | 不向き | 限定的 | キューごとに別ベクタ(理想的) |
MSI-XはMSIを拡張し、デバイスが最大2048個の割り込みを持て、各エントリの宛先アドレス・データ・マスクを個別に設定できます。これがマルチキューNICやNVMeで決定的に効きます。受信キューごと・CPUコアごとに別ベクタを割り当てれば、各コアが自分のキューの割り込みだけを処理し、ロック競合やキャッシュ移動を避けられるからです。
MSI/MSI-Xが「メモリ書き込み」で実装される事実は、保護の観点で重大です。悪性デバイスやバグったファームウェアが任意アドレスへ任意データを書ければ、偽装した割り込みで他CPUへ不正なベクタを注入できてしまいます。これを防ぐのがIOMMUの割り込みリマッピングで、デバイスが上げてよい割り込みエントリを検証・変換し、なりすましを遮断します。割り込みの安全性とDMA保護が同じ土台に乗っているわけです。
APIC/GIC ─ 配送・優先度・アフィニティ
デバイスが上げた割り込みを「どのCPUへ・どの優先度で・いつ」届けるかを決めるのが割り込みコントローラです。x86ではAPIC(各コアのLocal APICと、I/O APIC/割り込みリマッピングユニット)、Armでは**GIC(Generic Interrupt Controller)**が担います。
配送で要となるのが3つの概念です。
- 優先度: ベクタには優先度があり、高優先度の割り込みは低優先度のハンドラ実行中でもプリエンプト(横入り)できます。逆に処理中はそれ以下の優先度をマスクし、取りこぼしと無限の入れ子を防ぎます。Local APICのTPR(Task Priority Register)やGICのプライオリティマスクがこの閾値を持ちます。
- アフィニティ: 各割り込みをどのCPUへ送るかの指定です。特定コアへ固定(physical)すれば、そのコアのキャッシュに関連データが温まったまま処理でき、キャッシュの局所性を活かせます。コア集合へ分散(logical/lowest-priority)も選べます。
- EOI(End Of Interrupt): ハンドラが処理を終えたらコントローラへ完了を通知し、同種割り込みの再受理を許可します。これを怠ると割り込みが二度と来ない、あるいは嵐のように再発します。
割り込み配送の全体像
デバイス --MSI書き込み--> [割り込みリマッピング/IO-APIC or GIC Distributor]
| 宛先CPU・ベクタ・優先度を決定
v
[Local APIC / GIC Redistributor] (各コア)
| 優先度がTPRを超えればコアへ割り込み
v
CPUコア -> ハンドラ -> EOI
CPU間で直接割り込みを送る**IPI(Inter-Processor Interrupt)**も同じ機構を使います。TLBシュートダウン(他コアのTLBを無効化)やスケジューラの再分配は、あるコアが他コアへIPIを撃って実現されます。
割り込みモデレーションとNAPI ─ 多すぎる割り込みを抑える
10GbE/100GbEでは毎秒数百万パケットが到来し得ます。1パケット1割り込みでは、ハンドラへの出入り(コンテキスト退避・復元・キャッシュ汚染)だけでCPUが飽和し、割り込みライブロック(割り込み処理に追われ本来の仕事が一切進まない状態)に陥ります。
対策は2方向です。第一に割り込みモデレーション(coalescing)で、デバイスが「Nパケットたまる」または「Tマイクロ秒経過」まで割り込みを保留し、まとめて1回上げます。レイテンシとCPU負荷のトレードオフをこの閾値で調整します。第二にNAPI(Linuxの新APIモデル)で、最初の割り込みを受けたら以降の割り込みを一時的にマスクし、ポーリングへ切り替えてリングのパケットをまとめて吸い上げ、流入が途切れたら割り込みへ戻します。
| 状況 | 純割り込み | NAPI(割り込み+ポーリング) |
|---|---|---|
| 低トラフィック | 即応・低レイテンシで最適 | 割り込みで起動(同等) |
| 高トラフィック | 割り込み嵐でライブロック | マスクしてポーリング吸い上げ |
| CPU効率 | 出入りのオーバーヘッド大 | 1回の起動で多数を処理 |
| 切替の主役 | なし | 負荷に応じ割り込み⇄ポーリングを往復 |
要は、低負荷では割り込みの即応性を、高負荷ではポーリングのスループットをいいとこ取りする適応制御です。同じ思想は、割り込みを上半分(最小限の受理)と下半分(softirq/tasklet/threaded IRQ)に分割し、重い処理をハンドラの外へ追い出す設計にも現れます。ハンドラ内で長居しないことが、システム全体の応答性を守る鉄則です。
「例外は同期で原因命令を特定でき(フォールト=再実行/トラップ=後続へ/アボート=致命)、割り込みは非同期で命令境界に相乗りして受理」「ベクタ番号からIDT/ベクタテーブルでハンドラへディスパッチ」「INTxはピン共有で誰の割り込みか不明、MSI/MSI-Xはメモリ書き込みで表しキューごとに別ベクタを割り当て可能」「APIC/GICが宛先・優先度・アフィニティを決め、EOIで完了通知、IPIでコア間通知」「割り込みモデレーションとNAPIで高負荷時はポーリングへ切り替えライブロックを回避」が核心です。「正確な例外」「割り込みライブロック」は頻出語です。
まとめ
- 割り込みは命令境界という安全点で受理され、状態退避→ベクタ番号でハンドラへディスパッチ→復帰、という流れで非同期事象を処理する。
- 例外は同期で原因命令を特定でき(フォールト/トラップ/アボート)、正確な例外として退役時点で確定する。割り込みは非同期で原因命令を持たず命令境界に相乗りする。
- レガシなINTxピン共有は誰の割り込みか不明という欠点があり、MSI/MSI-Xは割り込みをメモリ書き込みで表すことでこれを解消、MSI-Xは最大2048ベクタを個別設定できキューごと・コアごとの割り当てを可能にする。
- APIC/GICが宛先CPU・優先度・アフィニティを決定し、EOIで完了を通知、IPIでコア間割り込みを送る。MSIの安全性は割り込みリマッピングが支える。
- 高負荷ではモデレーションとNAPIで割り込みをポーリングへ切り替え、割り込みライブロックを避ける。低負荷の即応性と高負荷のスループットを適応的に両立する。
割り込みは、DMAで運ばれたデータの「完了通知」であり、PCIe上をメモリ書き込みとして流れ、キャッシュ局所性を意識して配送先が選ばれます。データの流れと通知の流れを一体で押さえると、I/O経路の全体像が見通せます。
CPU/メモリ/ディスク Article
割り込みと例外処理の原理 ─ ベクタ・優先度・MSI-Xを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
割り込み
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 6
導入後に効く点
例外は命令実行から同期的に発生し原因命令を特定できるのに対し、割り込みはデバイス都合で非同期に到来し直近の命令境界で受理される。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 6
判断チェックリスト
- 自社の用途が「割り込み / 例外処理」に近いか確認する。
- 強みである「割り込みベクタは割り込み番号からハンドラへ飛ぶための表(IDT/ベクタテーブル)で、CPUは現在命令の境界で実行を中断しベクタ番号からディスパッチする。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。