4. 中断注册核心API:request_irq、request_threaded_irq、devm_request_irq

中断注册是驱动与内核中断子系统建立连接的入口。Linux 内核提供了三个核心 API 用于注册中断处理函数,分别对应不同的使用场景和生命周期管理策略。理解它们的差异、适用场景以及内部协同机制,是设计高并发、低延迟驱动的关键前提。

4.1 核心API概览与对比

下表从函数原型、线程化支持、资源管理、推荐场景四个维度对三个 API 进行横向对比:

API 名称 函数原型(简化) 线程化支持 资源管理 推荐场景
request_irq int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) 否(纯中断上下文) 手动调用 free_irq 简单、快速、非共享中断;中断处理时间极短
request_threaded_irq int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev) 是(handler + 内核线程) 手动调用 free_irq 需要中断底半部处理;处理流程可能阻塞或耗时
devm_request_irq int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id) 否(纯中断上下文) 设备驱动卸载时自动释放 设备模型驱动的标准选择;避免资源泄漏
设计提示: 在设备驱动模型中,优先使用 devm_request_irq 或 devm_request_threaded_irq(如果存在)。这可以显著减少错误路径上的资源释放代码,提高代码健壮性。

4.2 API 内部流程与参数详解

所有中断注册 API 最终都会调用内核核心函数 __setup_irq。下图展示了从驱动调用到内核中断子系统的完整注册流程:

中断注册核心API内部流程 中断注册核心API内部流程 驱动调用层 request_irq / request_threaded_irq / devm_request_irq 参数校验与转换 flags 解析 / 线程化准备 / devm 绑定 核心注册层 __setup_irq() 中断描述符操作 irq_desc / action 链表 线程化分支(可选) 仅 request_threaded_irq 内核线程创建 kthread_create(irq_thread) 线程挂载到 action action->thread = thread 最终统一进入 irq_desc

4.3 request_irq:经典同步中断注册

request_irq 是最基础的中断注册接口。其处理函数在中断上下文中执行,必须满足以下约束:

  • 不可阻塞:不能调用可能导致睡眠的函数(如 mutex_lock、kmalloc(GFP_KERNEL))。
  • 执行时间极短:通常只完成数据读取、状态清除和唤醒等待队列等原子操作。
  • 可重入性:对于共享中断(IRQF_SHARED),处理函数必须能正确判断中断是否来自本设备。
典型代码模式:
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    // 读取硬件状态寄存器
    u32 status = ioread32(dev->regs + STATUS_REG);
    if (!(status & IRQ_PENDING))
        return IRQ_NONE;  // 非本设备中断
    // 清除中断标志
    iowrite32(IRQ_CLEAR, dev->regs + CLEAR_REG);
    // 唤醒等待队列
    wake_up_interruptible(&dev->wait_queue);
    return IRQ_HANDLED;
}

// 注册(通常在 probe 中)
ret = request_irq(dev->irq_num, my_interrupt_handler,
                  IRQF_TRIGGER_RISING, "my_device", dev);
if (ret) {
    dev_err(dev->dev, "Failed to request IRQ %d\n", dev->irq_num);
    goto err_free;
}

4.4 request_threaded_irq:线程化中断处理

当中断处理需要执行耗时操作(如 I2C/SPI 通信、数据拷贝、复杂计算)时,request_threaded_irq 提供了“硬中断快速响应 + 软中断线程化处理”的协同机制。

双函数协作模型:

  • handler(硬中断上下文):快速完成硬件确认、数据抓取、中断清除,返回 IRQ_WAKE_THREAD 唤醒线程。
  • thread_fn(内核线程上下文):在可睡眠的进程上下文中执行剩余处理逻辑。
关键设计约束: handler 必须返回 IRQ_WAKE_THREAD 才能触发 thread_fn 的执行。如果返回 IRQ_HANDLED,内核不会创建或唤醒处理线程。
static irqreturn_t my_interrupt_top(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    // 快速:读取 FIFO 数据到临时缓冲区
    dev->fifo_count = ioread32(dev->regs + FIFO_COUNT);
    if (dev->fifo_count == 0)
        return IRQ_NONE;
    // 快速:DMA 或 PIO 读取数据
    for (int i = 0; i < dev->fifo_count; i++)
        dev->temp_buf[i] = ioread32(dev->regs + FIFO_DATA);
    // 清除中断
    iowrite32(1, dev->regs + IRQ_CLEAR);
    return IRQ_WAKE_THREAD;  // 唤醒线程
}

static irqreturn_t my_interrupt_thread(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    // 线程上下文:可以睡眠、加锁、调用复杂 API
    mutex_lock(&dev->data_lock);
    // 将临时缓冲区数据送入上层协议栈
    my_protocol_process(dev, dev->temp_buf, dev->fifo_count);
    mutex_unlock(&dev->data_lock);
    return IRQ_HANDLED;
}

// 注册
ret = request_threaded_irq(dev->irq_num, my_interrupt_top,
                           my_interrupt_thread,
                           IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                           "my_device", dev);

4.5 devm_request_irq:设备资源管理中断注册

devm_request_irq 是设备驱动模型(device driver model)推荐的中断注册方式。它将中断的生命周期与 struct device 绑定,在驱动卸载或设备移除时自动调用 free_irq。

核心优势:

  • 自动释放:无需在 remove 或错误路径中显式调用 free_irq。
  • 顺序保证:资源释放顺序与注册顺序相反,避免竞态。
  • 错误路径简化:probe 函数中多个资源注册失败时,无需逐一手动回滚。
典型 probe 函数中的使用模式:
static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
    if (!mydev)
        return -ENOMEM;

    // ... 资源映射、时钟使能等 ...

    // 使用 devm 注册中断
    ret = devm_request_irq(dev, mydev->irq_num,
                           my_interrupt_handler,
                           IRQF_TRIGGER_RISING | IRQF_SHARED,
                           "my_device", mydev);
    if (ret) {
        dev_err(dev, "Failed to request IRQ\n");
        return ret;  // 无需手动 free_irq,devm 框架自动处理
    }

    platform_set_drvdata(pdev, mydev);
    return 0;
}

// remove 函数可以非常简单,甚至为空
static int my_remove(struct platform_device *pdev)
{
    // devm 自动释放中断、内存等资源
    return 0;
}

4.6 并发控制视角下的API选择策略

中断注册 API 的选择直接影响驱动并发模型的设计复杂度。下表从并发控制角度给出推荐策略:

并发场景 推荐 API 并发控制策略 注意事项
极短处理(寄存器读写) request_irq 自旋锁 + 禁用本地中断 处理时间 < 50μs
中等处理(数据拷贝) request_threaded_irq handler 用自旋锁;thread 用互斥锁 handler 只做最小工作
复杂处理(协议栈交互) request_threaded_irq thread 中使用互斥锁或完成量 必须设置 IRQF_ONESHOT
设备模型驱动(标准做法) devm_request_irq 结合 devm 自动管理 优先选择,减少错误路径
实践建议: 对于新开发的设备驱动,除非有明确的性能测量表明线程化中断不可接受,否则优先使用 request_threaded_irq 配合 devm_ 变体。这为后续的并发控制优化保留了最大的灵活性。

4.7 中断注册的常见陷阱与避坑指南

  1. 共享中断的 dev_id 唯一性:使用 IRQF_SHARED 时,dev_id 参数必须唯一(通常指向设备结构体),否则 free_irq 无法正确匹配。
  2. IRQF_ONESHOT 的误用:线程化中断且 handler 返回 IRQ_WAKE_THREAD 时,必须设置 IRQF_ONESHOT,否则中断线在 handler 返回后立即重新使能,可能导致中断风暴。
  3. devm 与手动管理的混用:不要在同一个驱动中混用 devm_request_irq 和手动 free_irq,这会导致重复释放或资源泄漏。
  4. 中断号有效性检查:注册前应使用 irq_find_mapping 或检查平台资源确保中断号有效,避免注册野指针中断。
严重警告: 在 SMP 系统中,中断处理函数可能在多个 CPU 上同时执行(除非设置了 IRQF_NOBALANCING 或中断亲和性)。所有中断处理函数必须假设存在并发访问,并采取适当的同步措施。