2. 中断处理流程:CPU中断响应、中断上下文切换、中断处理函数执行

硬中断(Hard IRQ)的处理并非瞬时完成,而是由CPU按照严格的硬件-软件协同协议分阶段执行。理解这一流程是进行亲缘性调优的前提——只有知道中断在哪一刻被分发、哪一刻被阻塞、哪一刻完成,才能精准地将中断绑定到最合适的CPU核心上。

2.1 中断处理三阶段总览

一次完整的硬中断处理可拆解为三个阶段:CPU中断响应(硬件自动完成)、中断上下文切换(保存现场)、中断处理函数执行(ISR运行)。下表对比了各阶段的核心特征:

阶段 触发者 执行环境 是否可被其他中断抢占 耗时
CPU中断响应 硬件(中断控制器+CPU微架构) CPU微码/硬件状态机 否(硬件自动屏蔽同级及更低级中断) 纳秒级
中断上下文切换 内核entry代码(汇编) 内核栈(中断栈) 部分可(视中断优先级和CPU标志位) 微秒级
中断处理函数执行 内核C代码(ISR) 中断上下文(不可睡眠) 可被更高优先级中断抢占 微秒~毫秒级
调优启示:亲缘性调优主要影响的是第一阶段——中断控制器将中断信号路由到哪个CPU。但后两个阶段的效率(尤其是上下文切换时的TLB/缓存污染)决定了绑定后是否真正受益。

2.2 阶段一:CPU中断响应(硬件自动完成)

当网卡收到数据包后,通过PCIe总线向中断控制器(如x86的APIC或ARM的GIC)发送中断消息。中断控制器根据预先配置的中断亲缘性掩码(IRQ affinity mask),将中断信号仅发送到掩码中指定的CPU核心。该CPU在完成当前指令后,立即执行以下硬件操作:

  1. 保存关键寄存器:CPU自动将当前指令指针(RIP)、代码段(CS)、标志寄存器(RFLAGS)压入内核栈。
  2. 切换栈指针:从用户栈或内核进程栈切换到中断栈(Interrupt Stack),防止中断嵌套导致栈溢出。
  3. 屏蔽同级中断:硬件自动设置CPU的IF(Interrupt Flag)或PIC的IMR(Interrupt Mask Register),阻止同优先级或更低优先级的中断再次触发。
  4. 跳转到中断向量表:根据中断号查找IDT(Interrupt Descriptor Table),跳转到对应的内核入口函数。
关键指标:从网卡发出中断信号到CPU开始执行第一条内核指令,通常耗时 50~200ns。这个延迟受CPU频率、缓存命中率和中断控制器路由速度影响。
CPU中断响应硬件流程(阶段一) 网卡(设备) 中断控制器(APIC/GIC) CPU核心 中断栈 IDT入口函数 亲缘性掩码 决定中断路由 到哪个CPU

2.3 阶段二:中断上下文切换(保存现场)

CPU跳转到IDT入口后,执行内核汇编代码(如x86的entry_64.S中的common_interrupt)。这一阶段的核心任务是保存被中断任务的完整上下文,以便ISR执行完毕后能无缝恢复原任务。

  • 保存通用寄存器:将rax、rbx、rcx、rdx、rsi、rdi、r8~r15等通用寄存器压入中断栈。
  • 保存段寄存器和标志位:包括cs、ds、es、fs、gs以及rflags。
  • 保存中断号:将中断向量号作为参数传递给后续的C函数。
  • 切换页表(可选):如果中断发生在用户态,需要切换到内核页表;如果中断发生在内核态且使用同一组页表,则跳过此步以节省开销。
  • 调用do_IRQ():最终调用C语言编写的通用中断分发函数do_IRQ()或handle_irq()。
性能陷阱:上下文切换阶段会污染CPU的L1指令缓存和数据缓存。如果频繁将中断绑定到某个CPU,该CPU的缓存命中率可能因频繁的上下文切换而下降。实测表明,每10万次中断/秒的上下文切换,会导致该CPU的L1缓存命中率下降约2~5%。
中断上下文切换:栈操作(阶段二) 中断栈(高地址→低地址) RIP(返回地址) CS / RFLAGS 通用寄存器(rax~r15) 中断号(vector) do_IRQ() 参数区 切换关键动作 1. 保存RIP/CS/RFLAGS 2. 保存通用寄存器 3. 传递中断向量号 4. 切换页表(用户态→内核态)

2.4 阶段三:中断处理函数执行(ISR运行)

do_IRQ()根据中断号查找全局中断描述符表(irq_desc),调用注册在该中断线上的中断处理函数(ISR)。对于网卡多队列场景,每个队列通常注册独立的ISR,分别处理对应队列的数据包。

ISR的执行环境具有以下约束:

  • 不可睡眠:ISR运行在中断上下文中,不能调用可能导致睡眠的函数(如kmalloc(GFP_KERNEL)、mutex_lock())。
  • 不可触发缺页中断:ISR访问的内存必须已经映射在页表中,否则会导致双重中断(double fault)。
  • 执行时间应极短:ISR执行期间,同CPU上的其他中断(同优先级或更低优先级)被屏蔽。长时间ISR会导致中断延迟抖动(jitter)。

典型的网卡ISR执行流程如下:

  1. 读取中断状态寄存器:确认中断来源(哪个队列产生了中断)。
  2. 禁用该队列中断:防止同一队列在ISR处理期间再次触发中断(避免活锁)。
  3. 调度NAPI软中断:将数据包处理任务交给net_rx_action()软中断,ISR本身只做最轻量的通知。
  4. 写中断结束寄存器(EOI):通知中断控制器当前中断已处理完毕,允许后续中断到达。
  5. 返回:执行iretq指令,恢复被中断任务的上下文。
调优实战要点:在亲缘性调优中,我们通常将同一网卡队列的硬中断ISR和NAPI软中断绑定到同一个CPU核心,以最大化L1/L2缓存的热度。如果硬中断和软中断分散在不同CPU上,会导致缓存行 bouncing,吞吐量下降10~30%。
完整中断处理三阶段流程(含亲缘性影响点) 阶段一:CPU中断响应 硬件自动完成 保存RIP/CS/RFLAGS 切换中断栈 屏蔽同级中断 阶段二:中断上下文切换 内核汇编入口 保存通用寄存器 传递中断向量号 调用do_IRQ() 阶段三:ISR执行 C语言中断处理函数 读取中断状态寄存器 调度NAPI软中断 写EOI并返回 亲缘性调优在各阶段的影响点 阶段一 中断控制器根据 affinity mask路由 到指定CPU 阶段二 上下文切换污染 L1缓存/TLB 绑定可减少迁移 阶段三 ISR+NAPI绑定 到同一CPU 最大化缓存热度

2.5 中断处理流程中的关键性能指标

在调优实践中,需要关注以下指标来评估中断处理流程的健康度:

指标 测量位置 健康值 异常值(需调优)
中断响应延迟(IRQ latency) 从设备发出中断到CPU开始执行ISR < 1μs > 5μs(可能中断风暴或亲缘性错误)
ISR执行时间 ISR函数开始到返回 < 10μs > 50μs(ISR中做了过多工作)
中断栈使用率 内核统计(/proc/interrupts 间接观察) < 30% > 70%(可能栈溢出风险)
同一CPU上的中断频率 /proc/interrupts 按CPU列统计 各CPU均衡或按设计分布 单个CPU中断率超过其他CPU 3倍以上
实战检查命令:使用 cat /proc/interrupts | grep eth0 查看网卡各队列的中断分布;使用 cat /proc/softirqs | grep NET_RX 查看软中断分布。如果硬中断和软中断不在同一CPU上,说明需要调整RPS(Receive Packet Steering)或亲缘性设置。