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 哈希表,用于合并同流的数据包。
struct napi_struct 核心字段关系 struct napi_struct NAPI 实例核心 调度与状态 • poll_list:挂入软中断链表 • state:NAPI_STATE_SCHED 等 • weight:轮询预算 回调与设备 • poll():驱动轮询函数 • dev_id:关联设备/队列 • timer:定时器(低延迟模式) GRO 处理 • gro_hash[ ]:GRO 哈希表 • rx_list:待合并包链表 • gro_count:当前合并计数 每个 NAPI 实例独立管理轮询状态、回调函数与 GRO 上下文 驱动通过 napi_schedule() 将实例加入 poll_list 触发轮询

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;
};
struct softnet_data 关键字段说明
字段 类型 作用
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。
softnet_data 调度 NAPI 实例流程 struct softnet_data Per-CPU 调度核心 poll_list NAPI 实例链表 由 net_rx_action() 遍历 napi_struct A napi_struct B napi_struct C backlog (伪 NAPI) 兼容传统中断驱动 input_pkt_queue 输入 每个 CPU 独立维护 softnet_data,实现 NAPI 实例的 Per-CPU 调度

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 状态标志详解
标志 位 含义 典型操作
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()
状态转换关键路径:
  1. 中断到来 → napi_schedule() 设置 NAPI_STATE_SCHED,将实例加入 poll_list。
  2. 软中断 net_rx_action() 遍历 poll_list,调用 poll 回调。
  3. 若 poll 返回 weight(预算耗尽),保留 SCHED 标志,下次软中断继续。
  4. 若 poll 返回 < weight(处理完毕),清除 SCHED 标志,重新使能中断。
  5. 若 MISSED 被设置,即使处理完毕也重新调度,避免丢包。
NAPI 状态标志转换流程 空闲 SCHED=0, MISSED=0 已调度 SCHED=1, 在 poll_list 轮询中 NPSVC=1, 执行 poll() 中断重入 MISSED=1, 需重新调度 禁用 DISABLE=1, 不可调度 napi_schedule() net_rx_action() poll 返回 < weight 新中断设置 MISSED napi_complete() 检查 napi_disable() napi_enable() 恢复空闲

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 关键数据结构对比
数据结构 存储位置 核心作用
gro_list napi_struct.gro_hash[] 按哈希桶组织待合并的 skb,加速同流查找
napi_gro_cb skb->cb[48] 记录每个 skb 的 GRO 状态:是否同流、是否刷新、是否释放
skb_gro_params skb 扩展字段 存储用于流匹配的关键参数:设备、协议、哈希值
GRO 合并流程:
  1. 驱动在 poll 回调中调用 napi_gro_receive() 传入 skb。
  2. 根据 skb 的 L3/L4 信息计算哈希,定位 gro_hash[hash] 桶。
  3. 遍历桶内 skb,调用 same_flow() 比较 skb_gro_params 是否匹配。
  4. 若匹配,调用 gro_receive() 将新包合并到现有 skb 中,设置 napi_gro_cb->same_flow=1。
  5. 若不匹配或桶满,调用 gro_flush() 将原 skb 送入协议栈,新包入桶。
GRO 数据结构与合并流程 napi_struct gro_hash[GRO_HASH_BUCKETS] gro_hash[0] gro_list: count=3 gro_hash[1] gro_list: count=1 gro_hash[...] 最多 GRO_HASH_BUCKETS 桶 skb A (合并) skb B (合并) skb C (合并) napi_gro_cb (skb->cb) • same_flow: 是否与桶内包同流 • flush: 强制刷新合并结果 • free: 释放原始 skb 流程:napi_gro_receive() → 哈希定位 → 同流合并 → gro_flush() 送入协议栈

2.5 数据结构协作关系总结

NAPI 数据结构的协作关系可概括为:中断触发 → napi_schedule() 设置状态 → 加入 softnet_data.poll_list → 软中断遍历调用 poll → poll 内使用 GRO 合并 → 送入协议栈。

NAPI 核心数据结构协作矩阵
场景 涉及结构体 关键操作
中断到达