2. Linux 中断处理机制:中断注册函数 request_irq、中断处理函数原型、flags 标志详解

在 Linux 内核中,设备驱动程序通过 request_irq() 函数向内核注册一个中断处理函数(Interrupt Handler / ISR)。该函数是中断处理流程的入口点,其原型、参数含义以及 flags 标志位的选择,直接决定了中断响应的实时性、共享能力以及上下文的执行环境。本节将深入剖析这三个核心要素。

2.1 中断注册函数 request_irq

request_irq() 是驱动程序与中断子系统交互的标准接口。其函数原型定义在 <linux/interrupt.h> 中:

int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);

参数详解:

参数 类型 说明
irq unsigned int 要注册的中断号。通常由 platform_get_irq()、pci_irq_vector() 或设备树解析获得。
handler irq_handler_t 中断处理函数指针。当中断发生时,内核调用此函数。
flags unsigned long 中断管理标志位,控制中断的触发方式、共享策略、电源管理等行为。
name const char * 设备名称,用于 /proc/interrupts 和内核调试信息。
dev void * 传递给中断处理函数的设备标识。对于共享中断,此参数用于区分不同设备。
返回值:成功返回 0;失败返回负的错误码(如 -EBUSY 表示中断已被占用且未设置共享标志,-EINVAL 表示无效参数)。

2.2 中断处理函数原型

中断处理函数(ISR)必须遵循内核定义的严格原型。其类型 irq_handler_t 定义如下:

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);

参数与返回值:

  • irq:触发本次中断的中断号。在共享中断场景下,同一个处理函数可能被多个中断号注册,此参数用于区分。
  • dev_id:注册时传入的 dev 指针。通常指向设备结构体,用于在 ISR 中访问设备寄存器、私有数据或完成队列。
  • 返回值:必须返回 irqreturn_t 枚举值:
    • IRQ_NONE (0):中断不是本设备产生的(用于共享中断检测)。
    • IRQ_HANDLED (1):中断已被本设备正确处理。
    • IRQ_WAKE_THREAD (2):请求内核调度中断线程化处理函数(配合 request_threaded_irq() 使用)。
关键约束:中断处理函数运行在中断上下文(原子上下文)中,不可休眠、不可调度、不可使用可能引起阻塞的 API(如 mutex_lock()、copy_from_user()、kmalloc(GFP_KERNEL))。所有耗时操作必须推迟到底半部(Bottom Half)执行。

2.3 flags 标志详解

flags 参数由多个位掩码组合而成,定义在 <linux/interrupt.h> 和 <linux/irqflags.h> 中。以下是最常用的标志位:

标志 值 作用
IRQF_TRIGGER_RISING 0x00000001 上升沿触发
IRQF_TRIGGER_FALLING 0x00000002 下降沿触发
IRQF_TRIGGER_HIGH 0x00000004 高电平触发
IRQF_TRIGGER_LOW 0x00000008 低电平触发
IRQF_SHARED 0x00000080 允许多个设备共享同一中断线
IRQF_PROBE_SHARED 0x00000100 中断共享探测标志,与 IRQF_SHARED 配合使用
IRQF_NO_SUSPEND 0x00004000 系统休眠时不关闭此中断
IRQF_FORCE_RESUME 0x00008000 系统唤醒时强制恢复中断
IRQF_NO_THREAD 0x00010000 禁止中断线程化,强制在硬中断上下文执行
IRQF_EARLY_RESUME 0x00020000 在系统唤醒早期阶段恢复中断
IRQF_ONESHOT 0x00002000 中断处理期间屏蔽同一条中断线,直到处理函数返回
实战建议:
  • 对于边缘触发(Edge-triggered)中断,通常使用 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。
  • 对于电平触发(Level-triggered)中断,使用 IRQF_TRIGGER_HIGH 或 IRQF_TRIGGER_LOW。
  • 当多个设备共用一根中断线时,必须设置 IRQF_SHARED,且所有共享设备必须使用相同的触发方式。
  • 对于需要快速响应的实时设备,避免使用 IRQF_NO_THREAD 以外的标志,以免引入调度延迟。

2.4 中断注册与处理流程 SVG 架构图

下图展示了从设备触发中断到 ISR 执行完毕的完整注册与处理流程:

中断注册与处理流程 中断注册与处理流程 设备触发中断 内核中断子系统 查找 irq_desc 与 handler 执行 ISR 返回 IRQ_HANDLED 注册阶段 request_irq(irq, handler, flags, name, dev) 处理阶段 irqreturn_t handler( int irq, void *dev_id) 返回 IRQ_NONE/HANDLED

2.5 中断注册实战示例

以下代码展示了一个典型的中断注册与处理函数实现:

#include <linux/interrupt.h>
#include <linux/device.h>

struct my_device {
    void __iomem *base;
    int irq;
    struct completion done;
};

/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *mydev = (struct my_device *)dev_id;
    unsigned int status;

    /* 读取设备状态寄存器 */
    status = ioread32(mydev->base + REG_STATUS);

    if (status & INT_PENDING) {
        /* 清除中断标志 */
        iowrite32(INT_CLEAR, mydev->base + REG_CLEAR);

        /* 唤醒等待队列或完成量 */
        complete(&mydev->done);

        return IRQ_HANDLED;
    }

    return IRQ_NONE;  /* 不是本设备的中断 */
}

/* 驱动初始化时注册中断 */
static int my_probe(struct platform_device *pdev)
{
    struct my_device *mydev;
    int ret;

    mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
    mydev->irq = platform_get_irq(pdev, 0);
    init_completion(&mydev->done);

    /* 注册中断:上升沿触发,非共享 */
    ret = request_irq(mydev->irq,
                      my_irq_handler,
                      IRQF_TRIGGER_RISING,
                      "my_device",
                      mydev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ %d\n", mydev->irq);
        return ret;
    }

    return 0;
}

/* 驱动移除时释放中断 */
static int my_remove(struct platform_device *pdev)
{
    struct my_device *mydev = platform_get_drvdata(pdev);
    free_irq(mydev->irq, mydev);
    return 0;
}
最佳实践:
  • 优先使用 devm_request_irq() 替代 request_irq(),前者由设备资源管理框架自动释放,避免遗漏 free_irq()。
  • 在 ISR 中只做最少的必要操作(如读取状态、清除中断标志),将数据拷贝和协议解析推迟到底半部(Tasklet/Workqueue)。
  • 对于共享中断,必须检查 dev_id 对应的设备寄存器,确认中断是否由本设备产生,否则返回 IRQ_NONE。

2.6 中断上下文与底半部调度关系

下图展示了 ISR 与底半部(Bottom Half)之间的协作关系:

中断上下文与底半部调度 中断上下文与底半部调度关系 硬中断上下文 (ISR) 原子操作,不可休眠 调度 底半部调度 Tasklet / Workqueue 软中断 可休眠 上下文对比 • 硬中断上下文:中断关闭,不可抢占,不可休眠,不能访问用户空间 • 软中断上下文:中断开启,不可休眠,可被硬中断抢占 • Tasklet:基于软中断,同一 Tasklet 在单 CPU 上串行执行 • Workqueue:进程上下文,可休眠,可调度,适合耗时操作 • 选择原则:快速操作用 Tasklet,慢速/可阻塞操作用 Workqueue

2.7 常见错误与调试技巧

错误场景 现象 解决方案
未设置 IRQF_SHARED 但中断线已被占用 request_irq() 返回 -EBUSY 确认硬件是否支持共享中断,添加 IRQF_SHARED 标志
ISR 中调用了 msleep() 或 mutex_lock() 内核崩溃(Kernel Panic)或死锁 将阻塞操作移至 Workqueue 或 Threaded IRQ
共享中断中未正确检查设备状态 系统响应变慢,CPU 占用率高 在 ISR 开头读取设备状态寄存器,确认中断归属
中断触发方式与硬件不匹配 中断丢失或频繁误触发 查阅硬件手册,使用正确的 IRQF_TRIGGER_* 标志
忘记调用 free_irq() 模块卸载后中断仍被触发,导致系统崩溃 使用 devm_request_irq() 自动管理生命周期
调试命令:
  • cat /proc/interrupts:查看当前系统中所有中断的注册情况、触发次数和 CPU 亲和性。
  • cat /proc/irq/<irq_num>/smp_affinity:查看或设置中断的 CPU 亲和性。
  • echo 1 > /sys/kernel/debug/tracing/tracing_on && echo 'irq:*' > /sys/kernel/debug/tracing/set_event:使用 ftrace 跟踪中断处理延迟。