4. 中断注册API:request_irq()函数详解

在Linux内核驱动中,中断处理程序的注册是设备驱动与硬件交互的核心环节。request_irq()是内核提供给驱动开发者用于注册中断处理函数的标准API。理解其参数含义、返回值处理以及使用场景,是编写可靠中断驱动的基础。

4.1 request_irq()函数原型

request_irq()函数定义在 <linux/interrupt.h> 头文件中,其原型如下:

int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev_id);

该函数将指定的中断号 irq 与处理函数 handler 绑定,并配置中断触发方式等属性。调用成功返回0,失败返回负的错误码。

4.2 参数详解

下表列出了 request_irq() 五个参数的含义与使用要点:

参数 类型 含义 关键说明
irq unsigned int 中断号 硬件中断线的逻辑编号,通常由 platform_get_irq() 或 gpio_to_irq() 获取,也可在设备树中指定。
handler irq_handler_t 中断处理函数指针 函数类型为 irqreturn_t (*handler)(int irq, void *dev_id)。返回 IRQ_HANDLED 或 IRQ_NONE。
flags unsigned long 中断标志位 控制中断触发方式、共享属性、是否可唤醒等。常用标志见下文。
name const char * 中断名称 用于 /proc/interrupts 中标识中断源,便于调试。通常设为驱动名称。
dev_id void * 设备标识指针 传递给处理函数的私有数据。共享中断时必须唯一,用于区分设备;非共享中断可传 NULL。

4.3 flags 标志位详解

flags 参数由多个位掩码组合而成,常用的标志定义在 <linux/interrupt.h> 中:

  • IRQF_TRIGGER_RISING — 上升沿触发
  • IRQF_TRIGGER_FALLING — 下降沿触发
  • IRQF_TRIGGER_HIGH — 高电平触发
  • IRQF_TRIGGER_LOW — 低电平触发
  • IRQF_SHARED — 允许共享中断线,多个设备可共用同一中断号
  • IRQF_ONESHOT — 中断处理期间硬件中断线被屏蔽,处理完成后自动重新使能
  • IRQF_NO_SUSPEND — 系统休眠时不关闭该中断
  • IRQF_FORCE_RESUME — 系统唤醒时强制重新使能中断
实战建议:对于边沿触发的中断,通常配合 IRQF_ONESHOT 使用,防止中断处理过程中再次触发导致嵌套。对于共享中断,必须设置 IRQF_SHARED 标志,且 dev_id 不能为 NULL。

4.4 返回值处理

request_irq() 的返回值是判断注册是否成功的关键:

返回值 含义 处理方式
0 注册成功 继续后续驱动初始化流程
-EINVAL 参数无效(如 irq 号无效、handler 为 NULL) 检查中断号获取逻辑,确认 handler 非空
-EBUSY 中断已被占用且未设置 IRQF_SHARED 确认中断号是否冲突,或添加共享标志
-ENOMEM 内核内存不足 检查系统内存状态,考虑延迟注册或释放其他资源
常见错误:许多初学者忽略对返回值的检查,直接假设注册成功。如果 request_irq() 返回非零值,后续的中断处理将不会触发,且可能导致设备无法正常工作。务必在驱动初始化时检查返回值并执行错误回滚。

4.5 中断注册流程SVG图

下图展示了从驱动入口到中断注册完成的典型流程:

中断注册流程 request_irq() 注册流程 驱动入口函数 获取中断号 (platform_get_irq) 检查 irq 有效性 (irq > 0) 调用 request_irq() 检查返回值 == 0 ? 是 (0) 注册成功 否 (非0) 错误回滚 说明:注册失败时需调用 free_irq() 清理已分配资源,并返回错误码给上层。 共享中断必须设置 IRQF_SHARED 标志,且 dev_id 不能为 NULL。

4.6 完整注册代码示例

以下是一个典型的中断注册代码片段,展示了参数设置与返回值处理:

#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;
    /* 处理中断事件 */
    pr_info("Interrupt received on irq %d\n", irq);
    return IRQ_HANDLED;
}

static int my_probe(struct platform_device *pdev)
{
    struct my_device *dev;
    int irq, ret;

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

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

    /* 注册中断,使用上升沿触发,非共享 */
    ret = request_irq(irq, my_irq_handler,
                      IRQF_TRIGGER_RISING,
                      "my_driver", dev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request irq %d, ret=%d\n",
                irq, ret);
        return ret;
    }

    dev->irq = irq;
    platform_set_drvdata(pdev, dev);
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    struct my_device *dev = platform_get_drvdata(pdev);
    free_irq(dev->irq, dev);
    return 0;
}
关键点总结:
  • 中断号必须通过标准接口获取,不要硬编码。
  • flags 参数根据硬件手册选择正确的触发方式。
  • 共享中断时 dev_id 必须唯一,用于 free_irq() 时区分。
  • 返回值检查是驱动健壮性的基本保障。
  • 在 remove 函数中必须调用 free_irq() 释放中断。

4.7 共享中断的 dev_id 作用

当多个设备共享同一中断线时,内核无法仅通过中断号区分中断来源。dev_id 参数在此场景下至关重要:

  • 注册时:每个共享设备传入不同的 dev_id 指针。
  • 中断触发时:内核遍历该中断号上的所有处理函数,依次调用,直到某个处理函数返回 IRQ_HANDLED。
  • 注销时:free_irq(irq, dev_id) 通过 dev_id 精确移除对应的处理函数,不会影响其他共享设备。
共享中断陷阱:如果共享中断的处理函数返回 IRQ_NONE,内核会认为该中断未被处理,可能导致系统认为存在“伪中断”而触发错误处理。务必在确认中断属于本设备后才返回 IRQ_HANDLED。

4.8 中断注册状态SVG图

下图展示了 request_irq() 在内核中的数据结构关系:

中断注册数据结构 request_irq() 内核数据结构关系 irq_desc[irq] action 链表头 irqaction 节点1 handler, flags, name dev_id = device1 irqaction 节点2 handler, flags, name dev_id = device2 next 共享中断链表结构 每个 irq_desc 维护一个 irqaction 链表,共享中断时多个节点串联。 中断触发时内核遍历链表,依次调用各节点的 handler,直到返回 IRQ_HANDLED。 free_irq(irq, dev_id) 通过 dev_id 匹配并移除对应节点。

4.9 常见错误与避坑指南

错误场景 后果 解决方案
未检查返回值 中断未注册,设备无响应 始终检查 request_irq() 返回值
共享中断未设 IRQF_SHARED 注册返回 -EBUSY 确认硬件是否支持共享,添加标志
共享中断 dev_id 为 NULL 无法区分设备,free_irq 可能误删 传入设备结构体指针作为 dev_id
中断处理函数耗时过长 系统延迟增大,可能丢失中断 使用 request_threaded_irq() 将耗时操作移至线程
忘记在 remove 中释放中断 模块卸载后中断仍可能触发,导致内核崩溃 在 remove 函数中调用 free_irq()
进阶提示:对于需要在中断上下文中执行耗时操作的场景,推荐使用 request_threaded_irq() 替代 request_irq()。前者允许将中断处理分为上半部(硬中断)和下半部(线程化处理),避免长时间关闭本地中断。