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图
下图展示了从驱动入口到中断注册完成的典型流程:
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() 在内核中的数据结构关系:
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()。前者允许将中断处理分为上半部(硬中断)和下半部(线程化处理),避免长时间关闭本地中断。