1.Linux内核收包路径
网卡DMA将数据写入RX描述符环 → 网卡触发硬中断 → 硬中断处理函数(top half)禁用网卡中断、调用napi_schedule() → 软中断NET_RX_SOFTIRQ执行NAPI poll函数(bottom half)→ 驱动poll函数调用napi_gro_receive() → 经过netfilter各hook点(PREROUTING → 路由决策 → FORWARD/INPUT)→ 协议栈处理(IP层 → TCP/UDP层)→ 数据放入socket接收缓冲区 → 用户态read()/recvfrom()取数据。
2.中断的top half和bottom half?为什么要分
Top half(硬中断处理):在中断上下文中执行,必须尽快完成(不能睡眠、不能阻塞),通常只做最小工作------确认中断、禁用该中断、调度bottom half。
Bottom half(软中断/tasklet/workqueue):在稍后的时机执行,可以做更复杂的处理。分离的原因:硬中断期间CPU不响应其他中断,如果处理时间过长会导致中断延迟和丢包。NAPI就是典型的bottom half机制------硬中断只调度NAPI,实际收包在softirq上下文中批量完成。
总的来说,""为什么要分Top/Bottom Half"?"
"主要是为了解决中断延迟和吞吐量的矛盾。
(1)Top Half 在硬中断上下文,必须原子执行、不能睡眠,所以它只做最小工作:确认中断、关闭中断线、调度软中断。目的是尽快释放 CPU,避免阻塞其他硬件。
(2)Bottom Half(NAPI Poll)在软中断上下文,它的核心价值是批量处理(Batching)。在关闭硬中断的情况下,一次性把 Ring Buffer 里的包全收完,摊薄了中断开销。
(3)收完再开中断,形成了一个'关中断 -> 批量收 -> 开中断'的闭环。这样既保证了低延迟(有包立刻响应),又保证了高吞吐(不频繁进出中断上下文)。"
Q: Bottom Half 执行时,硬中断是开着的还是关着的?
A: 关着的。
这也是为什么 NAPI 能防止丢包的原因。在 Poll 期间,网卡的中断线是 Mask 的,网卡收到新包只会放在 Ring 里,不会发信号。等 Poll 结束 Unmask 后,如果 Ring 里还有包,网卡会再发一次中断,触发新一轮的 Top Half。这避免了"一边收包一边被中断打断"的竞态问题。
3.中断节流
NAPI 有个核心机制叫"中断节流"(Interrupt Coalescing),它和 Top/Bottom Half 配合决定了网卡的性能表现。
中断节流(Interrupt Coalescing),在 Linux 系统中也常被称为中断合并或中断调节(Interrupt Moderation)。
如果说 NAPI 的 Top/Bottom Half 机制是软件层面的"攒一波再处理",那么中断节流就是硬件(网卡)层面的"攒一波再敲门"。
为什么需要中断节流?(硬件视角的痛点)
在没有中断节流的纯中断模式下,网卡每收到一个数据包,就会立刻向 CPU 发送一个硬中断信号。
痛点: 现代万兆网卡在满载时,每秒可能产生数百万个数据包。如果每个包都触发一次中断,CPU 需要频繁地"保存现场 -> 处理中断 -> 恢复现场"。这种上下文切换(Context Switch)带来的开销(CPU 软中断占用率)极高,甚至可能导致 CPU 100% 忙于处理中断,反而没时间去处理业务数据(即"中断风暴")。
中断节流是如何工作的?
中断节流由网卡硬件支持。启用后,网卡不会为每个收到的数据包立即触发中断,而是将多个中断请求"打包"。它通常基于以下两个条件之一来触发中断:
- 定时器超时(Time-based): 比如网卡内部设定了一个 200 微秒的定时器。收到第一个包时开始计时,如果 200 微秒内又来了包,就重置计时器或继续等待;直到 200 微秒到了,才把这段时间内积攒的所有包一次性通过中断通知 CPU。
- 包数量阈值(Frame-based): 比如设定阈值为 5 个包。网卡收到前 4 个包时不吭声,等第 5 个包到达时,再触发一次中断。
中断节流与NAPI的配合
中断节流和 NAPI 是完美互补的:
- 中断节流(硬件): 减少了触发 Top Half 的次数。比如 100 个包原本要触发 100 次硬中断,现在只触发 1 次。
- NAPI(软件): 提高了单次 Bottom Half 的效率。这 1 次硬中断触发后,NAPI 会在 Bottom Half 中一次性把这 100 个包全部 Poll 出来处理。
延迟与吞吐的权衡(Trade-off)
中断节流本质上是在降低 CPU 开销和增加网络延迟之间做权衡。
- 优点(高吞吐,低 CPU): 极大地降低了 CPU 的中断处理负担,使得系统有充足的算力去处理海量数据(如大文件传输、视频流),提升了整体吞吐量。
- 缺点(增加延迟,吞吐抖动): 数据包到达网卡后,必须等待定时器超时或攒够数量才能被 CPU 处理。这会引入微秒级甚至毫秒级的额外延迟。如果阈值设置不当,可能导致 TCP 流量变得"突发(Bursty)"或"成块(Clumpy)",影响实时性。
总结
"中断节流是网卡硬件层面的优化,它通过按时间或按包数量合并中断,大幅降低了 CPU 的上下文切换开销,提升了高并发下的吞吐量。但它会引入一定的网络延迟。现代网卡通常采用自适应中断节流(Adaptive IM),根据流量特征(大包/小包)动态调节阈值,从而在吞吐量和延迟之间取得最佳平衡。它与软件层面的 NAPI 机制配合,构成了现代高性能网络收包的基础。"
4.既然 Bottom Half 期间硬中断也是关的,那跟直接在 Top Half 里干完所有活有什么区别?
硬中断上下文 vs 软中断上下文
这是理解这个问题的关键:
Top Half(硬中断上下文)执行时:
CPU 屏蔽了所有同级别及更低级别的可屏蔽中断
这意味着不仅网卡的中断被屏蔽,磁盘I/O中断、其他网卡中断、定时器中断、键盘中断......全部都被屏蔽
整个 CPU 在这段时间内对外界硬件"完全聋了"
Bottom Half(软中断上下文)执行时:
硬中断是重新打开的
其他硬件设备(磁盘、其他网卡、定时器)可以正常触发中断并被 CPU 响应
只是屏蔽了同优先级的软中断,防止软中断嵌套
| 维度 | 硬中断上下文(Top Half) | 软中断上下文(Bottom Half) |
|---|---|---|
| 硬中断状态 | 全部屏蔽 | 已打开 |
| 其他硬件能否中断 CPU | 不能 | 能 |
| 能否睡眠/阻塞 | 不能 | 不能(但比硬中断宽容) |
| 执行时间要求 | 极短(微秒级) | 相对宽松(可以批量处理) |
| 典型耗时 | 1~2 μs | 几十~几百 μs |