3. 中断注册与释放:request_irq()、free_irq()、irq_handler_t原型、中断标志位详解
中断处理程序的注册与释放是驱动开发中与硬件交互的核心环节。正确使用 request_irq() 和 free_irq(),理解中断处理函数原型及标志位含义,是保证系统稳定性和实时性的基础。本节将深入剖析这些接口的底层机制与调优参数。
3.1 irq_handler_t 原型与中断处理函数设计
Linux 内核中,中断处理函数通过 irq_handler_t 类型定义。该类型是一个函数指针,驱动开发者必须遵循其签名来实现具体的中断服务例程(ISR)。
irq_handler_t 原型定义:
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
- 参数 irq:触发中断的硬件中断号,由内核在调用时传入。
- 参数 dev_id:注册中断时传入的私有数据指针,通常指向设备结构体,用于在 ISR 中区分共享中断的设备。
- 返回值 irqreturn_t:枚举类型,必须返回
IRQ_NONE或IRQ_HANDLED。
返回值设计原则:
- IRQ_HANDLED:表示该中断由本驱动程序正确处理,内核会停止向其他共享中断处理函数传递该中断。
- IRQ_NONE:表示该中断并非由本设备产生,内核将继续调用其他注册在同一中断线上的处理函数。
调优要点:在共享中断场景下,务必在 ISR 入口处快速检查硬件状态寄存器,确认中断是否来自本设备。若不属于本设备,立即返回
IRQ_NONE,避免误处理导致系统行为异常。
3.2 request_irq() 函数详解与中断标志位
request_irq() 是注册中断处理函数的入口,其原型及参数含义如下:
request_irq() 原型:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
参数说明:
| 参数 | 类型 | 描述 |
|---|---|---|
irq |
unsigned int | 要注册的中断号,通常由 platform_get_irq() 或 gpio_to_irq() 获取。 |
handler |
irq_handler_t | 指向中断处理函数的指针。 |
flags |
unsigned long | 中断标志位,控制中断触发方式、共享属性等。 |
name |
const char * | 设备名称,用于 /proc/interrupts 标识。 |
dev_id |
void * | 传递给 ISR 的私有数据指针,共享中断时必须唯一。 |
3.3 中断标志位 flags 详解
flags 参数是中断调优的关键,通过位掩码组合控制中断行为。以下为最常用的标志位:
| 标志位 | 值 | 作用 | 调优建议 |
|---|---|---|---|
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发 | 适用于电平跳变明确的数字信号,如按键中断。 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 | 常用于数据线下降沿采样场景。 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发 | 适用于电平保持型中断,需配合硬件消抖。 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 | 常用于唤醒源中断,电平触发可避免丢失。 |
IRQF_SHARED |
0x00000080 | 允许共享中断线 | 多个设备共用同一中断线时必须设置。 |
IRQF_ONESHOT |
0x00002000 | 中断处理期间屏蔽本中断 | 用于线程化中断,防止中断嵌套导致数据竞争。 |
IRQF_NO_SUSPEND |
0x00004000 | 系统休眠时不关闭中断 | 用于唤醒源设备,如 RTC 闹钟中断。 |
常见错误:在共享中断场景下,若未设置
IRQF_SHARED 标志,request_irq() 将返回 -EBUSY。同时,共享中断的 dev_id 必须唯一,否则 free_irq() 无法正确释放。
3.4 free_irq() 函数与中断释放机制
free_irq() 用于在驱动卸载或设备移除时释放已注册的中断。其原型如下:
free_irq() 原型:
void free_irq(unsigned int irq, void *dev_id);
关键行为:
- 非共享中断:直接释放中断线,删除
/proc/interrupts中的对应条目。 - 共享中断:仅当
dev_id匹配时,移除当前驱动注册的处理函数;若还有其他驱动注册在同一中断线上,中断线保持活跃。 - 同步等待:
free_irq()会等待当前正在执行的 ISR 完成,确保释放后不会再有该中断的处理函数被调用。
调优陷阱:在共享中断场景下,
free_irq() 的 dev_id 必须与 request_irq() 时传入的指针完全一致。若传入 NULL 或错误指针,内核将无法找到对应的处理函数,导致释放失败或误释放其他驱动的中断。
3.5 中断注册与释放的典型流程
以下代码展示了在 SPI 设备驱动中注册与释放中断的完整流程:
/* 设备结构体 */
struct my_spi_device {
struct spi_device *spi;
int irq;
struct completion data_ready;
};
/* 中断处理函数 */
static irqreturn_t my_spi_irq_handler(int irq, void *dev_id)
{
struct my_spi_device *dev = (struct my_spi_device *)dev_id;
/* 检查硬件状态寄存器,确认中断来源 */
if (!(readl(dev->reg_base + STATUS_REG) & INT_PENDING))
return IRQ_NONE;
/* 清除中断标志 */
writel(INT_CLEAR, dev->reg_base + CLEAR_REG);
/* 唤醒等待队列 */
complete(&dev->data_ready);
return IRQ_HANDLED;
}
/* 中断注册 */
static int my_spi_probe(struct spi_device *spi)
{
struct my_spi_device *dev;
int ret;
dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
dev->spi = spi;
init_completion(&dev->data_ready);
/* 获取中断号 */
dev->irq = spi->irq;
if (dev->irq < 0)
return dev->irq;
/* 注册中断:下降沿触发 + 共享 + oneshot */
ret = request_irq(dev->irq, my_spi_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_SHARED | IRQF_ONESHOT,
"my_spi_dev", dev);
if (ret) {
dev_err(&spi->dev, "Failed to request IRQ %d\n", dev->irq);
return ret;
}
spi_set_drvdata(spi, dev);
return 0;
}
/* 中断释放 */
static void my_spi_remove(struct spi_device *spi)
{
struct my_spi_device *dev = spi_get_drvdata(spi);
free_irq(dev->irq, dev);
}
调优建议:在
probe() 函数中,建议将中断注册放在设备初始化流程的末尾,确保所有硬件资源准备就绪后再使能中断,避免 ISR 访问未初始化的寄存器。在 remove() 函数中,应优先释放中断,再释放其他资源,防止中断处理函数访问已释放的内存。
3.6 中断注册的调试与验证
注册完成后,可通过以下方式验证中断是否正常工作:
| 方法 | 命令/操作 | 预期结果 |
|---|---|---|
| 查看中断统计 | cat /proc/interrupts |
显示注册的中断号、触发次数及设备名称 |
| 触发中断测试 | 硬件产生中断信号 | /proc/interrupts 中对应中断计数增加 |
| 动态调试 | echo 'file my_driver.c +p' > /sys/kernel/debug/dynamic_debug/control |
ISR 入口和出口打印调试信息 |
| 压力测试 | 高频触发中断(如 10kHz) | 系统不崩溃,无中断丢失,dmesg 无错误 |
性能指标参考:
- 中断延迟(Interrupt Latency):从硬件触发到 ISR 开始执行的时间,通常要求 < 10μs。
- 中断处理时间:ISR 执行时间应尽量短,建议 < 50μs,超过此值应考虑使用线程化中断。
- 中断丢失率:在最高触发频率下,不应出现中断丢失,否则需检查硬件消抖或调整触发方式。
3.7 中断注册常见错误与排查
- -EBUSY:中断号已被占用,且未设置
IRQF_SHARED。检查硬件中断号是否冲突,或添加共享标志。 - -EINVAL:中断号无效或 flags 包含非法组合。确认中断号范围及标志位兼容性。
- 中断丢失:ISR 执行时间过长,导致后续中断被屏蔽。考虑使用
IRQF_ONESHOT或线程化中断。 - 系统卡死:ISR 中调用了可能导致睡眠的函数(如
mutex_lock、msleep)。确保 ISR 为原子上下文。
调优总结:中断注册与释放是驱动稳定性的基石。合理选择触发方式、正确使用共享标志、确保 ISR 的原子性,是避免竞态条件和系统崩溃的关键。在调试阶段,善用
/proc/interrupts 和动态调试接口,可以快速定位问题。