2. Linux 中断子系统架构:中断控制器、中断描述符、中断处理函数注册

Linux 中断子系统是驱动与硬件交互的核心通道。理解其架构,是设计高效底半部机制与 DMA 协同的前提。本章从中断控制器、中断描述符、中断处理函数注册三个维度,拆解中断从硬件触发到软件处理的完整链路。

2.1 中断控制器:硬件中断的汇聚与分发

中断控制器(Interrupt Controller)是 CPU 与外部中断源之间的桥梁。在 ARM SoC 中,常见的中断控制器包括 GIC(Generic Interrupt Controller)v2/v3/v4。其核心职责包括:

  • 中断汇聚:将多个外设中断请求(IRQ)合并为一条信号线送入 CPU。
  • 优先级仲裁:同时到达的中断按优先级排序,高优先级先处理。
  • 中断屏蔽:允许软件按位屏蔽特定中断源。
  • 中断分发:在多核系统中,将中断路由到指定 CPU 核心。
驱动视角: 驱动开发者通常不直接操作中断控制器寄存器,而是通过 Linux 内核提供的 irq_chip 结构体间接控制。中断控制器驱动由 arch/ 或 drivers/irqchip/ 下的代码实现,对设备驱动透明。

下图展示了中断控制器在硬件中断路径中的位置与作用:

中断控制器硬件架构 外设 A (UART) 外设 B (DMA) 外设 C (GPIO) 中断控制器 GIC v3 优先级 / 屏蔽 / 分发 CPU 核心 IRQ 线 FIQ / IRQ 信号线 CPU 响应后进入异常向量

2.2 中断描述符:内核的中断对象模型

Linux 内核使用 struct irq_desc 描述每个中断源。中断描述符是中断子系统的核心数据结构,串联起硬件中断号、中断处理函数链表、中断控制器操作、线程化处理等机制。

字段 类型 说明
irq_data struct irq_data 硬件中断号、irq_chip 指针、芯片私有数据
action struct irqaction * 中断处理函数链表头(共享中断时挂接多个 handler)
status_use_accessors unsigned int 中断状态标志(IRQ_INPROGRESS / IRQ_DISABLED 等)
depth unsigned int 中断禁用嵌套深度
threads_oneshot atomic_t 线程化中断的 one-shot 同步计数
kstat_irqs struct irqstat 中断统计信息(触发次数、时间戳)

中断描述符组织在全局数组 irq_desc[NR_IRQS] 中,通过 irq_to_desc(irq) 宏根据中断号快速索引。每个描述符内部维护一个 action 链表,支持多个设备共享同一中断线。

关键设计: 中断描述符的 action 链表是驱动注册中断处理函数的挂载点。共享中断时,内核遍历链表依次调用各驱动的 handler,直到某个 handler 返回 IRQ_HANDLED 为止。

下图展示了中断描述符在内核中的组织关系:

中断描述符 irq_desc 结构 irq_desc[0] irq_desc[1] irq_desc[2] irq_desc[3] irq_desc[...] irq_desc[2] 内部展开 irq_data (hwirq=2, chip=GIC) status: IRQ_ACTIVE depth: 0 action → irqaction 链表 handler A handler B

2.3 中断处理函数注册:request_irq 与 irqaction

驱动通过 request_irq() 或 devm_request_irq() 注册中断处理函数。内核在后台完成以下操作:

  1. 分配 irqaction 结构体:填充 handler、dev_id、irq flags 等字段。
  2. 挂入 action 链表:将 irqaction 插入对应 irq_desc 的 action 链表尾部。
  3. 使能中断:调用 irq_chip 的 irq_unmask() 回调,允许硬件中断送达 CPU。
  4. 设置中断流处理:根据中断类型(电平触发/边沿触发)选择 handle_level_irq 或 handle_edge_irq。
常见错误: 注册中断时未检查返回值。request_irq 可能返回 -EBUSY(中断已被独占注册)、-EINVAL(无效参数)或 -ENOMEM(内存不足)。驱动必须处理这些错误,否则中断无法正常工作。

下面是典型的中断注册代码模板:

#include <linux/interrupt.h>
#include <linux/device.h>

/* 中断处理函数(顶半部) */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;

    /* 读取硬件状态寄存器,判断中断源 */
    uint32_t status = ioread32(dev->regs + STATUS_REG);

    if (status & RX_DONE) {
        /* 调度 tasklet 或 workqueue 处理数据 */
        tasklet_schedule(&dev->rx_tasklet);
    }

    if (status & TX_DONE) {
        /* 唤醒等待队列 */
        wake_up_interruptible(&dev->tx_wait);
    }

    return IRQ_HANDLED;
}

/* 驱动初始化时注册中断 */
static int my_probe(struct platform_device *pdev)
{
    struct my_device *dev;
    int ret, irq;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    /* 注册中断:IRQF_SHARED 表示共享中断 */
    ret = devm_request_irq(&pdev->dev, irq, my_irq_handler,
                           IRQF_SHARED | IRQF_TRIGGER_HIGH,
                           "my_device", dev);
    if (ret) {
        dev_err(&pdev->dev, "failed to request irq %d: %d\n", irq, ret);
        return ret;
    }

    dev_info(&pdev->dev, "irq %d registered successfully\n", irq);
    return 0;
}

2.4 中断处理流程:从硬件触发到软件执行

一个完整的中断处理流程分为以下阶段:

阶段 执行者 关键操作
1. 硬件中断触发 外设 / 中断控制器 外设拉高 IRQ 线,GIC 仲裁后向 CPU 发送 IRQ 信号
2. CPU 异常向量 CPU 硬件 保存上下文,跳转到 vector_irq 入口
3. 中断流处理 handle_arch_irq 读取 GIC 的 IAR 寄存器获取中断号,调用 handle_fasteoi_irq
4. 通用中断层 generic_handle_irq_desc 根据 irq_desc 的 action 链表,调用注册的 handler
5. 驱动顶半部 驱动 handler 快速处理硬件状态,调度底半部,返回 IRQ_HANDLED
6. 中断退出 CPU / GIC 写 EOI 寄存器,恢复上下文,返回被中断的任务

下图完整展示了从硬件中断到驱动顶半部的调用链:

中断处理完整调用链 外设触发中断 硬件拉高 IRQ 线 GIC 仲裁 优先级 / 分发 CPU 异常向量 保存上下文 handle_fasteoi_irq 读取 IAR / 调用流处理 generic_handle_irq 查找 irq_desc 驱动顶半部 handler 调度底半部 中断退出 写 EOI / 恢复上下文 关键时间约束 顶半部必须快速返回 禁止休眠 / 调度 耗时操作移至底半部

2.5 中断标志与共享中断

request_irq() 的 flags 参数控制中断行为。常用标志如下:

标志 值 说明
IRQF_SHARED 0x00000080 允许多个设备共享同一中断线
IRQF_TRIGGER_RISING 0x00000001 上升沿触发
IRQF_TRIGGER_FALLING 0x00000002 下降沿触发
IRQF_TRIGGER_HIGH 0x00000004 高电平触发
IRQF_TRIGGER_LOW 0x00000008 低电平触发
IRQF_ONESHOT 0x00002000 中断处理期间硬件中断保持屏蔽,用于线程化中断
IRQF_NO_SUSPEND 0x00004000 系统休眠时不关闭该中断
共享中断注意事项: 使用 IRQF_SHARED 时,所有注册的 handler 必须接受 dev_id 参数,并在 handler 中判断中断是否来自本设备。如果中断不是本设备触发的,必须返回 IRQ_NONE,否则会导致系统误判。

2.6 本章小结