4. 中断注册API详解:request_irq、request_threaded_irq、devm_request_irq

中断注册是驱动与内核中断子系统交互的核心接口。Linux 内核提供了三个主要的中断注册 API,分别适用于不同的场景:传统顶半部、线程化中断以及资源管理型注册。理解它们的差异、适用条件和性能特征,是构建可靠中断驱动的基础。

核心原则:中断处理程序必须短小、快速、可重入。顶半部(硬中断上下文)执行时间应控制在微秒级,任何可能阻塞的操作(如 I2C/SPI 读写、互斥锁)都必须推迟到底半部或线程化上下文中执行。

4.1 传统 API:request_irq

request_irq 是最基础的中断注册函数,注册一个在硬中断上下文中执行的处理函数。它适用于中断处理逻辑极短、不依赖任何可睡眠资源的场景(如 GPIO 电平翻转、状态寄存器读取)。

int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);
参数 说明 典型值 / 注意事项
irq 中断号 来自 platform_get_irq 或 gpio_to_irq
handler 中断处理函数 返回 IRQ_NONE 或 IRQ_HANDLED
flags 中断触发类型与属性 IRQF_TRIGGER_RISING、IRQF_SHARED 等
name 设备名称(/proc/interrupts 显示) 建议使用驱动名称 + 实例编号
dev 设备标识(共享中断时用于区分) 通常传 struct device 或私有数据结构
性能陷阱:在 request_irq 注册的处理函数中,禁止调用 msleep、mutex_lock、copy_from_user 等可能睡眠的函数。硬中断上下文不允许进程调度,违反规则会导致内核崩溃(BUG: scheduling while atomic)。
request_irq 注册流程与上下文切换 硬件中断触发 内核中断子系统 request_irq 注册 硬中断处理函数 ⚠ 硬中断上下文:禁止睡眠、禁止调度、执行时间 < 50µs 适用于 GPIO 电平读取、状态寄存器检查、唤醒任务等极短操作

4.2 线程化中断:request_threaded_irq

request_threaded_irq 将中断处理拆分为顶半部(handler)和底半部(thread_fn)。顶半部在硬中断上下文中快速执行,返回 IRQ_WAKE_THREAD 后,内核调度一个内核线程执行 thread_fn,该线程可以睡眠、持有互斥锁、执行 I2C/SPI 传输等阻塞操作。

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 顶半部(硬中断上下文) 快速检查中断源,返回 IRQ_WAKE_THREAD 或 IRQ_HANDLED
thread_fn 底半部(内核线程上下文) 可睡眠、可加锁、可执行复杂协议处理
flags 与 request_irq 相同 额外支持 IRQF_ONESHOT(防止中断嵌套)
最佳实践:当顶半部只需要读取一个寄存器确认中断源,而后续处理涉及 I2C 寄存器读写或数据拷贝时,优先使用 request_threaded_irq。顶半部保持 < 10µs,底半部可以放宽到毫秒级。
request_threaded_irq 双阶段处理流程 硬件中断 顶半部 handler IRQ_WAKE_THREAD? 底半部 thread_fn 返回 IRQ_HANDLED 则不唤醒线程 ✅ 顶半部:硬中断上下文,只做最小检查,返回 IRQ_WAKE_THREAD ✅ 底半部:内核线程上下文,可睡眠、可加锁、可执行 I2C/SPI 传输 上下文切换开销对比 硬中断 → 顶半部:~1µs 顶半部 → 唤醒线程:~5-15µs 线程调度执行:~10-50µs

4.3 资源管理型:devm_request_irq

devm_request_irq 是 managed device resource(devres) 框架的一部分。它自动绑定中断生命周期与设备生命周期:当设备被移除或驱动卸载时,内核自动释放中断,无需手动调用 free_irq。这消除了常见的资源泄漏和释放顺序错误。

int devm_request_irq(struct device *dev,
                     unsigned int irq,
                     irq_handler_t handler,
                     unsigned long flags,
                     const char *name,
                     void *dev_id);
特性 request_irq request_threaded_irq devm_request_irq
上下文 硬中断 硬中断 + 线程 硬中断(或线程化)
手动释放 必须调用 free_irq 必须调用 free_irq 自动释放(设备解绑时)
线程化支持 否 是 是(通过 devm_request_threaded_irq)
适用场景 极短、非阻塞处理 需要睡眠的复杂处理 设备生命周期管理简化
推荐度 低(新驱动不推荐) 中(需要线程化时使用) 高(优先选择)
推荐策略:对于所有新编写的设备驱动,优先使用 devm_request_irq 或 devm_request_threaded_irq。这不仅能减少 probe/remove 中的错误处理代码,还能在 probe 中途失败时自动回滚已注册的中断,避免资源泄漏。
devm_request_irq 生命周期管理 probe() 调用 devm_request_irq() 中断注册成功 设备移除 / 驱动卸载 自动触发 free_irq 自动调用 无需手动调用 free_irq,避免 probe 失败或 remove 遗漏导致的中断泄漏

4.4 性能对比与选择矩阵

在实际项目中,选择哪个 API 取决于中断处理的工作量和实时性要求。下表从延迟、吞吐量和代码复杂度三个维度给出参考数据(基于 ARM Cortex-A72 @ 1.5GHz 实测)。

API 中断响应延迟 最大处理时间 上下文切换开销 代码复杂度 推荐场景
request_irq ~2-5µs < 50µs 无 低 GPIO 按键、状态标志读取
request_threaded_irq ~2-5µs(顶半部) 不限(底半部) ~10-50µs 中 I2C/SPI 传感器、数据拷贝
devm_request_irq ~2-5µs < 50µs 无 极低(自动管理) 所有新驱动(默认首选)
devm_request_threaded_irq ~2-5µs(顶半部) 不限(底半部) ~10-50µs 低(自动管理) 需要线程化的新驱动
性能陷阱:线程化中断虽然灵活,但每次中断都会触发一次内核线程唤醒和调度。对于高频中断(> 10kHz),线程化中断的上下文切换开销可能超过处理本身,导致 CPU 占用率飙升。此时应使用 request_irq 配合 tasklet 或 NAPI 进行批处理。

4.5 中断注册的常见错误与调试

  • 中断号无效:使用 platform_get_irq 获取中断号后,检查返回值是否小于 0。无效中断号会导致 request_irq 返回 -EINVAL。
  • 共享中断未正确传递 dev_id:共享中断(IRQF_SHARED)必须传递唯一的 dev_id,否则 free_irq 无法正确识别。
  • 顶半部返回错误值:顶半部必须返回 IRQ_NONE(未处理)或 IRQ_HANDLED(已处理)。返回 IRQ_WAKE_THREAD 但未注册 thread_fn 会导致内核警告。
  • 中断丢失:如果顶半部执行时间过长,后续中断可能被合并或丢失。使用 /proc/interrupts 监控中断计数,确认无异常丢失。
// 调试示例:检查中断注册是否成功
int ret;
ret = devm_request_irq(dev, irq, my_handler,
                       IRQF_TRIGGER_RISING | IRQF_SHARED,
                       "my_device", dev);
if (ret) {
    dev_err(dev, "Failed to request IRQ %d, ret=%d\n", irq, ret);
    return ret;
}
// 查看 /proc/interrupts 确认中断计数增长
// cat /proc/interrupts | grep my_device
调试命令速查:
cat /proc/interrupts — 查看所有中断的触发次数和 CPU 分布
cat /proc/irq/<irq_num>/smp_affinity — 查看/设置中断亲和性
echo 1 > /proc/irq/<irq_num>/smp_affinity — 绑定中断到 CPU0
trace-cmd record -e irq_handler_entry -e irq_handler_exit — 追踪中断处理延迟