4. 中断处理基础:Linux中断子系统概述、中断号与中断描述符、request_irq()与free_irq()接口
中断处理是网络设备驱动响应硬件事件的基石。当网络接口卡(NIC)接收到数据帧、完成DMA传输或发生链路状态变化时,通过中断线向CPU发出信号,驱动的中断服务程序(ISR)负责快速响应并调度后续处理。本章深入剖析Linux中断子系统的核心概念、数据结构与编程接口,为后续实现高效的中断与DMA协同机制奠定基础。
4.1 Linux中断子系统架构概述
Linux内核的中断子系统采用分层设计,将硬件相关的中断控制器管理与设备驱动通用的中断注册、调度机制解耦。其核心层次从上到下依次为:
- 通用中断层(Generic IRQ Layer):提供与架构无关的API,如
request_irq()、free_irq()、enable_irq()等,管理中断描述符与中断流处理。 - 中断控制器驱动层(IRQ Controller Driver):封装GIC(通用中断控制器)、APIC、IO-APIC等具体硬件控制器的操作,实现中断的屏蔽、使能、路由与应答。
- CPU中断入口层(CPU Interrupt Entry):处理CPU架构相关的异常向量、中断栈切换与现场保存/恢复。
💡 设计要点:通用中断层是驱动开发者最常接触的接口层。它屏蔽了底层中断控制器的差异,使得网络设备驱动只需调用
request_irq()即可注册中断处理函数,无需关心中断是来自GIC还是APIC。
4.2 中断号与中断描述符
4.2.1 中断号(IRQ Number)
中断号是内核用于唯一标识一个中断源的整型数字。在设备驱动中,中断号通常由设备树(Device Tree)、ACPI表或PCI配置空间动态分配。对于网络设备,中断号可通过pci_irq_vector()或platform_get_irq()获取。
| 中断号来源 | 典型场景 | 获取API |
|---|---|---|
| 设备树(DT) | 嵌入式平台(ARM/ RISC-V) | platform_get_irq(pdev, 0) |
| PCI配置空间 | x86/ARM64服务器网卡 | pci_irq_vector(pdev, vec_idx) |
| ACPI表 | x86/ARM64 ACPI系统 | acpi_register_gsi() |
| 静态定义 | 旧式ISA设备 | 头文件宏定义(如IRQ_EINT0) |
4.2.2 中断描述符(irq_desc)
内核使用struct irq_desc结构体管理每个中断号的状态、处理函数链表、流处理函数与中断控制器操作。该结构体是中断子系统的核心数据对象,定义在<linux/irqdesc.h>中。
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq; /* 中断流处理函数 */
struct irqaction *action; /* 中断动作链表 */
unsigned int status_use_accessors;
struct list_head irq_pending_list; /* 待处理中断列表 */
struct proc_dir_entry *dir;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
4.3 request_irq() 与 free_irq() 接口详解
4.3.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 |
要注册的中断号 |
handler |
irq_handler_t |
中断处理函数指针,原型为 irqreturn_t (*handler)(int irq, void *dev_id) |
flags |
unsigned long |
中断标志位,控制触发方式、共享属性等 |
name |
const char * |
中断名称,显示在 /proc/interrupts 中 |
dev |
void * |
传递给处理函数的设备标识,共享中断时用于区分设备 |
4.3.2 常用中断标志位(flags)
| 标志位 | 值 | 用途 |
|---|---|---|
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 |
IRQF_SHARED |
0x00000080 | 允许共享中断线 |
IRQF_ONESHOT |
0x00002000 | 中断处理期间硬件中断线保持屏蔽,常用于线程化中断 |
IRQF_NO_SUSPEND |
0x00004000 | 系统休眠时不关闭该中断 |
4.3.3 free_irq() 函数
free_irq()用于注销已注册的中断处理函数,在驱动卸载或设备移除时调用。
void free_irq(unsigned int irq, void *dev_id);
⚠️ 重要注意事项:
free_irq()会等待所有正在执行的中断处理函数完成,因此不能在中断上下文或持有自旋锁时调用。- 对于共享中断,
dev_id必须与request_irq()时传入的值一致,否则无法正确注销。 - 调用
free_irq()后,该中断号上的所有处理函数将被移除,中断线可能被禁用。
4.4 网络设备驱动中的中断注册实践
以下是一个典型的网络设备驱动中断注册代码片段,展示了从获取中断号到注册处理函数的完整流程:
#include <linux/interrupt.h>
#include <linux/pci.h>
struct net_device_priv {
struct net_device *netdev;
struct napi_struct napi;
void __iomem *ioaddr;
int irq;
};
static irqreturn_t netdev_irq_handler(int irq, void *dev_id)
{
struct net_device_priv *priv = dev_id;
/* 读取中断状态寄存器,判断中断源 */
u32 status = ioread32(priv->ioaddr + REG_INTR_STATUS);
if (!(status & INTR_MASK))
return IRQ_NONE; /* 非本设备中断 */
/* 清除中断标志 */
iowrite32(INTR_MASK, priv->ioaddr + REG_INTR_CLEAR);
/* 调度NAPI处理接收数据 */
if (likely(napi_schedule_prep(&priv->napi))) {
__napi_schedule(&priv->napi);
}
return IRQ_HANDLED;
}
static int netdev_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct net_device_priv *priv;
int ret;
/* ... 分配net_device、映射IO内存、初始化NAPI ... */
/* 获取PCI设备的中断号 */
priv->irq = pci_irq_vector(pdev, 0);
if (priv->irq < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return priv->irq;
}
/* 注册中断处理函数 */
ret = request_irq(priv->irq, netdev_irq_handler,
IRQF_SHARED | IRQF_TRIGGER_HIGH,
"mynetdev", priv);
if (ret) {
dev_err(&pdev->dev, "failed to request IRQ %d: %d\n",
priv->irq, ret);
return ret;
}
return 0;
}
static void netdev_remove(struct pci_dev *pdev)
{
struct net_device_priv *priv = netdev_priv(pci_get_drvdata(pdev));
/* 注销中断处理函数 */
free_irq(priv->irq, priv);
/* ... 释放其他资源 ... */
}
4.5 中断处理函数的返回值与上下文约束
4.5.1 返回值语义
| 返回值 | 枚举值 | 含义 |
|---|---|---|
IRQ_NONE |
irqreturn_t (0) |
中断不是本设备产生的,内核继续遍历共享中断链 |
IRQ_HANDLED |
irqreturn_t (1) |
中断已被正确处理,内核停止遍历 |
IRQ_WAKE_THREAD |
irqreturn_t (2) |
唤醒中断线程化处理函数(配合 request_threaded_irq()) |
4.5.2 中断上下文约束
🔧 中断处理函数执行在中断上下文中,必须遵守以下规则:
- 不能调用可能导致睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock()、copy_from_user())。 - 应尽量短小精悍,仅完成最必要的硬件操作(如读取状态、清除中断标志),将耗时处理推迟到 tasklet、workqueue 或 NAPI 中。
- 访问共享数据时需使用
spin_lock()而非mutex_lock()。 - 对于网络设备,推荐使用 NAPI 机制在软中断上下文中轮询接收数据,减少中断频率。
4.6 本章小结
本章系统梳理了Linux中断子系统的分层架构,明确了中断号与中断描述符的映射关系,并深入讲解了request_irq()与free_irq()的接口规范与使用要点。网络设备驱动开发者必须准确理解中断上下文约束,合理选择中断标志位,并严格遵循注册/注销的生命周期管理。这些基础知识是后续实现中断与DMA高效协同的必备前提。