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