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内核将中断处理分为两个阶段,以平衡响应速度和系统吞吐量:
- 上半部(Top Half):在中断上下文中立即执行,必须快速完成,通常只做最紧急的工作(如清除中断标志、拷贝寄存器状态)。
- 下半部(Bottom Half):在更宽松的上下文中延迟执行,处理耗时操作(如网络协议栈处理、数据包分发)。
设计原则:上半部越快越好,下半部越少越好。上半部执行期间CPU禁止响应同类型中断,过长会导致中断丢失。
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)对齐,减少跨节点访问延迟。
- 隔离关键任务:将中断绑定到特定核心,为实时任务预留专用核心。
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上并行执行 |