2. NAPI数据结构
NAPI 机制的高效运转依赖于精心设计的数据结构。本章深入剖析 struct napi_struct、struct softnet_data、NAPI 状态标志以及 GRO 相关数据结构,为后续驱动开发与性能调优奠定坚实基础。
核心要点:NAPI 数据结构是连接驱动中断处理、轮询机制与网络协议栈的桥梁。理解这些结构体的字段含义与协作关系,是编写高性能网络驱动的关键。
2.1 struct napi_struct:NAPI 实例的核心
struct napi_struct 是 NAPI 机制的核心数据结构,每个注册了 NAPI 的网络设备或虚拟队列都对应一个实例。它管理着轮询状态、回调函数、设备关联以及 GRO 上下文。
struct napi_struct {
/* 链表节点:用于将 napi_struct 挂入 poll_list */
struct list_head poll_list;
/* 引用计数与状态标志 */
unsigned long state;
int weight;
/* 轮询回调函数与私有数据 */
int (*poll)(struct napi_struct *, int);
void *dev_id;
/* GRO 相关字段 */
struct gro_list gro_hash[GRO_HASH_BUCKETS];
struct list_head rx_list; /* 待处理的 GRO 包列表 */
int gro_count;
/* 硬件时间戳与 IRQ 上下文 */
ktime_t timestamp;
struct hrtimer timer;
/* 其他辅助字段 */
struct list_head dev_list;
struct rcu_head rcu;
};
字段解读:
- poll_list:将当前 NAPI 实例挂入 CPU 的
softnet_data->poll_list,由软中断遍历调度。 - state:位掩码标志,控制 NAPI 的调度、禁用、挂起等状态。
- weight:单次轮询最大处理包数,典型值为 64,过大会导致软中断占用过长。
- poll:驱动实现的轮询函数,返回值为实际处理的包数。
- gro_hash:GRO 哈希表,用于合并同流的数据包。
2.2 struct softnet_data:Per-CPU 调度核心
struct softnet_data 是每个 CPU 独立维护的软中断调度数据结构,负责管理 NAPI 实例的轮询队列、输入包队列以及统计信息。它是 NAPI 调度机制的“调度器”。
struct softnet_data {
/* NAPI 轮询链表 */
struct list_head poll_list;
/* 输入包队列(backlog) */
struct sk_buff_head input_pkt_queue;
struct napi_struct backlog;
/* 完成队列(NAPI 完成处理) */
struct sk_buff_head completion_queue;
/* 统计信息 */
unsigned int processed;
unsigned int time_squeeze;
unsigned int cpu_collision;
/* 其他辅助字段 */
int defer_count;
int defer_ipi_scheduled;
struct rcu_head rcu;
};
| 字段 | 类型 | 作用 |
|---|---|---|
| poll_list | struct list_head | 挂载所有待轮询的 napi_struct 实例,由 net_rx_action() 遍历 |
| input_pkt_queue | struct sk_buff_head | 非 NAPI 模式(传统中断)的输入包队列 |
| backlog | struct napi_struct | 非 NAPI 模式使用的伪 NAPI 实例,用于兼容旧驱动 |
| completion_queue | struct sk_buff_head | NAPI 完成队列,存放已处理完毕待释放的 skb |
| processed | unsigned int | 累计处理包数,用于性能统计 |
| time_squeeze | unsigned int | 轮询超时次数,指示预算不足或中断频率过高 |
性能调优关注点:
time_squeeze 计数器持续增长,说明当前 CPU 的 NAPI 轮询预算(weight)不足或中断频率过高,需调整预算或启用多队列 RSS。
2.3 NAPI 状态标志:位掩码控制调度生命周期
napi_struct.state 字段使用位掩码记录 NAPI 实例的当前状态。内核通过原子操作设置/清除这些标志,确保多核环境下的正确性。
enum {
NAPI_STATE_SCHED, /* 0:已调度,等待轮询 */
NAPI_STATE_MISSED, /* 1:调度期间有新中断到达 */
NAPI_STATE_DISABLE, /* 2:被禁用,不可调度 */
NAPI_STATE_NPSVC, /* 3:非抢占式服务中 */
NAPI_STATE_HASHED, /* 4:已加入全局 napi_hash */
NAPI_STATE_NO_BUSY_POLL,/* 5:禁止忙轮询 */
NAPI_STATE_IN_BUSY_POLL,/* 6:正在忙轮询 */
};
| 标志 | 位 | 含义 | 典型操作 |
|---|---|---|---|
| NAPI_STATE_SCHED | 0 | NAPI 实例已加入 poll_list,等待软中断轮询 | napi_schedule() 设置,poll 完成后清除 |
| NAPI_STATE_MISSED | 1 | 轮询期间又有新中断到达,需重新调度 | 中断处理中设置,napi_complete() 检查 |
| NAPI_STATE_DISABLE | 2 | NAPI 被禁用,禁止调度和轮询 | napi_disable() 设置,napi_enable() 清除 |
| NAPI_STATE_NPSVC | 3 | 正在执行非抢占式轮询服务 | net_rx_action() 设置,防止抢占 |
| NAPI_STATE_HASHED | 4 | 已加入全局哈希表,用于查找 | napi_hash_add() / napi_hash_del() |
状态转换关键路径:
- 中断到来 →
napi_schedule()设置 NAPI_STATE_SCHED,将实例加入 poll_list。 - 软中断
net_rx_action()遍历 poll_list,调用 poll 回调。 - 若 poll 返回 weight(预算耗尽),保留 SCHED 标志,下次软中断继续。
- 若 poll 返回 < weight(处理完毕),清除 SCHED 标志,重新使能中断。
- 若 MISSED 被设置,即使处理完毕也重新调度,避免丢包。
2.4 GRO 相关数据结构:高效包合并
GRO(Generic Receive Offload)在 NAPI 轮询过程中将同流的小包合并为大包,减少协议栈处理开销。其核心数据结构包括 gro_list、napi_gro_cb 和 skb_gro_params。
/* GRO 哈希表项 */
struct gro_list {
struct list_head list; /* 同哈希桶的 skb 链表 */
int count; /* 当前桶中 skb 数量 */
};
/* GRO 控制块(skb->cb 中存储) */
struct napi_gro_cb {
union {
struct {
unsigned int same_flow:1; /* 与上一个包同流 */
unsigned int flush:1; /* 强制刷新合并 */
unsigned int free:1; /* 释放原 skb */
unsigned int encapsulation:1;/* 隧道封装 */
unsigned int gro_remcsum:1; /* 远程校验和 */
};
u16 gro_bits;
};
u16 frag0_len; /* 线性数据长度 */
void *frag0; /* 线性数据指针 */
};
/* GRO 匹配参数 */
struct skb_gro_params {
struct net_device *dev; /* 接收设备 */
struct net_device *orig_dev; /* 原始设备(bonding) */
__be16 proto; /* L3 协议类型 */
u16 h_proto; /* 原始协议字段 */
};
| 数据结构 | 存储位置 | 核心作用 |
|---|---|---|
| gro_list | napi_struct.gro_hash[] | 按哈希桶组织待合并的 skb,加速同流查找 |
| napi_gro_cb | skb->cb[48] | 记录每个 skb 的 GRO 状态:是否同流、是否刷新、是否释放 |
| skb_gro_params | skb 扩展字段 | 存储用于流匹配的关键参数:设备、协议、哈希值 |
GRO 合并流程:
- 驱动在 poll 回调中调用
napi_gro_receive()传入 skb。 - 根据 skb 的 L3/L4 信息计算哈希,定位
gro_hash[hash]桶。 - 遍历桶内 skb,调用
same_flow()比较skb_gro_params是否匹配。 - 若匹配,调用
gro_receive()将新包合并到现有 skb 中,设置napi_gro_cb->same_flow=1。 - 若不匹配或桶满,调用
gro_flush()将原 skb 送入协议栈,新包入桶。
2.5 数据结构协作关系总结
NAPI 数据结构的协作关系可概括为:中断触发 → napi_schedule() 设置状态 → 加入 softnet_data.poll_list → 软中断遍历调用 poll → poll 内使用 GRO 合并 → 送入协议栈。
| 场景 | 涉及结构体 | 关键操作 |
|---|---|---|
| 中断到达 | ← 上一章 📖 返回目录 下一章 → |