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架构相关的异常向量、中断栈切换与现场保存/恢复。
Linux 中断子系统分层架构 设备驱动层 (request_irq / free_irq) 通用中断层 (Generic IRQ Layer) 中断描述符管理 · 中断流处理 · 中断线程化 中断控制器驱动层 (IRQ Chip Driver) GIC / APIC / IO-APIC 封装 · 中断路由与应答 CPU 中断入口层 (异常向量/栈切换) 关键设计原则 分层解耦 架构无关API 硬件抽象
💡 设计要点:通用中断层是驱动开发者最常接触的接口层。它屏蔽了底层中断控制器的差异,使得网络设备驱动只需调用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;
中断描述符与中断号映射关系 irq_desc[0] irq_desc[1] irq_desc[2] irq_desc[3] irq_desc[...] struct irq_desc irq_data.irq = 2 handle_irq = handle_level_irq action → irqaction链表 status = IRQ_ACTIVE kstat_irqs → 统计计数 parent_irq = 0 name = "eth0-tx" owner = THIS_MODULE 中断号 → 描述符查找 irq_to_desc(irq) 返回 struct irq_desc * 时间复杂度 O(1)

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);

    /* ... 释放其他资源 ... */
}
网络设备中断注册与处理流程 ① 获取中断号 pci_irq_vector() ② request_irq() 注册处理函数 ③ 硬件中断触发 NIC 产生中断信号 ④ ISR 执行 读取状态 → 调度NAPI 关键检查点 ✓ 中断号有效性检查 ✓ flags 与硬件匹配 ✓ dev_id 唯一标识 ✓ ISR 返回 IRQ_NONE 或 IRQ_HANDLED ✓ 共享中断时检查 中断状态寄存器 ✓ 卸载时 free_irq()

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高效协同的必备前提。