デバイスは絶えず割り込みを通知しますが、マスクしても DMA がメモリへの書き込みを続けることがあります。ここでは、通知、デバイスの状態、バックグラウンド処理をそれぞれ追跡する必要があります。
参照目標 読書位置
番号付けとディスパッチ 第 1~3 節
実行コンテキストと遅延処理 第 4~5 節
終了と有効なアフィニティ 第 6~7 節
割り込みは、デバイスが CPU にイベントを通知するためのメカニズムです。1 つの通知が複数の完了したリクエストに対応することもあり、1 つのリクエストが複数の通知に関与することもあります。したがって、ドライバは最終的にデバイスの状態、キュー、または完了記録を確認すべきであり、割り込みカウントをそのままビジネスの完了数として扱うべきではありません。
1. 3つの番号付けと4つのオブジェクト
概念 責任 混同しやすいオブジェクト
hwirq 特定の割り込みドメイン内のハードウェア番号 システム全体で一意な番号ではない
Linux IRQ カーネルが管理する IRQ の論理識別子 x86 CPU ベクターに等しくない
CPU ベクター / アーキテクチャ割り込み ID アーキテクチャのエントリポイントでの識別とルーティング APIC、GIC などのメカニズムに応じて具体的な意味が異なる
irq_domain ハードウェアドメインと Linux IRQ の関係をマッピング・管理 階層化可能であり、単なる固定オフセットの追加ではない
irq_chip マスク、ack、EOI、アフィニティなどのコントローラ操作 ドライバがデバイスのビジネスを処理するハンドラに等しくない
irq_desc IRQ の状態、制御フロー、統計、action など 1 つの IRQ が複数の共有 action を関連付けることができる
irqaction ドライバが登録した処理関数、引数、スレッド情報 必ずしも1つのエントリが1本の排他的な物理線を対応するわけではない
1つの GPIO コントローラが別の割り込みコントローラの下部として機能することができ、割り込みドメインは階層を構成する可能性があります。PCI MSI も、MSI ドメイン、リマップ、CPU ルーティングなどの多層管理を経る場合があります。ドライバは通常、サブシステムが提供する Linux IRQ を取得して処理関数を登録しますが、ハードウェア番号を推測すべきではありません。irq_domain ドキュメント を参照してください。
2. イベントのディスパッチと確認
ハードウェア通知からドライバ処理へ:各層には異なる責任がある
デバイスがイベントを生成
電圧レベル、エッジ、または MSI メッセージ;必ずしも各イベントが1回の通知に対応するわけではない
1
割り込みコントローラとアーキテクチャエントリ
CPU へルーティングされ、対応する例外/割り込み処理パスに入る
2
Linux IRQ 記述と flow handler
マスク、ack、EOI などのコントローラ操作を調整
3
ドライバのプライマリハンドラ
ソースを識別し、必要な状態を処理するか、IRQ_WAKE_THREAD を返す
4
後続処理とリソース回収
IRQ スレッド、NAPI、通常 workqueue などは要件に応じて選択
5
EOI、デバイスの状態クリア、IRQ マスク、DMA 停止は同じ動作ではありません。
アーキテクチャエントリは必要なコンテキストを保存し、カーネル処理環境を構築します。スタックの切り替えとレジスタ保存の具体的なルールは、アーキテクチャとエントリタイプによって異なります。システムコールエントリ、x86 外部割り込み、ARM 例外処理を同じ固定関数チェーンに書くことはできません。
generic IRQ レイヤは、その IRQ 用にインストールされた flow handler を呼び出し、次に irq_chip とドライバ action を呼び出します。generic_handle_irq_desc() は一般的なディスパッチ関連インターフェースであり、handle_edge_irq()、handle_fasteoi_irq() と並べて3つのトリガータイプとして描くべきではありません。
マスク、ack、EOI とデバイス状態
操作 処理される状態の層
マスク 通知を継続して配信するかどうか
ack コントローラの認識/応答
EOI コントローラに対して関連するサービスフェーズの終了を通知
デバイス状態のクリア、完了キューの消費 ドライバがデバイスプロトコルに従って真のイベントソースを処理
コントローラの確認とデバイスのビジネス完了は別々に発生するため、一方を他方に置き換えることはできません。
レベルソースがまだ有効な場合、マスク解除後に再度通知される可能性があります。エッジソースでは、通知がラッチされているか、処理中に新しいエッジが発生するかどうかを考慮する必要があります。異なる flow handler はこれらの制約に基づいて操作を構成し、fasteoi も一律に「まずマスクし、処理し、最後にアンマスクする」として記述してはいけません。その核心は対応する EOI プロトコルであり、状態や oneshot などの条件に基づいてマスクを処理します。
コントローラへの EOI の送信は、デバイスの割り込み原因を自動的にクリアしません。IRQ のマスクも DMA を自動的に停止しません。この2層を逆にすると、割り込み嵐や解放後の DMA 書き込みの原因となる可能性があります。generic IRQ を参照してください。
デバイスソース、コントローラのサービス状態、およびマスクビットを別々に操作します。EOI のみを送信してレベルソースをクリアしない場合、再配信が発生する可能性があります。人為的に新しいデバイスイベントを作成する必要はありません。
図を準備しています 再読み込み
EOI でサービスは終了するが、デバイス原因はクリアされない デバイス原因、コントローラの in-service 状態、およびマスク状態は異なるオブジェクトです。EOI はデバイス原因をクリアせず、マスクは DMA を停止したりビジネスキューをクリアしたりしません。
{"id":"irq-source-and-eoi","title":"EOI でサービスは終了するが、デバイス原因はクリアされない","summary":"デバイス原因、コントローラの in-service 状態、およびマスク状態は異なるオブジェクトです。EOI はデバイス原因をクリアせず、マスクは DMA を停止したりビジネスキューをクリアしたりしません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"event\" data-i18n=\"event\"></button><button id=\"clear\" data-i18n=\"clear\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"masked\"><span data-i18n=\"masked\"></span></label><div class=\"track-row\"><span data-i18n=\"source\"></span><div id=\"source\" class=\"track\"></div></div><div class=\"wire\" id=\"wire\"></div><div class=\"track-row\"><span data-i18n=\"controller\"></span><div id=\"controller\" class=\"track\"></div></div><div class=\"actions\"><button id=\"deliver\" data-i18n=\"deliver\"></button><button id=\"eoi\" data-i18n=\"eoi\"></button></div><div class=\"metrics\"><div><span data-i18n=\"count\"></span><strong id=\"count\"></strong></div><div><span data-i18n=\"eligible\"></span><strong id=\"eligible\"></strong></div></div><div class=\"formula\" id=\"condition\"></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.wire{height:40px;width:2px;background:var(--rule);margin:auto;position:relative}.wire:after{content:\"▼\";position:absolute;bottom:-5px;left:-6px;color:var(--rule);font-size:14px}.wire.on,.wire.on:after{background:var(--accent);color:var(--accent)}.formula{min-height:4.8em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let level=false,service=false,count=0;function draw(){const mask=$('#masked').checked,ok=level&&!mask&&!service;cells('source',[{text:'level = '+Number(level),cls:level?'gold':'empty'}]);cells('controller',[{text:'in_service = '+Number(service),cls:service?'on':'empty'}]);$('#wire').classList.toggle('on',ok);$('#deliver').disabled=!ok;$('#eoi').disabled=!service;$('#clear').disabled=!level;set('count',count);set('eligible',t(ok?'yes':'no'));set('condition',Number(level)+' ∧ ¬'+Number(mask)+' ∧ ¬'+Number(service)+' = '+Number(ok));set('explanation',t('note'));}$('#event').onclick=()=>{level=true;draw();};$('#clear').onclick=()=>{level=false;draw();};$('#deliver').onclick=()=>{if(level&&!service&&!$('#masked').checked){service=true;count++;}draw();};$('#eoi').onclick=()=>{service=false;draw();};$('#masked').onchange=draw;$('#reset').onclick=()=>{level=service=false;count=0;$('#masked').checked=false;draw();};draw();","audio":false,"strings":{"clear":"デバイス原因をクリア","controller":"コントローラのサービス状態","count":"累積配信回数","deliver":"CPU へ配信","eligible":"現在配信可能","eoi":"EOI を送信","event":"デバイス原因をセット","heading":"EOI でサービスは終了するが、デバイス原因はクリアされない","intro":"デバイスのレベルをトリガーし、配信を試み、その後デバイスの状態をクリアするか EOI を送信します。デバイス原因を保持したまま、なぜ新しいイベントがなくても再配信が可能になるかを確認します。","masked":"この IRQ をマスク","no":"いいえ","note":"条件は level ∧ ¬masked ∧ ¬in_service です。EOI 後に level がまだ 1 の場合、配信を押すとカウントが増加します。デバイス原因をクリアすると、再通知のソースが削除されます。","reset":"リセット","route":"配信条件","scope":"共有線や並行処理がない抽象的なレベルトリガーソース。配信はボタンで明示的にトリガーされ、実際のハードウェアでは自動的に進行します。重複イベントは同じ原因ビットを維持し、イベント数でカウントしません。アーキテクチャの ack/優先度/oneshot プロトコルは省略されており、MSI やすべての IRQ flow の一般的な順序として扱われるものではありません。","scopeLabel":"モデルの範囲と規約","source":"デバイス原因 / レベル","yes":"はい"}}
3. 共有 IRQ と戻り値
共有ライン上のハンドラは、イベントが自分自身のものかどうかを確認する必要があります。dev_id は対応する登録項目とプライベートコンテキストを識別し、解除時にも一致させる必要があります。ソースを識別できない状態で無条件に割り込み処理の成功を宣言してはいけません。
戻り値 意味
IRQ_NONE このハンドラは、自分自身のイベントを処理したことを確認していない
IRQ_HANDLED 自分自身の割り込みイベントを処理した
IRQ_WAKE_THREAD 登録された thread_fn の実行を要求
IRQ_WAKE_THREAD は戻り値であり、IRQF_WAKE_THREAD という名前の登録フラグではありません。継続的に未処理の IRQ は、カーネルの例外統計と処理をトリガーする可能性がありますが、閾値とパスは実装に依存するため、「ちょうど10万回で必ず終了する」と硬く書き込むことはできません。
MSI は通常、従来の共有ラインのソース識別問題を回避しますが、キューのマージ、割り込みの集約、および完了処理は依然として存在します。pci_alloc_irq_vectors() は実際に割り当てられた数を返し、その後 pci_irq_vector() を使用して各 IRQ を取得します。これは申請上限を固定して返すわけでも、最初の IRQ を直接返すわけでもありません。詳細はPCIe と USB ドライバモデル を参照してください。
4. プライマリハンドラとスレッド化 IRQ
request_threaded_irq() は、プライマリハンドラと thread_fn を登録できます。プライマリハンドラは、実際のハード割り込みパス内で短く、スリープしてはなりません。後続のスレッド処理が必要な場合は、IRQ_WAKE_THREAD を返します。
IRQF_ONESHOT を使用すると、コアは規約に従ってスレッド処理中に IRQ マスクを維持し、関連する処理が完了する前に再配信されるのを防ぎます。これは、1つのハードウェアイベントがちょうど1回のスレッドウェイクアップに対応することを保証するものではなく、スレッドは通常、デバイスの状態またはキューを排空する必要があります。
制約を満たす場合、デフォルトのプライマリハンドラを thread_fn と組み合わせて使用できます。共有ラインでは、特にソースの正しい識別と調整が必要です。PREEMPT_RT の強制スレッド化と例外は、実際のコンテキストを変更するため、プロセス名や API 名に基づいてすべてのハンドラがスリープ可能であると推測してはいけません。
IRQ スレッドは適切なスリープ操作を実行できますが、raw spinlock を保持している場合や、自分自身によってブロックされたパスに依存している場合に待機することはできません。スレッド化はハード割り込みセグメントの長さを短縮しますが、ロック順序やリソースの寿命を自動的に修復するものではありません。
5. softirq、tasklet、および workqueue
メカニズム 実行特徴 設計上の境界
softirq 固定カテゴリ、CPU ごとに保留および実行、ksoftirqd によって処理される可能性あり 通常の softirq コールバックは随意にスリープできず、CPU を跨ぐ並行実行が可能
tasklet softirq ベースの古い遅延メカニズム 同一の tasklet インスタンスは同時に実行されないが、すべての同種 tasklet が排他的ではない
通常 workqueue ワーカープールがプロセスコンテキストを提供 ルールに従ってスリープ可能であり、専用キューが必ずしも固定の排他スレッドを意味するわけではない
WQ_BH workqueue このインターフェースをサポートするカーネル内で BH/softirq コンテキストを使用 通常 workqueue のスリープ仮定をそのまま適用できない
IRQ スレッド IRQ action と関連付けられたカーネルスレッド デバイスプロトコルおよび oneshot/共有などの制約を処理
ksoftirqd の存在が、softirq コールバックを mutex を自由に使用できる通常のワーク関数にするものではありません。tasklet はまだいくつかのカーネルパスに存在しますが、新しい実装ではより適切なスレッド化、NAPI、または workqueue メカニズムを選択すべきであり、単に「カーネルから削除された」と主張してはいけません。
10回の通知で、なぜ work が10回実行されないのか
同一の work がすでに pending の場合、queue_work() は false を返す可能性があります。これは、「この work を実行する必要がある」というスケジューリング状態を保存するものであり、10個の独立したビジネスイベントではありません。
10回の IRQ がすべて同じ work をスケジュールする場合、イベントの数は、他のキュー/カウントによって保存するか、work がデバイスの照会可能な完了状態を排空することによって維持されなければなりません。これにより、スケジュール通知がマージされても、ビジネスの事実は1つだけ残ることはありません。
複数の work の並行実行、順序、およびメモリ回収コンテキストは、queue flags、pool、および設定によって決定されます。厳密な順序が必要な場合は、対応する ordered インターフェースを使用します。workqueue ドキュメント を参照してください。
6. 終了パス:新しいアクティビティを先に阻止し、古いアクティビティを待機する
終了時には、依存関係に従って段階的にアクティビティソースをシャットダウンします。
フェーズ 確立すべき条件
新しい送信の停止 新しいビジネスを生成しない
デバイスソースと DMA の制御 ハードウェアが危険なアクセスを生成しない
IRQ の解除/同期 対応するハンドラがリソースを使用しない
バックグラウンドワークのキャンセルまたは待機 以前にキューに入れられたコールバックがコンテキストにアクセスしない
リソースの解放 前述の使用者が退出した
実際の順序はドライバの依存関係に影響されます。特に、あるコールバックが停止したリソースを再起動しないように防止することが重要です。
disable_irq_nosync() は、実行中のハンドラを待機しません。disable_irq() や synchronize_irq() などの待機インターフェースは、ハンドラが必要とするロックを保持している状態で呼び出すことはできません。synchronize_irq() 自体が、常に後続の新しい IRQ を禁止する措置ではありません。
free_irq() は待機して対応する処理登録を取り消しますが、デバイスハードウェアを自動的に停止するわけでも、以前にキューに入れられた work を自動的にキャンセルするわけでもありません。共有 IRQ の場合、IRQ 全体を無効化して他のデバイスに影響を与えることはできません。まず、自分のデバイスソースを正しく制御することが優先されます。
7. アフィニティと読み取り専用観察
/proc/irq/N/smp_affinity_list は要求された CPU コレクションを表し、effective_affinity_list はサポートされている場合、有効なルーティングを表示します。managed IRQ、コントローラの能力、CPU hotplug、irqbalance、およびドライバポリシーが実際の結果に影響を与える可能性があります。
from pathlib import Path
for irq in sorted ( Path ( ' /proc/irq' ) . glob ( ' [0-9]*' ) , key = lambda p : int ( p . name ) ) [ : 12 ] :
values = { }
for field in ( ' smp_affinity_list' , ' effective_affinity_list' ) :
try :
values [ field ] = ( irq / field ) . read_text ( ) . strip ( )
except OSError :
pass
print ( irq . name , values )
/proc/interrupts には、特定のアーキテクチャの特殊イベント行も含まれており、各行がすべて通常の再割り当て可能なアフィニティを持つデバイス IRQ であるわけではありません。カウントの増加が1つの CPU に集中していることが必ずしもエラーであるとは限りません。キューマッピング、NUMA、NAPI、およびビジネス負荷と組み合わせて判断する必要があります。