3. 中断上半部(硬中断):注册中断处理函数、中断标志与中断分类

中断上半部(硬中断)是 Linux 内核响应外部设备事件的最快路径。当 CPU 接收到中断信号后,内核会立即执行注册在对应中断号上的处理函数。本节将深入讲解中断处理函数的注册机制、关键中断标志位的含义,以及快速中断与慢速中断的设计差异。

3.1 注册中断处理函数:request_irq

request_irq 是驱动开发者向内核注册中断处理函数的核心 API。该函数将中断号、处理函数指针、中断标志、设备名称和设备上下文绑定在一起,形成一条中断描述符记录。

函数原型:
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev);
参数 类型 说明
irq unsigned int 中断号,由硬件平台或设备树指定
handler irq_handler_t 中断处理函数指针,返回 irqreturn_t
flags unsigned long 中断标志位,控制中断行为特性
name const char * 设备名称,用于 /proc/interrupts 显示
dev void * 设备标识,共享中断时用于区分设备
典型注册示例:
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* 快速处理硬件事件 */
    return IRQ_HANDLED;
}

static int __init my_driver_init(void)
{
    int ret;
    ret = request_irq(IRQ_GPIO_A, my_irq_handler,
                      IRQF_TRIGGER_RISING,
                      "my_gpio_dev", &my_device);
    if (ret) {
        pr_err("Failed to register IRQ %d\n", IRQ_GPIO_A);
        return ret;
    }
    return 0;
}

3.2 中断标志位详解

中断标志位 flags 决定了中断处理函数的行为模式。Linux 内核在 <linux/interrupt.h> 中定义了丰富的标志位,开发者需要根据硬件特性和系统需求组合使用。

标志位 值 作用
IRQF_SHARED 0x00000001 允许多个设备共享同一中断线
IRQF_TRIGGER_RISING 0x00000002 上升沿触发
IRQF_TRIGGER_FALLING 0x00000004 下降沿触发
IRQF_TRIGGER_HIGH 0x00000008 高电平触发
IRQF_TRIGGER_LOW 0x00000010 低电平触发
IRQF_ONESHOT 0x00000020 中断处理期间屏蔽该中断线,处理完再重新使能
IRQF_NO_SUSPEND 0x00000040 系统休眠时不关闭该中断
IRQF_FORCE_RESUME 0x00000080 系统唤醒时强制重新使能中断
IRQF_NO_THREAD 0x00000100 禁止将中断处理函数线程化
IRQF_EARLY_RESUME 0x00000200 在早期恢复阶段重新使能中断
共享中断使用要点:
  • 所有共享同一中断线的设备必须都设置 IRQF_SHARED 标志。
  • 中断处理函数必须检查 dev_id 参数,确认中断是否来自本设备。
  • 共享中断线不支持边沿触发模式,必须使用电平触发。

3.3 快速中断与慢速中断

Linux 内核将中断处理函数分为快速中断和慢速中断两类,这种分类决定了中断处理期间 CPU 的行为模式。

快速中断 vs 慢速中断 处理流程对比 快速中断 (IRQF_DISABLED) CPU 关闭本地中断 执行中断处理函数 恢复中断使能 慢速中断 (默认) CPU 保持中断使能 执行中断处理函数 可被其他中断抢占 快速中断适用于耗时极短的硬件操作;慢速中断允许嵌套,但需注意重入问题
特性 快速中断 慢速中断
标志位 IRQF_DISABLED(旧内核) 默认(无特殊标志)
中断屏蔽 执行期间关闭本地 CPU 中断 执行期间保持中断使能
可抢占性 不可被其他中断抢占 可被更高优先级中断抢占
执行时间 极短(微秒级) 可稍长(毫秒级)
适用场景 清除硬件状态、读取寄存器 数据拷贝、简单协议处理
实时性 高,延迟确定 较低,可能被嵌套中断延迟
内核版本演进说明:

在 Linux 2.6 早期内核中,快速中断通过 SA_INTERRUPT 标志设置。从 2.6.24 开始,IRQF_DISABLED 成为默认行为,所有中断处理函数都在关闭本地中断的情况下执行。现代内核(4.x+)中,中断线程化机制进一步改变了中断处理模型,但硬中断上半部仍然保持快速响应的特性。

3.4 中断处理函数返回值

中断处理函数必须返回 irqreturn_t 类型值,告知内核中断处理结果。

返回值 含义
IRQ_NONE 中断不是本设备产生的,共享中断时使用
IRQ_HANDLED 中断已被正确处理
IRQ_WAKE_THREAD 唤醒中断线程化处理函数(配合 request_threaded_irq)
共享中断处理函数模板:
static irqreturn_t shared_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;

    /* 检查中断是否来自本设备 */
    if (!(readl(dev->reg_base + INT_STATUS) & dev->int_mask)) {
        return IRQ_NONE;  /* 不是本设备的中断 */
    }

    /* 清除中断标志 */
    writel(dev->int_mask, dev->reg_base + INT_CLEAR);

    /* 处理硬件事件 */
    dev->event_count++;

    return IRQ_HANDLED;
}

3.5 中断注册的常见错误与调试

  • 中断号无效:使用 platform_get_irq 或 gpio_to_irq 动态获取中断号,避免硬编码。
  • 共享中断未设置 IRQF_SHARED:多个设备共用中断线时,必须全部设置该标志。
  • 中断处理函数耗时过长:硬中断中不应执行耗时操作,应使用 tasklet 或工作队列推迟处理。
  • 忘记释放中断:驱动卸载时调用 free_irq(irq, dev_id) 释放中断资源。
调试技巧:
  • 查看 /proc/interrupts 确认中断注册情况和触发次数。
  • 使用 cat /proc/irq/{irq_num}/smp_affinity 查看中断 CPU 亲和性。
  • 内核启动参数 irqdebug 可开启中断调试信息。
中断注册与处理完整流程 驱动初始化 request_irq 注册 硬件中断触发 执行中断处理函数