---
title: 割込みサブシステム
url: https://doc.liz6.com/ja/linux-kernel/04-interrupts-and-clocks/01-interrupt-subsystem
locale: ja
area: linux-kernel
tags:
- linux-kernel
- interrupts-and-clocks
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: irq_desc/irq_chip/irq_domain → 割込み処理フロー (トップハーフ/ボトムハーフ) → softirq/tasklet/workqueue → スレッド化された割込み (threaded IRQ) → MSI/MSI-X → APIC/IOMMU による割込みリマップ。カーネルバージョン: 2.6 ~ 6.x'
---

# 割込みサブシステム

> カバー範囲: irq_desc/irq_chip/irq_domain → 割込み処理フロー (トップハーフ/ボトムハーフ) → softirq/tasklet/workqueue → スレッド化された割込み (threaded IRQ) → MSI/MSI-X → APIC/IOMMU による割込みリマップ
> カーネルバージョン: 2.6 ~ 6.x

## 概要

割込みサブシステムは、カーネルにおいてハードウェアと最も密接に結合している部分です。ネットワークカードがパケットを受信したり、ディスクがDMAを完了したり、タイマーが期限切れになったりすると、ハードウェアは割込みコントローラ (APIC/GIC) を介してCPUに割込み信号を送信します。カーネルの割込みサブシステムは、以下の役割を担います：割込みを正しいCPUへルーティングする、対応するデバイスドライバのハンドラへ分散する、そして時間のかかる処理をボトムハーフ (下半部) へ延期する。

現代の割込みサブシステムの中心的な抽象化は3層で構成されます：
- **irq_chip**: ハードウェア割込みコントローラ (IO-APIC, GIC など)
- **irq_domain**: ハードウェア割込み番号からLinux IRQ番号へのマッピング
- **irq_desc**: 各Linux IRQのディスクリプタ (ハンドラ、統計情報、アフィニティ)

---

## ハードウェア割込み vs Linux IRQ

```
ハードウェア割込み番号 (hwirq):
  割込みコントローラ内部の割込みラインの番号
  異なるコントローラで同じhwirqが存在し得る

Linux IRQ番号 (virq):
  カーネルが統一する仮想割込み番号
  irq_domainを介してマッピングされる: virq = irq_domain->map(hwirq)
  /proc/interrupts で表示されるのはLinux IRQ番号
```

### irq_desc

```c
// include/linux/irqdesc.h
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data         irq_data;
    unsigned int __percpu   *kstat_irqs;     // per-CPU割込みカウント (/proc/interrupts用)
    irq_flow_handler_t      handle_irq;      // 上位レベルのハンドラ (handle_fasteoi など)
    struct irqaction        *action;         // ドライバが登録したハンドラのリスト
    unsigned int            depth;           // 無効化カウンター
    unsigned int            istate;          // 内部状態
};
```

### irqaction: ドライバが登録したハンドラ

```c
// include/linux/interrupt.h
struct irqaction {
    irq_handler_t           handler;         // ドライバが提供するハンドラ
    unsigned long           flags;           // IRQF_SHARED, IRQF_ONESHOT など
    const char              *name;           // /proc/interrupts で表示される名前
    void                    *dev_id;         // デバイス識別子 (共有割込みでは一意である必要がある)
    struct irqaction        *next;           // 共有割込みの次のアクション
};
```

---

## 割込み処理フロー

### ハードウェアからカーネルへ

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="ハードウェア割込みからカーネルへ: デバイスがラインを引いてhandle_irq_desc()に入るまでの3ステップフロー">
  <defs>
    <marker id="irq-hw-arrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">ハードウェア割込みからカーネルへ: デバイスがラインを引いてhandle_irq_desc()に入るまでの3ステップフロー</text>

  <rect x="40" y="46" width="640" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="71" text-anchor="middle" font-size="12.5" font-weight="600" fill="#3730a3">① デバイスが割込みラインを引く → 割込みコントローラ(APIC / GIC)</text>

  <line x1="360" y1="86" x2="360" y2="100" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-hw-arrow)"/>

  <rect x="40" y="100" width="640" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="125" text-anchor="middle" font-size="12.5" font-weight="600" fill="#3730a3">② 割込みコントローラが割込みメッセージを送信 → CPU(APICバス / MSI経由)</text>

  <line x1="360" y1="140" x2="360" y2="154" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-hw-arrow)"/>

  <rect x="40" y="154" width="640" height="122" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="60" y="176" font-size="12.5" font-weight="700" fill="#3730a3">③ CPUが割込みに応答</text>
  <text x="76" y="198" font-size="11.5" fill="#4f46e5">・ ハードウェアがRIP / CS / RFLAGSを保存(x86: スタックへpush)</text>
  <text x="76" y="220" font-size="11.5" fill="#4f46e5">・ カーネルスタックへ切り替え(ユーザー空間から入った場合)</text>
  <text x="76" y="242" font-size="11.5" fill="#4f46e5">・ IDT内のエントリ(entry_SYSCALL_64の対応するエントリ)へジャンプ</text>

  <line x1="360" y1="276" x2="360" y2="292" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-hw-arrow)"/>

  <rect x="40" y="294" width="170" height="40" rx="6" fill="#0d9488"/>
  <text x="125" y="319" text-anchor="middle" font-size="12" font-weight="600" fill="#ffffff">common_interrupt()</text>
  <line x1="210" y1="314" x2="248" y2="314" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-hw-arrow)"/>

  <rect x="250" y="294" width="150" height="40" rx="6" fill="#0d9488"/>
  <text x="325" y="319" text-anchor="middle" font-size="12" font-weight="600" fill="#ffffff">do_IRQ()</text>
  <line x1="400" y1="314" x2="438" y2="314" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-hw-arrow)"/>

  <rect x="440" y="294" width="240" height="40" rx="6" fill="#0d9488"/>
  <text x="560" y="319" text-anchor="middle" font-size="12" font-weight="600" fill="#ffffff">handle_irq_desc()</text>

  <rect x="60" y="352" width="600" height="34" rx="7" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="373" font-size="12" fill="#115e59">handle_irq_desc()への3ステップ: コンテキストの保存 → エントリの特定 → コアへの委譲。その後、割込みのトリガタイプに応じて分流する(下図参照)。</text>
</svg>

### handle_irq_desc() パス

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="handle_irq_descは割込みのトリガタイプに応じて、generic、fasteoi、edgeの3つのフローハンドラへ分流する呼び出しツリー">
  <defs>
    <marker id="irq-tree-arrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="16.5" font-weight="700" fill="#1f2933">handle_irq_desc() パス: 割込みのトリガタイプに応じて対応するフローハンドラへ分流</text>

  <rect x="280" y="44" width="160" height="36" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="66" text-anchor="middle" font-size="12.5" font-weight="700" fill="#3730a3">handle_irq_desc(desc)</text>

  <line x1="360" y1="80" x2="135" y2="112" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-tree-arrow)"/>
  <line x1="360" y1="80" x2="360" y2="112" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-tree-arrow)"/>
  <line x1="360" y1="80" x2="585" y2="112" stroke="#475569" stroke-width="1.6" marker-end="url(#irq-tree-arrow)"/>

  <!-- 列 A: generic -->
  <rect x="40" y="112" width="190" height="32" rx="6" fill="#e2e8f0"/>
  <text x="135" y="132" text-anchor="middle" font-size="10.5" font-weight="600" fill="#334155">generic_handle_irq_desc(desc)</text>
  <line x1="135" y1="144" x2="135" y2="160" stroke="#475569" stroke-width="1.4" marker-end="url(#irq-tree-arrow)"/>
  <rect x="40" y="160" width="190" height="46" rx="6" fill="#e2e8f0"/>
  <text x="135" y="180" text-anchor="middle" font-size="11" font-weight="700" fill="#334155">desc-&gt;handle_irq(desc)</text>
  <text x="135" y="196" text-anchor="middle" font-size="10" fill="#64748b">上位フローハンドラ</text>

  <!-- 列 B: fasteoi(最も一般的、ハイライトされた青緑色) -->
  <rect x="250" y="112" width="220" height="44" rx="6" fill="#ccfbf1" stroke="#99f6e4"/>
  <text x="360" y="130" text-anchor="middle" font-size="12" font-weight="700" fill="#0f766e">handle_fasteoi(desc)</text>
  <text x="360" y="146" text-anchor="middle" font-size="10" fill="#115e59">最も一般的 ・ レベルトリガ</text>
  <line x1="360" y1="156" x2="360" y2="170" stroke="#475569" stroke-width="1.4" marker-end="url(#irq-tree-arrow)"/>
  <rect x="250" y="170" width="220" height="40" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="188" text-anchor="middle" font-size="11.5" font-weight="700" fill="#0f766e">mask_irq(desc)</text>
  <text x="360" y="203" text-anchor="middle" font-size="10" fill="#115e59">本割込みをマスク(無効化)</text>
  <line x1="360" y1="210" x2="360" y2="224" stroke="#475569" stroke-width="1.4" marker-end="url(#irq-tree-arrow)"/>
  <rect x="250" y="224" width="220" height="58" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="242" text-anchor="middle" font-size="11.5" font-weight="700" fill="#0f766e">handle_irq_event(desc)</text>
  <text x="360" y="258" text-anchor="middle" font-size="10" fill="#115e59">actionリストを走査 → action-&gt;handler()</text>
  <text x="360" y="272" text-anchor="middle" font-size="10" fill="#115e59">IRQF_WAKE_THREAD → カーネルスレッドをウェイクアップ</text>
  <line x1="360" y1="282" x2="360" y2="296" stroke="#475569" stroke-width="1.4" marker-end="url(#irq-tree-arrow)"/>
  <rect x="250" y="296" width="220" height="40" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="314" text-anchor="middle" font-size="11.5" font-weight="700" fill="#0f766e">unmask_irq(desc)</text>
  <text x="360" y="329" text-anchor="middle" font-size="10" fill="#115e59">復元</text>

  <!-- 列 C: edge -->
  <rect x="490" y="112" width="190" height="44" rx="6" fill="#e2e8f0"/>
  <text x="585" y="130" text-anchor="middle" font-size="11.5" font-weight="700" fill="#334155">handle_edge_irq(desc)</text>
  <text x="585" y="146" text-anchor="middle" font-size="10" fill="#64748b">エッジトリガ</text>
  <line x1="585" y1="156" x2="585" y2="170" stroke="#475569" stroke-width="1.4" marker-end="url(#irq-tree-arrow)"/>
  <rect x="490" y="170" width="190" height="46" rx="6" fill="#e2e8f0"/>
  <text x="585" y="190" text-anchor="middle" font-size="10.5" font-weight="600" fill="#334155">ack_irq(desc) →</text>
  <text x="585" y="205" text-anchor="middle" font-size="10.5" font-weight="600" fill="#334155">handle_irq_event() → …</text>

  <rect x="60" y="356" width="600" height="50" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="376" font-size="12" fill="#115e59">handle_fasteoiは最も一般的なパス(レベルトリガ): まず割込みをマスク → 登録された全actionを走査してハンドラを呼び出し → 復元;</text>
  <text x="76" y="394" font-size="12" fill="#115e59">もしあるactionがIRQF_WAKE_THREADを要求する場合、対応するカーネルスレッドをウェイクアップして下半部を実行します。</text>
</svg>

### 共有割込み

```
IRQF_SHARED: 複数のデバイスが同じ割込みラインを共有
  → 登録された全ハンドラが順に呼び出される
  → 各ハンドラは「自分のデバイスからの割込みかどうか」を確認する
     (デバイスレジスタを読み取り、一致しない場合は → IRQ_NONEを返す)
  → 全ハンドラがIRQ_NONEを返す → 誤割込み (spurious interrupt)
     + spuriousカウントを増加 → 100k回継続 → この割込みを無効化
```

---

## ボトムハーフ (下半部)

割込みハンドラは最も緊急の作業のみを行う (割込みの確認、デバイスのマスク、データのコピー)。時間のかかる処理 (プロトコルスタックの処理、キャッシュへの書き戻し) はボトムハーフに委ねる。

### softirq

```c
// kernel/softirq.c
// 事前に定義されたsoftirqの種類 (コンパイル時に決定):
enum {
    HI_SOFTIRQ    = 0,   // 高優先度tasklet
    TIMER_SOFTIRQ = 1,   // タイマー期限切れ
    NET_TX_SOFTIRQ = 2,  // ネットワーク送信
    NET_RX_SOFTIRQ = 3,  // ネットワーク受信 (NAPI)
    BLOCK_SOFTIRQ = 4,   // ブロック層
    TASKLET_SOFTIRQ = 6, // 通常のtasklet
    RCU_SOFTIRQ   = 9,   // RRCUコールバック
    NR_SOFTIRQS
};

// softirqは割込みコンテキストで実行される (ただし割込み有効)
// irq_exit() によってチェックがトリガされる:
//   if (in_interrupt()) return;  // ネストしたハードウェア割込み中でない場合
//   do_softirq() → __do_softirq() → softirqビットマップを走査

// ksoftirqd: per-CPUカーネルスレッド
//   softirqの処理が多すぎる場合 (MAX_SOFTIRQ_RESTART) → ksoftirqdへ委譲
//   → CPUをユーザープロセスへ譲渡
```

### tasklet (廃止済み、threaded IRQに置き換え)

```c
// kernel/softirq.c
// taskletはsoftirq (HI_SOFTIRQ または TASKLET_SOFTIRQ) をベースにする
// 同種のtaskletは同時に実行されない (同期を簡素化)
// ⚠️ 2022以降のカーネルでは廃止: 新コードではthreaded IRQを使用

struct tasklet_struct {
    void (*func)(unsigned long);
    unsigned long data;
};
```

### Threaded IRQ: 現在推奨される方法

```c
// kernel/irq/manage.c
// 各割込みごとに独立したカーネルスレッドを持つことができる
// ハンドラが IRQ_WAKE_THREAD を返すと、カーネルは threaded_fn の実行をスケジューリングする

request_threaded_irq(irq, handler, thread_fn, flags, name, dev);
//   handler: ハードウェア割込みコンテキストで実行 (最小限の作業のみ)
//   thread_fn: 独立したカーネルスレッド内で実行 (スリープ可能、mutex取得可能)
//
// 利点:
//   - 割込み無効時間の削減
//   - 下半部でスリープ可能 (mutex取得、IO実行)
//   - リアルタイムスケジューリングポリシーで管理可能
```

### Workqueue: より重い下半部

```c
// kernel/workqueue.c
// workqueueはプロセスコンテキスト (カーネルスレッド kworker) で実行される
// 適している用途: スリープが必要、長時間実行される作業

// tasklet/softirqとの主な違い:
//   workqueue: プロセスコンテキスト、スリープ可能
//   tasklet/softirq: 割込みコンテキスト、スリープ不可

schedule_work(&work);          // システムworkqueue
queue_work(my_wq, &work);     // 専用workqueue
```

---

## MSI / MSI-X

```c
// kernel/irq/msi.c
// メッセージシグナルド割込み (Message Signaled Interrupts)
// デバイスが直接メモリアドレスへ書き込み → 割込みコントローラ (割込みラインを経由しない)
// 利点:
//   - 共有不要 (各MSIに一意のアドレスがある)
//   - より高性能 (PCIeのposted write vs INTxの低速なレベル信号)
//   - MSI-Xではデバイスが複数の割込みベクタを持てる (例: NVMeはキューごとに1つ)

// PCIデバイスでMSIを有効化:
pci_alloc_irq_vectors(pdev, 1, 16, PCI_IRQ_MSI);
  → デバイスが16個のMSIベクタを割り当て
  → Linux IRQ番号を返す (各ベクタごとに1つ)
```

---

## 割込みアフィニティと負荷分散

```bash
# 割込みの分布を確認
cat /proc/interrupts

# 割込みアフィニティの設定 (どのCPUがどの割込みを処理するか)
echo 2 > /proc/irq/<N>/smp_affinity  # CPU 1にバインド

# irqbalanceデーモンが自動でバランスを取る
#   割込みカウントとCPU負荷に基づいて判断
#   特定の割込み (例: NVMe) はNUMAローカルCPUに直接バインドされることがある
```

---

## 参考と拡張

- **カーネルドキュメント**: `Documentation/core-api/irq/`, `Documentation/PCI/msi-howto.rst`
- **LWN**: "The design of the interrupt subsystem", "Threaded interrupt handlers"
- **ソースコード**:
  - `kernel/irq/` — 割込みコア (handle, manage, chip, domain, msi)
  - `kernel/softirq.c` — softirqとtasklet
  - `kernel/workqueue.c` — workqueue
  - `arch/x86/kernel/irq.c` — x86割込みエントリ
  - `arch/x86/kernel/apic/` — APICドライバ

---

*キーワード: irq_desc, irq_chip, irq_domain, softirq, tasklet, threaded IRQ, workqueue, MSI/MSI-X, interrupt affinity*
