4. 中断处理函数注册:request_irq()、free_irq()、中断标志位详解
在网卡驱动中,中断处理函数的注册与注销是连接硬件中断信号与软件处理逻辑的核心桥梁。Linux 内核提供了 request_irq() 和 free_irq() 两个关键 API,配合丰富的中断标志位,让开发者能够精确控制中断的触发方式、共享策略以及执行上下文。本节将深入剖析这些接口的用法、参数含义及典型配置场景。
4.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 |
中断号,通常由 pci_irq_vector() 或平台设备资源获取 |
handler |
irq_handler_t |
中断处理函数指针,返回 irqreturn_t 类型 |
flags |
unsigned long |
中断标志位,控制触发方式、共享策略等 |
name |
const char * |
设备名称,显示在 /proc/interrupts 中 |
dev |
void * |
传递给处理函数的设备标识,共享中断时用于区分设备 |
返回值检查:
request_irq() 成功返回 0,失败返回负的错误码(如 -EBUSY 表示中断号已被占用且不允许共享)。驱动中必须检查返回值,并在失败时执行清理操作。
4.2 free_irq():注销中断处理函数
当设备卸载或驱动移除时,必须调用 free_irq() 释放中断资源:
void free_irq(unsigned int irq, void *dev);
关键行为:
- 等待处理完成:在返回前,
free_irq()会确保该中断号上所有正在执行的处理函数均已退出,因此不能在持有自旋锁的上下文中调用。 - 共享中断处理:对于共享中断(设置了
IRQF_SHARED),dev参数必须与注册时一致,用于精确匹配要移除的处理函数。 - 调用时机:通常在驱动的
remove()回调或模块卸载函数中调用,且应在释放硬件资源(如取消映射 IO 内存)之前完成。
4.3 中断标志位详解
中断标志位 flags 是 request_irq() 的核心配置项,通过按位或组合多个标志位来定义中断行为。以下是网卡驱动中最常用的标志位:
| 标志位 | 值 | 说明 |
|---|---|---|
IRQF_SHARED |
0x00000080 | 允许中断线被多个设备共享,处理函数必须通过 dev 参数判断是否为自己的设备产生的中断 |
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发,常用于 PCIe MSI/MSI-X 中断 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发,传统 PCI 中断(INTx)常用 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 |
IRQF_ONESHOT |
0x00002000 | 中断处理期间屏蔽该中断线,处理完成后自动重新使能,常用于线程化中断处理 |
IRQF_NO_SUSPEND |
0x00004000 | 系统休眠时不关闭该中断,用于唤醒源 |
IRQF_NO_THREAD |
0x00010000 | 禁止将中断处理函数线程化,强制在硬中断上下文中执行 |
共享中断的陷阱:使用
IRQF_SHARED 时,处理函数必须检查硬件状态寄存器确认中断是否来自本设备。如果误处理了其他设备的中断,可能导致数据错乱或死锁。同时,共享中断不支持 IRQF_ONESHOT 与 IRQF_SHARED 同时使用。
4.4 中断标志位组合策略
不同的网卡硬件和中断模式需要不同的标志位组合。以下 SVG 图展示了三种典型场景下的标志位选择流程:
4.5 典型注册代码示例
以下代码展示了在网卡驱动中注册 MSI-X 中断的典型实现:
static int nic_request_irqs(struct nic_adapter *adapter)
{
struct pci_dev *pdev = adapter->pdev;
int i, ret;
for (i = 0; i < adapter->num_queues; i++) {
struct nic_queue *queue = &adapter->queues[i];
int irq = pci_irq_vector(pdev, i);
snprintf(queue->name, sizeof(queue->name),
"%s-q%d", pci_name(pdev), i);
ret = request_irq(irq, nic_irq_handler,
IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
queue->name, queue);
if (ret) {
dev_err(&pdev->dev,
"Failed to request IRQ %d for queue %d\n",
irq, i);
goto err_free_irqs;
}
queue->irq = irq;
}
return 0;
err_free_irqs:
while (--i >= 0) {
struct nic_queue *queue = &adapter->queues[i];
free_irq(queue->irq, queue);
}
return ret;
}
关键设计要点:
- 每个队列使用独立的
request_irq()调用,对应独立的 MSI-X 中断向量 dev参数传入queue结构体指针,处理函数可通过该指针直接访问队列上下文- 错误处理采用 goto 模式,确保部分注册成功时能正确回滚已注册的中断
4.6 中断处理函数的返回值
中断处理函数必须返回 irqreturn_t 类型,内核根据返回值判断中断处理状态:
| 返回值 | 宏定义 | 含义 |
|---|---|---|
IRQ_NONE |
0 |
中断不是本设备产生的,共享中断时用于告知内核继续尝试其他处理函数 |
IRQ_HANDLED |
1 |
中断已被正确处理,内核停止在该中断线上继续查找处理函数 |
IRQ_WAKE_THREAD |
2 |
中断处理需要唤醒线程化处理函数,常用于将耗时操作推迟到进程上下文 |
NAPI 场景下的返回值:使用 NAPI 的网卡驱动通常在中断处理函数中返回
IRQ_HANDLED,因为 NAPI 调度机制已经接管了后续的数据包处理。如果中断处理函数检测到中断并非来自本设备(共享中断场景),必须返回 IRQ_NONE 以避免内核误判。
4.7 中断注册的调试与验证
注册完成后,可以通过以下方式验证中断是否正常工作:
- /proc/interrupts:查看中断号、CPU 亲和性、中断触发次数及注册的设备名称
- /proc/irq/{irq}/smp_affinity:设置中断的 CPU 亲和性,实现负载均衡
- dmesg:检查驱动加载时的中断注册日志,确认无错误信息
以下是一个典型的 /proc/interrupts 输出片段:
CPU0 CPU1 CPU2 CPU3
32: 12 0 0 0 IR-PCI-MSI 512000-edge eth0-q0
33: 0 15 0 0 IR-PCI-MSI 512001-edge eth0-q1
34: 0 0 18 0 IR-PCI-MSI 512002-edge eth0-q2
35: 0 0 0 22 IR-PCI-MSI 512003-edge eth0-q3
从输出可以看出,4 个 MSI-X 中断分别绑定到不同的 CPU 核心,实现了中断负载均衡,这是高性能网卡驱动的典型配置。