本页目录

中断子系统

覆盖: 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

// 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

// 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
};

中断处理流程

硬件到内核

硬件中断到内核:从设备拉线到进入 handle_irq_desc() ① 设备拉中断线 → 中断控制器(APIC / GIC) ② 中断控制器发中断消息 → CPU(通过 APIC bus / MSI) ③ CPU 响应中断 · 硬件保存 RIP / CS / RFLAGS(x86:push to stack) · 切换内核栈(如果从用户态进入) · 跳转到 IDT 中的入口(entry_SYSCALL_64 的同伴) common_interrupt() do_IRQ() handle_irq_desc() 三步走到 handle_irq_desc():保存现场 → 找到入口 → 交给核心分发,再往下按中断触发类型分流(见下图)。

handle_irq_desc() 路径

handle_irq_desc() 路径:按中断触发类型分流到对应 flow handler handle_irq_desc(desc) generic_handle_irq_desc(desc) desc->handle_irq(desc) 高层 flow handler handle_fasteoi(desc) 最常见 · level-triggered mask_irq(desc) 屏蔽本中断 handle_irq_event(desc) 遍历 action 链表 → action->handler() IRQF_WAKE_THREAD → 唤醒内核线程 unmask_irq(desc) 恢复 handle_edge_irq(desc) edge-triggered ack_irq(desc) → handle_irq_event() → … handle_fasteoi 是最常见路径(level-triggered):先屏蔽中断 → 遍历所有注册 action 调用 handler → 恢复; 若某 action 要求 IRQF_WAKE_THREAD,则唤醒对应内核线程执行下半部。

共享中断

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

下半部 (Bottom Half)

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

softirq

// 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 取代)

// 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: 当前推荐方式

// 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: 更重的下半部

// kernel/workqueue.c
// workqueue 运行在进程上下文 (内核线程 kworker)
// 适合: 需要睡眠、需要长时间运行的工作

// 与 tasklet/softirq 的主要区别:
//   workqueue: 进程上下文, 可以睡眠
//   tasklet/softirq: 中断上下文, 不能睡眠

schedule_work(&work);          // 系统 workqueue
queue_work(my_wq, &work);     // 专用 workqueue

MSI / MSI-X

// 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  (每个向量一个)

中断亲和性与负载均衡

# 查看中断分布
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