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,避免误处理导致系统行为异常。
irq_handler_t 调用流程与返回值决策 中断触发 调用 ISR (irq_handler_t) 检查硬件状态寄存器 非本设备 本设备 返回 IRQ_NONE 返回 IRQ_HANDLED 正确返回值确保中断处理链路的效率与可靠性

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() 无法正确释放。
中断标志位组合与触发方式选择 触发方式 IRQF_TRIGGER_RISING IRQF_TRIGGER_FALLING IRQF_TRIGGER_HIGH IRQF_TRIGGER_LOW 共享属性 IRQF_SHARED IRQF_ONESHOT IRQF_NO_SUSPEND 组合规则 • 触发方式只能选其一 • 共享属性可叠加 • 示例: IRQF_TRIGGER_FALLING | IRQF_SHARED | IRQF_ONESHOT 组合使用需考虑硬件与内核兼容性

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,超过此值应考虑使用线程化中断。
  • 中断丢失率:在最高触发频率下,不应出现中断丢失,否则需检查硬件消抖或调整触发方式。
中断注册与释放生命周期 request_irq() 中断处理运行中 free_irq() 生命周期关键点 1. 注册时确保硬件已初始化,避免 ISR 访问无效寄存器 2. 运行中可通过 /proc/interrupts 监控中断计数 3. 释放时确保 dev_id 与注册时一致,共享中断需谨慎

3.7 中断注册常见错误与排查

  • -EBUSY:中断号已被占用,且未设置 IRQF_SHARED。检查硬件中断号是否冲突,或添加共享标志。
  • -EINVAL:中断号无效或 flags 包含非法组合。确认中断号范围及标志位兼容性。
  • 中断丢失:ISR 执行时间过长,导致后续中断被屏蔽。考虑使用 IRQF_ONESHOT 或线程化中断。
  • 系统卡死:ISR 中调用了可能导致睡眠的函数(如 mutex_lock、msleep)。确保 ISR 为原子上下文。
调优总结:中断注册与释放是驱动稳定性的基石。合理选择触发方式、正确使用共享标志、确保 ISR 的原子性,是避免竞态条件和系统崩溃的关键。在调试阶段,善用 /proc/interrupts 和动态调试接口,可以快速定位问题。