3. 中断注册API:request_irq()、devm_request_irq()、free_irq()的使用与参数详解

中断注册,说白了就是告诉内核:「这个中断号归我管,来了信号就叫我」。在嵌入式Linux开发中,我几乎每天都要跟这几个API打交道。今天咱们就把它们掰开揉碎了讲清楚。

3.1 request_irq() — 最基础的中断注册函数

这是最原始、最直接的中断注册方式。它的原型长这样:

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

参数一个一个来看:

参数 含义 我踩过的坑
irq 中断号 别写死!用platform_get_irq()获取
handler 中断处理函数指针 返回值必须是irqreturn_t类型
flags 中断标志位 共享中断必须设IRQF_SHARED
name 设备名称(/proc/interrupts里能看到) 尽量用设备名,别用"my_irq"这种
dev 传递给handler的私有数据 共享中断时这个参数是区分标识

核心要点:handler运行在中断上下文中,不能睡眠!不能调用可能导致阻塞的函数(比如copy_from_user、kmalloc(GFP_KERNEL))。

我早期做项目时,就因为在中断处理函数里调用了printk太多东西,导致系统响应变慢。后来才明白——中断处理函数要快进快出,复杂逻辑扔到底半部去。

3.2 flags参数详解 — 这些标志位你搞清楚了吗?

flags参数是个位掩码,常用的有这么几个:

  • IRQF_TRIGGER_RISING — 上升沿触发。我用的最多,按键、GPIO边沿检测常用。
  • IRQF_TRIGGER_FALLING — 下降沿触发。比如检测设备断开。
  • IRQF_TRIGGER_HIGH — 高电平触发。注意电平触发容易重复进中断。
  • IRQF_TRIGGER_LOW — 低电平触发。
  • IRQF_SHARED — 共享中断。多个设备共用一根中断线时必须设。
  • IRQF_ONESHOT — 中断处理期间屏蔽本中断。常用于 threaded IRQ。

注意:电平触发(HIGH/LOW)如果不配合硬件清除中断状态,会反复触发中断,导致系统卡死。我曾经在一个项目中,GPIO中断设成了高电平触发,结果中断服务函数里忘了清中断标志位,CPU直接跑飞了……

3.3 devm_request_irq() — 资源管理的救星

这个函数是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);

多了一个参数——struct device *dev。好处是什么?

  • 设备卸载时,内核自动帮你调用free_irq()
  • 不用手动管理中断释放,减少内存泄漏风险
  • 代码更简洁,出错路径处理更少

我个人习惯:新写的驱动,能用devm_request_irq()就别用request_irq()。除非你有特殊需求,比如中断的生命周期和设备生命周期不一致。

小技巧:如果你用了devm_request_irq(),就别再手动调用free_irq()了。否则内核会报double free的警告。嗯,我刚开始用的时候也犯过这个错。

3.4 free_irq() — 善后工作要做好

原型很简单:

void free_irq(unsigned int irq, void *dev_id);

注意这个dev_id参数——必须和注册时传的值一致。否则内核找不到对应的中断,释放失败。

什么时候需要手动调用?

  • 用了request_irq()注册的,必须在模块卸载时调用
  • 共享中断时,free_irq()会等待所有中断处理完成才返回
  • 如果中断处理函数正在执行,free_irq()会阻塞直到它完成

我曾经踩过的坑:在中断处理函数里直接调用free_irq()释放自己。这会导致死锁!因为free_irq()要等中断处理函数返回,而它自己就在中断处理函数里……死循环了。正确的做法是:设置一个标志位,在进程上下文中去释放。

3.5 中断处理函数的返回值

中断处理函数必须返回irqreturn_t类型:

static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* 处理中断 */
    if (/* 确实是本设备的中断 */) {
        /* 处理逻辑 */
        return IRQ_HANDLED;
    }
    /* 不是本设备的中断 */
    return IRQ_NONE;
}
  • IRQ_HANDLED — 中断被正确处理了
  • IRQ_NONE — 不是本设备的中断(共享中断时很重要)
  • IRQ_WAKE_THREAD — 唤醒 threaded IRQ 线程

你想想看,如果共享中断的设备都返回IRQ_NONE,内核会认为这是个「虚假中断」,次数多了会打印警告甚至禁用该中断线。

3.6 实际使用示例

来个完整的例子,看看怎么用:

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
    struct my_device *mdev = (struct my_device *)dev_id;
    
    /* 快速处理,实际工作交给底半部 */
    schedule_work(&mdev->work);
    
    return IRQ_HANDLED;
}

static int my_probe(struct platform_device *pdev)
{
    struct my_device *mdev;
    int irq, ret;
    
    mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
    if (!mdev)
        return -ENOMEM;
    
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    
    /* 使用devm版本,省心 */
    ret = devm_request_irq(&pdev->dev, irq, gpio_irq_handler,
                           IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                           "my_gpio_dev", mdev);
    if (ret) {
        dev_err(&pdev->dev, "failed to request irq %d\n", irq);
        return ret;
    }
    
    return 0;
}

总结一下我的经验:

  • 新驱动优先用devm_request_irq()
  • 中断处理函数要快,复杂逻辑用底半部
  • 共享中断时dev_id参数不能为NULL
  • 电平触发一定要配合硬件清中断
  • 不要在中断上下文里调用free_irq()

这几个API看着简单,但用不好真的会出大问题。我见过太多新手在中断处理函数里做复杂操作,结果系统卡死。记住一句话:中断处理函数不是让你干活的,是让你登记活儿的。具体怎么干,交给底半部机制去处理。

中断注册API核心流程 request_irq() devm_request_irq() free_irq() 参数详解: irq: 中断号(从设备树或platform_get_irq获取) handler: 中断处理函数(irqreturn_t类型) flags: 触发方式(IRQF_TRIGGER_*)| 共享标志(IRQF_SHARED)| 一次性(IRQF_ONESHOT) name: 设备名称(/proc/interrupts可见) | dev/dev_id: 私有数据/设备指针 关键区别: • request_irq():手动管理,需在remove/unbind中调用free_irq() • devm_request_irq():设备生命周期管理,自动释放,推荐使用 中断处理函数返回值: • IRQ_HANDLED:中断被正确处理 • IRQ_NONE:不是本设备中断(共享中断时用于区分) • IRQ_WAKE_THREAD:唤醒threaded IRQ线程

个人建议:如果你刚开始写驱动,先用devm_request_irq()把功能跑通。等你对中断生命周期理解透彻了,再考虑用request_irq()做精细控制。毕竟,少一个bug比多一个功能重要得多。