4. 中断注册与注销:request_irq()、free_irq()、线程化中断 request_threaded_irq()

中断处理程序的生命周期由注册开始,以注销结束。Linux 内核提供了 request_irq() 和 free_irq() 这一对基础接口,以及用于线程化中断的 request_threaded_irq()。理解这些接口的语义、参数和内部流程,是编写可靠设备驱动的第一步。

4.1 request_irq():注册传统中断处理程序

request_irq() 是驱动注册中断处理程序的标准入口。它将一个中断号与一个处理函数绑定,并指定触发方式、设备名和共享标志。

函数原型:
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);
参数 含义 典型值 / 说明
irq 中断号 由 platform_get_irq() 或 pci_irq_vector() 获取
handler 中断处理函数指针 返回 irqreturn_t(IRQ_NONE / IRQ_HANDLED / IRQ_WAKE_THREAD)
flags 中断标志位 IRQF_TRIGGER_RISING、IRQF_SHARED、IRQF_ONESHOT 等
name 设备名称 用于 /proc/interrupts 显示,如 "my_eth"
dev 设备标识 共享中断时用于区分设备,通常传 struct device * 或私有结构体
关键标志位速查:
  • IRQF_SHARED:允许共享中断线,多个驱动可注册同一中断号。
  • IRQF_ONESHOT:在中断线程化时保持中断线屏蔽,直到线程处理完成。
  • IRQF_TRIGGER_HIGH / IRQF_TRIGGER_LOW:电平触发。
  • IRQF_TRIGGER_RISING / IRQF_TRIGGER_FALLING:边沿触发。

4.1.1 注册流程与内部调用链

request_irq() 内部实际调用 request_threaded_irq(),并将 thread_fn 参数设为 NULL。内核随后执行以下步骤:

  1. 权限检查:验证调用者是否有 CAP_SYS_ADMIN 权限。
  2. 描述符分配:通过 irq_to_desc() 获取或创建 struct irq_desc。
  3. 动作链插入:将 struct irqaction 插入描述符的 action 链表。
  4. 硬件配置:调用 __irq_set_trigger() 配置中断控制器触发方式。
  5. 启用中断:若 IRQF_NOAUTOEN 未设置,则调用 enable_irq() 使能该中断线。
request_irq() 内部调用链 request_irq() request_threaded_irq() __setup_irq() 权限检查 irq_to_desc() 动作链插入 __irq_set_trigger() enable_irq()

4.2 free_irq():安全注销中断处理程序

当设备卸载或驱动移除时,必须调用 free_irq() 释放中断资源。该函数会从 irq_desc 的动作链中移除对应的 irqaction,并等待正在执行的中断处理完成。

函数原型:
void free_irq(unsigned int irq, void *dev_id);
参数 说明
irq 要释放的中断号
dev_id 注册时传入的设备标识,用于共享中断时精确匹配
常见错误:
  • 在中断处理函数内部调用 free_irq() 可能导致死锁或同步问题。
  • 共享中断时 dev_id 必须与注册时一致,否则无法正确移除。
  • 忘记调用 free_irq() 会导致中断线泄漏,后续驱动无法注册。

4.2.1 注销流程

  1. 查找动作节点:遍历 irq_desc->action 链表,匹配 dev_id。
  2. 同步等待:调用 synchronize_irq() 等待该中断线上所有正在执行的处理函数完成。
  3. 移除节点:从链表中摘除 irqaction,释放内存。
  4. 关闭中断线:若动作链为空,则调用 __disable_irq() 关闭该中断线。

4.3 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 硬中断处理函数(快速处理),返回 IRQ_WAKE_THREAD 唤醒线程
thread_fn 线程化处理函数,运行在进程上下文,可睡眠
flags 通常需要设置 IRQF_ONESHOT 防止中断嵌套

4.3.1 线程化中断的工作模型

当硬件中断到达时,内核先执行 handler(硬中断部分),若返回 IRQ_WAKE_THREAD,则唤醒对应的内核线程执行 thread_fn。这种设计将中断处理分为两个阶段:

  • 上半部(硬中断):快速完成硬件确认、状态读取、清除中断标志等不可延迟的操作。
  • 下半部(线程):处理数据拷贝、协议解析、I/O 操作等可能阻塞的任务。
线程化中断处理流程 硬件中断到达 handler() 执行 IRQ_WAKE_THREAD? thread_fn() 执行 处理完成 否

4.3.2 使用场景对比

特性 传统 request_irq() 线程化 request_threaded_irq()
上下文 硬中断上下文(原子) 硬中断 + 进程上下文
可睡眠 否 是(thread_fn 中)
中断延迟影响 高(处理期间屏蔽中断) 低(硬中断快速返回)
适用场景 简单状态读取、轻量操作 网络协议栈、块设备、USB 等复杂驱动
标志要求 无特殊要求 通常需要 IRQF_ONESHOT
最佳实践:
  • 对于网络设备(如 NAPI 驱动),推荐使用线程化中断 + NAPI 轮询的组合,实现低延迟与高吞吐。
  • 若 handler 为 NULL,内核会自动使用默认的硬中断处理函数 irq_default_primary_handler(),该函数直接返回 IRQ_WAKE_THREAD。
  • 线程化中断的优先级可通过 sched_setscheduler() 调整,确保关键设备的中断线程获得足够 CPU 时间。

4.4 中断注册与注销的同步保证

内核通过 synchronize_irq() 提供同步机制,确保在注销或修改中断处理程序时,没有正在执行的实例残留。

关键函数:
void synchronize_irq(unsigned int irq);

该函数会阻塞调用者,直到指定中断号上的所有处理函数(包括线程化部分)执行完毕。常用于 free_irq() 内部,也可在驱动卸载时手动调用。

4.4.1 同步流程示意

  1. 调用 disable_irq() 屏蔽中断线。
  2. 调用 synchronize_irq() 等待当前处理完成。
  3. 安全修改或移除 irqaction。
  4. 调用 enable_irq() 重新使能。
注意:在 SMP 系统上,synchronize_irq() 会等待所有 CPU 上的中断处理完成。如果处理函数中持有自旋锁,必须确保锁的释放路径正确,否则可能引发死锁。

4.5 本章小结

  • request_irq() 是注册传统中断处理程序的标准接口,内部委托给 request_threaded_irq()。
  • free_irq() 负责安全注销,通过 synchronize_irq() 保证无并发执行。
  • request_threaded_irq() 将中断处理拆分为硬中断和线程两部分,适用于需要阻塞操作的复杂驱动。
  • 线程化中断与 NAPI 轮询结合,是现代高性能网络驱动的主流设计模式。