---
title: 中断子系统
url: https://doc.liz6.com/linux-kernel/04-interrupts-and-clocks/01-interrupt-subsystem
locale: zh
area: linux-kernel
tags:
- linux-kernel
- 中断与时钟
date: 2026-06-30
modified: 2026-07-11
description: '覆盖: irq_desc/irq_chip/irq_domain → 中断处理流程 (top/bottom half) → softirq/tasklet/workqueue → threaded IRQ → MSI/MSI-X → APIC/IOMMU 中断重映射 内核版本: 2.6 ~ 6.x'
---

# 中断子系统

> 覆盖: irq_desc/irq_chip/irq_domain → 中断处理流程 (top/bottom half) → softirq/tasklet/workqueue → threaded IRQ → MSI/MSI-X → APIC/IOMMU 中断重映射
> 内核版本: 2.6 ~ 6.x

## 概述

中断子系统是内核中与硬件最紧密耦合的部分。当网卡收到包、磁盘完成 DMA、定时器到期时，硬件通过中断控制器 (APIC/GIC) 向 CPU 发送中断信号。内核的中断子系统负责：路由中断到正确的 CPU、分发到对应的设备驱动 handler、以及 defer 耗时工作到下半部 (bottom half)。

现代中断子系统的核心抽象有三层：
- **irq_chip**: 硬件中断控制器 (IO-APIC, GIC 等)
- **irq_domain**: 硬件中断号 → Linux IRQ 号的映射
- **irq_desc**: 每个 Linux IRQ 的描述符 (handler, 统计, 亲和性)

---

## 硬件中断 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;      // 高层 handler (handle_fasteoi, ...)
    struct irqaction        *action;         // 驱动注册的 handler 链表
    unsigned int            depth;           // disable 计数器
    unsigned int            istate;          // 内部状态
};
```

### irqaction: 驱动注册的 handler

```c
// include/linux/interrupt.h
struct irqaction {
    irq_handler_t           handler;         // 驱动提供的 handler
    unsigned long           flags;           // IRQF_SHARED, IRQF_ONESHOT, ...
    const char              *name;           // /proc/interrupts 显示的名字
    void                    *dev_id;         // 设备标识 (共享中断时必须唯一)
    struct irqaction        *next;           // 共享中断的下一个 action
};
```

---

## 中断处理流程

### 硬件到内核

<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() 的三步流程">
  <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()</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 bus / 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 to stack)</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():保存现场 → 找到入口 → 交给核心分发,再往下按中断触发类型分流(见下图)。</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 三种 flow handler 的调用树">
  <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() 路径:按中断触发类型分流到对应 flow handler</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">高层 flow handler</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">最常见 · level-triggered</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">edge-triggered</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 是最常见路径(level-triggered):先屏蔽中断 → 遍历所有注册 action 调用 handler → 恢复;</text>
  <text x="76" y="394" font-size="12" fill="#115e59">若某 action 要求 IRQF_WAKE_THREAD,则唤醒对应内核线程执行下半部。</text>
</svg>

### 共享中断

```
IRQF_SHARED: 多个设备共享同一条中断线
  → 所有注册的 handler 依次调用
  → 每个 handler 检查"是不是我的设备产生的中断"
     (读设备寄存器，如果不匹配 → 返回 IRQ_NONE)
  → 所有 handler 都返回 IRQ_NONE → 误触发 (spurious interrupt)
     + 记 spurious 计数 → 持续 100k 次 → 禁用此中断
```

---

## 下半部 (Bottom Half)

中断 handler 只做最紧急的事 (确认中断、屏蔽设备、复制数据)。耗时操作 (处理协议栈、回写缓存) 交给下半部。

### 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,   // RCU callbacks
    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
// 每个中断可以有自己的内核线程
// handler 返回 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 每队列一个)

// PCI 设备启用 MSI:
pci_alloc_irq_vectors(pdev, 1, 16, PCI_IRQ_MSI);
  → 设备分配 16 个 MSI 向量
  → 返回 Linux IRQ 号 (每个向量一个)
```

---

## 中断亲和性与负载均衡

```bash
# 查看中断分布
cat /proc/interrupts

# 设置中断亲和性 (哪个 CPU 处理哪个中断)
echo 2 > /proc/irq/<N>/smp_affinity  # 绑定到 CPU 1

# irqbalance 守护进程自动平衡
#   基于中断计数和 CPU 负载
#   某些中断 (如 NVMe) 直接绑定到 NUMA-local 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*
