1. 中断基础概念

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即处理的事件。在多核CPU与网卡队列的上下文中,中断是连接网络设备与CPU核心的关键机制,直接影响数据包处理效率和系统吞吐量。

1.1 什么是中断

中断是一种让CPU暂停当前执行的任务,转而处理更高优先级事件的机制。当网卡接收到数据包时,它会通过硬件中断通知CPU,CPU暂停当前工作,执行中断服务程序(ISR)来处理数据包。

核心要点:中断避免了CPU轮询设备状态带来的CPU资源浪费,是高性能网络处理的基础。
类比理解:中断就像你在写报告时,电话铃响了(中断信号),你暂停写报告(保存上下文),接听电话(执行ISR),挂断后继续写报告(恢复上下文)。

1.2 中断向量

中断向量(Interrupt Vector)是中断号与中断服务程序入口地址之间的映射表。每个中断源被分配一个唯一的中断向量号,CPU收到中断信号后,通过中断向量表(IVT)快速定位对应的处理函数。

概念 说明 示例(x86架构)
中断向量号 唯一标识中断源的数字编号 网卡中断通常分配 16~23 号
中断向量表 存储中断号与处理函数地址的映射表 IDT(中断描述符表)
IRQ 硬件中断请求线编号 eth0 对应 IRQ 77
MSI-X 消息信号中断扩展,支持多队列 每个网卡队列独立中断向量
注意:在多队列网卡场景中,每个队列可以拥有独立的中断向量,这是实现中断分布到不同CPU核心的前提条件。

1.3 中断处理流程:上半部与下半部

Linux内核将中断处理分为两个阶段,以平衡响应速度和系统吞吐量:

  1. 上半部(Top Half):在中断上下文中立即执行,必须快速完成,通常只做最紧急的工作(如清除中断标志、拷贝寄存器状态)。
  2. 下半部(Bottom Half):在更宽松的上下文中延迟执行,处理耗时操作(如网络协议栈处理、数据包分发)。
设计原则:上半部越快越好,下半部越少越好。上半部执行期间CPU禁止响应同类型中断,过长会导致中断丢失。
中断处理流程:上半部与下半部 硬件中断源(网卡) CPU 接收中断信号 上半部(Top Half) 清除中断标志 · 保存寄存器 · 调度下半部 下半部(Bottom Half) 软中断 · Tasklet · 工作队列 · 协议栈处理 上半部在中断上下文中执行,下半部在软中断或进程上下文中执行,支持延迟处理和负载均衡

1.4 上半部与下半部的实现机制

特性 上半部(Top Half) 下半部(Bottom Half)
执行上下文 中断上下文(不可睡眠) 软中断/进程上下文(可睡眠)
执行时机 立即执行 延迟执行(调度后)
中断屏蔽 同类型中断被屏蔽 不屏蔽中断
典型操作 ACK中断、拷贝寄存器 网络协议栈、数据分发
耗时要求 微秒级完成 毫秒级完成
内核实现 request_irq() 注册 softirq / tasklet / workqueue
实践要点:在网卡驱动中,上半部通常只做最小必要操作(如读取状态寄存器、清除中断标志),然后将数据包放入队列并触发软中断(NET_RX_SOFTIRQ),由下半部完成协议栈处理。

1.5 中断与多核CPU的关联

在多核CPU系统中,中断分布(Interrupt Affinity)决定了哪个CPU核心处理哪个中断。通过合理配置中断亲和性,可以实现:

  • 负载均衡:将不同网卡队列的中断分布到不同CPU核心,避免单核过载。
  • 缓存局部性:让处理数据包的CPU核心与数据所在的内存节点(NUMA)对齐,减少跨节点访问延迟。
  • 隔离关键任务:将中断绑定到特定核心,为实时任务预留专用核心。
多核CPU中断分布架构 多队列网卡(NIC) 队列 0(IRQ 77) 队列 1(IRQ 78) 队列 2(IRQ 79) CPU 0 CPU 1 CPU 2 软中断处理 软中断处理 软中断处理 通过设置 /proc/irq/{IRQ}/smp_affinity 将不同队列中断绑定到不同CPU核心,实现并行处理

1.6 中断相关内核代码示例

以下代码展示了网卡驱动中注册中断处理函数的基本模式:

/* 网卡驱动中断注册示例 */
static irqreturn_t my_nic_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    struct my_nic_priv *priv = netdev_priv(dev);

    /* 上半部:读取状态寄存器,清除中断标志 */
    u32 status = readl(priv->ioaddr + STATUS_REG);
    writel(status, priv->ioaddr + STATUS_REG);

    if (status & RX_INT) {
        /* 调度下半部处理接收数据 */
        napi_schedule(&priv->napi);
    }

    return IRQ_HANDLED;
}

/* 驱动初始化时注册中断 */
static int my_nic_probe(struct pci_dev *pdev, ...)
{
    /* 为每个队列注册独立中断 */
    for (int i = 0; i < priv->num_queues; i++) {
        int irq = pci_irq_vector(pdev, i);
        snprintf(priv->irq_name[i], IFNAMSIZ, "%s-q%d",
                 netdev->name, i);
        request_irq(irq, my_nic_interrupt, 0,
                    priv->irq_name[i], netdev);
    }
    return 0;
}
关键点:每个网卡队列拥有独立的中断号(通过MSI-X实现),这是后续进行中断分布绑定的硬件基础。上半部调用 napi_schedule() 触发软中断,将实际数据包处理推迟到下半部。

1.7 本章小结

概念 核心要点 与多核分布的关系
中断 硬件异步通知CPU的事件机制 网卡通过中断通知CPU处理数据包
中断向量 中断号到处理函数的映射表 每个队列独立中断号是实现分布的前提
上半部 快速响应,屏蔽同类型中断 必须极短,避免中断丢失
下半部 延迟处理,可被中断抢占 软中断可在不同CPU上并行执行