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 的行为模式。
| 特性 | 快速中断 | 慢速中断 |
|---|---|---|
| 标志位 | 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可开启中断调试信息。