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。内核随后执行以下步骤:
- 权限检查:验证调用者是否有 CAP_SYS_ADMIN 权限。
- 描述符分配:通过
irq_to_desc()获取或创建struct irq_desc。 - 动作链插入:将
struct irqaction插入描述符的action链表。 - 硬件配置:调用
__irq_set_trigger()配置中断控制器触发方式。 - 启用中断:若
IRQF_NOAUTOEN未设置,则调用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 注销流程
- 查找动作节点:遍历
irq_desc->action链表,匹配dev_id。 - 同步等待:调用
synchronize_irq()等待该中断线上所有正在执行的处理函数完成。 - 移除节点:从链表中摘除
irqaction,释放内存。 - 关闭中断线:若动作链为空,则调用
__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 操作等可能阻塞的任务。
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 同步流程示意
- 调用
disable_irq()屏蔽中断线。 - 调用
synchronize_irq()等待当前处理完成。 - 安全修改或移除
irqaction。 - 调用
enable_irq()重新使能。
注意:在 SMP 系统上,
synchronize_irq() 会等待所有 CPU 上的中断处理完成。如果处理函数中持有自旋锁,必须确保锁的释放路径正确,否则可能引发死锁。
4.5 本章小结
request_irq()是注册传统中断处理程序的标准接口,内部委托给request_threaded_irq()。free_irq()负责安全注销,通过synchronize_irq()保证无并发执行。request_threaded_irq()将中断处理拆分为硬中断和线程两部分,适用于需要阻塞操作的复杂驱动。- 线程化中断与 NAPI 轮询结合,是现代高性能网络驱动的主流设计模式。