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/ 下的代码实现,对设备驱动透明。
下图展示了中断控制器在硬件中断路径中的位置与作用:
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 为止。
下图展示了中断描述符在内核中的组织关系:
2.3 中断处理函数注册:request_irq 与 irqaction
驱动通过 request_irq() 或 devm_request_irq() 注册中断处理函数。内核在后台完成以下操作:
- 分配 irqaction 结构体:填充 handler、dev_id、irq flags 等字段。
- 挂入 action 链表:将 irqaction 插入对应 irq_desc 的 action 链表尾部。
- 使能中断:调用 irq_chip 的 irq_unmask() 回调,允许硬件中断送达 CPU。
- 设置中断流处理:根据中断类型(电平触发/边沿触发)选择 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 寄存器,恢复上下文,返回被中断的任务 |
下图完整展示了从硬件中断到驱动顶半部的调用链:
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,否则会导致系统误判。