09 NAPI 收包轮询机制与实现详解
1. 概述
NAPI(New API)是 Linux 网卡 收包路径 的核心抽象:用 短硬中断 + 软中断批量轮询 替代“每包一进中断”,在高 PPS 场景下降低 CPU 开销、提高吞吐。
| 项目 | 说明 |
|---|---|
| 头文件 | include/linux/netdevice.h |
| 实现 | net/core/dev.c(__napi_schedule、net_rx_action、netif_napi_add_weight) |
| 与 socket 关系 | NAPI 在 L2/L3 入口;net/socket.c 的 recvmsg 在更上层读已入队的 skb |
| 驱动职责 | 实现 poll(),在 RX 中断里调用 napi_schedule() |
2. 要解决的问题
2.1 纯中断收包的问题
1 | 每包 → 硬中断 → 中断上下文收包、上交协议栈 |
- 中断次数 ≈ PPS,千兆/万兆时 CPU 耗在中断与上下文切换;
- 中断里不宜做重逻辑,却又必须及时处理;
- 多队列、多 CPU 时难以统一调度。
2.2 NAPI 的思路
1 | 硬中断:关中断/清状态 → napi_schedule() |
本质:中断 触发,收包 轮询,并由内核 统一限流。
3. 核心数据结构
定义于 include/linux/netdevice.h:
1 | struct napi_struct { |
3.1 状态位(节选)
| 状态 | 含义 |
|---|---|
NAPI_STATE_SCHED |
已调度,poll 进行中或待执行 |
NAPI_STATE_MISSED |
schedule 时已在跑,需再次调度 |
NAPI_STATE_DISABLE |
禁用中,napi_schedule 无效 |
NAPI_STATE_THREADED |
poll 在内核线程中执行 |
NAPI_STATE_IN_BUSY_POLL |
用户态 busy poll 占用 |
3.2 驱动回调 poll
1 | int (*poll)(struct napi_struct *napi, int budget); |
| 参数/返回值 | 约定 |
|---|---|
budget |
本次最多处理包数(通常 = napi->weight) |
| 返回值 | 实际处理包数;必须 ≤ budget |
< budget |
通常表示本轮收完 → 调用 napi_complete() |
== budget |
可能还有包 → 框架 repoll |
默认权重:NAPI_POLL_WEIGHT = 64。
4. 驱动侧 API
4.1 注册与生命周期
| API | 作用 |
|---|---|
netif_napi_add(dev, napi, poll) |
注册 NAPI,weight=64 |
netif_napi_add_weight(dev, napi, poll, weight) |
指定 weight |
netif_napi_add_tx(dev, napi, poll) |
仅 TX 队列用 NAPI |
napi_enable(napi) |
使能,允许 schedule |
napi_disable(napi) |
禁用并等待 poll 结束 |
netif_napi_del(napi) |
从设备链表移除 |
netif_napi_add_weight() 实现要点(net/core/dev.c):
- 设置
napi->poll、napi->weight、napi->dev; - 加入
dev->napi_list与napi_hash(busy poll); - 可选创建 NAPI 内核线程(
dev->threaded)。
4.2 调度与完成
| API | 典型调用点 |
|---|---|
napi_schedule(napi) |
RX 硬中断下半部 |
napi_schedule_irqoff(napi) |
已 local_irq_disable() 时 |
napi_complete(napi) |
poll 返回 work < budget |
napi_complete_done(napi, work) |
带 GRO flush 等 |
napi_reschedule(napi) |
complete 后仍有包 |
napi_schedule() 逻辑:
1 | if (napi_schedule_prep(n)) /* CAS 置 SCHED,保证单实例 */ |
4.3 收包辅助 API
| API | 作用 |
|---|---|
napi_alloc_skb(napi, len) |
NAPI 上下文分配 skb |
napi_gro_receive(napi, skb) |
收包并尝试 GRO 聚合 |
napi_consume_skb(skb, budget) |
释放 skb(知悉 NAPI 预算) |
napi_get_frags() / napi_frags_* |
页片段收包模式 |
5. 内核调度路径
5.1 流程图
5.2 net_rx_action(软中断)
net/core/dev.c 中 NET_RX_SOFTIRQ 处理函数:
- 将当前 CPU
softnet_data.poll_list移到本地链表; - 循环对每个
napi_struct调用napi_poll()→__napi_poll()→napi->poll(); - 累计消耗
netdev_budget(默认 300)与netdev_budget_usecs时间片; - 预算用尽则
time_squeeze++,剩余 NAPI 留待下次软中断; - 若 poll_list 非空,再次
raise_softirq(NET_RX_SOFTIRQ)。
5.3 __napi_poll 与 repoll
1 | work = n->poll(n, weight); |
6. 驱动模板代码
1 | struct my_netdev_priv { |
注意:
- 不要在
poll里睡眠; work > budget会触发内核告警;- 多 RX 队列:每队列一个
napi_struct+ 独立poll。
7. 系统级调优参数
| 参数 | 默认(约) | 路径/变量 | 含义 |
|---|---|---|---|
NAPI_POLL_WEIGHT |
64 | 宏 | 单 NAPI 单次 poll 上限 |
dev_rx_weight |
64 | sysctl net.core.dev_weight |
backlog NAPI 权重 |
netdev_budget |
300 | sysctl net.core.netdev_budget |
一次软中断最多“消耗”的包预算 |
netdev_budget_usecs |
~2ms@HZ=100 | sysctl net.core.netdev_budget_usecs |
软中断时间上限 |
netdev_max_backlog |
1000 | sysctl net.core.netdev_max_backlog |
per-CPU 输入队列长度 |
7.1 观测
1 | cat /proc/net/softnet_stat |
time_squeeze 持续偏高 → 可适当增大 netdev_budget 或优化驱动/CPU 亲和。
8. 扩展机制
| 机制 | 说明 |
|---|---|
| GRO | napi_gro_receive 在 poll 路径聚合小包 |
| RPS | 跨 CPU 分发软中断(process_backlog + IPI) |
| busy poll | 用户态通过 napi_id 轮询,低延迟场景 |
| NAPI threaded | dev_set_threaded(),poll 在 kthread |
| XDP | 可在驱动更早 drop/redirect,与 NAPI 正交 |
协议栈默认 per-CPU backlog 也用一个 NAPI:process_backlog()(非网卡驱动),处理未启用 RPS 时的输入队列。
9. RK3588 关联
| 点 | 说明 |
|---|---|
| 实现位置 | NAPI 框架在 net/core;stmmac/dwmac、r8169 等在 drivers/net/ethernet/ |
| 板载 GMAC | DT 中 gmac + stmmac 驱动,probe 时 netif_napi_add,RX IRQ → napi_schedule |
| WiFi | SDIO/USB 无线驱动同样使用 NAPI;上电由 net/rfkill/rfkill-wlan.c 配合 |
| 与 Thunder Boot | 不改变 NAPI 接口本身 |
调试千兆吞吐时:驱动 poll + netdev_budget + 中断亲和(smp_affinity) 需一并查看。
10. 源码索引
| 内容 | 路径 |
|---|---|
struct napi_struct、内联 API |
include/linux/netdevice.h |
netif_napi_add_weight、__napi_schedule、net_rx_action |
net/core/dev.c |
| sysctl | net/core/sysctl_net_core.c |
napi_alloc_skb |
net/core/skbuff.c / include/linux/skbuff.h |
11. 相关文档
- 01-net-core核心机制与实现详解.md
- 07-数据面路径与drivers分界机制与实现详解.md
- core/rk3588-kernel-6.1-net-core-NAPI接口设计原理分析_zh.md(若存在早期笔记可对照)
12. 小结
| 问题 | 答案 |
|---|---|
| NAPI 是什么? | 网卡收包的 调度框架(不是 socket API) |
| 驱动要做什么? | 实现 poll,中断里 napi_schedule,收完 napi_complete |
| 内核做什么? | 软中断里按 budget 批量调 poll,全局限流 |
| 为何重要? | 高吞吐下 减中断、提效率、统一调度 |
正在加载留言…