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 执行完毕的完整注册与处理流程:
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)之间的协作关系:
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 跟踪中断处理延迟。