2. 网络设备驱动基础架构

在深入性能优化之前,必须牢固掌握 Linux 网络设备驱动的核心骨架。本章将拆解 net_device 结构体、NAPI 机制、中断处理函数(ISR)以及数据包从硬件到协议栈的完整收发流程。理解这些基础组件是后续实施零拷贝、中断合并等优化策略的前提。

2.1 net_device 结构体:驱动的“身份证”

net_device 是 Linux 网络子系统中最核心的数据结构,每一个网络接口(无论是物理网卡还是虚拟设备)在内核中都对应一个 net_device 实例。它包含了设备的所有属性、操作函数指针以及统计信息。

关键字段分类: 以下表格列出了 net_device 中与驱动开发和性能优化最相关的字段。
类别 字段名 说明 性能影响
标识 name 设备名称(如 eth0) —
操作函数 netdev_ops 指向 struct net_device_ops,包含 ndo_open、ndo_stop、ndo_start_xmit 等 发送路径核心回调
NAPI napi_list 设备关联的 NAPI 实例链表 轮询与中断平衡
特性 features 设备能力标志位(如 NETIF_F_SG、NETIF_F_GRO) 决定协议栈卸载策略
队列 tx_queue_len 发送队列长度 影响发送拥塞控制
统计 stats struct net_device_stats,包含收发包计数、错误计数 监控与调优依据
MTU mtu 最大传输单元 影响分片与吞吐
// net_device 结构体简化定义 (include/linux/netdevice.h)
struct net_device {
    char                    name[IFNAMSIZ];
    const struct net_device_ops *netdev_ops;
    struct list_head        napi_list;      // NAPI 实例链表
    unsigned long long      features;       // 设备特性标志
    unsigned int            tx_queue_len;
    struct net_device_stats stats;
    unsigned int            mtu;
    // ... 其他字段
};
优化视角: 在驱动初始化时,正确设置 features 字段(如启用 NETIF_F_GRO 和 NETIF_F_RXHASH)可以直接让协议栈启用硬件卸载和 RSS 分流,这是零成本性能提升的第一步。

2.2 中断处理函数(ISR):第一响应者

当网卡接收到数据包或完成发送时,硬件会触发一个中断信号。CPU 会暂停当前任务,执行注册的中断处理函数(ISR)。ISR 的设计直接决定了系统的实时性和吞吐量。

  • 上半部(Top Half): 在中断上下文中执行,必须快速、不可睡眠。典型操作:读取中断状态寄存器、清中断标志、调度下半部(如 NAPI 或 tasklet)。
  • 下半部(Bottom Half): 在软中断上下文中执行,可延迟处理。典型操作:将数据包从硬件 FIFO 拷贝到 sk_buff、调用 napi_schedule() 启动轮询。
性能陷阱: 如果在 ISR 上半部中执行了过多操作(如内存分配、复杂逻辑),会导致中断延迟增加,甚至引发中断丢失(interrupt storm)。现代高性能驱动几乎全部采用 NAPI 将数据包处理推迟到下半部。
// 典型 ISR 上半部示例
static irqreturn_t my_netdev_interrupt(int irq, void *dev_id) {
    struct net_device *dev = dev_id;
    struct my_priv *priv = netdev_priv(dev);

    // 1. 读取中断状态寄存器
    u32 status = readl(priv->ioaddr + INT_STATUS);

    // 2. 清中断标志(硬件要求)
    writel(status, priv->ioaddr + INT_STATUS);

    // 3. 如果收到数据包,调度 NAPI
    if (status & RX_INT) {
        napi_schedule(&priv->napi);
    }

    // 4. 如果发送完成,更新统计并唤醒队列
    if (status & TX_INT) {
        netif_wake_queue(dev);
    }

    return IRQ_HANDLED;
}

2.3 NAPI:中断与轮询的优雅平衡

NAPI(New API)是 Linux 网络子系统中用于在高吞吐场景下减少中断开销的核心机制。其核心思想是:在流量较低时使用中断唤醒驱动,在流量较高时切换到轮询模式,避免频繁中断导致的 CPU 浪费。

NAPI 工作流程:
  1. 中断触发,ISR 调用 napi_schedule()。
  2. NAPI 实例被添加到软中断处理队列 softnet_data 的 poll_list 中。
  3. 软中断 NET_RX_SOFTIRQ 被触发,调用 NAPI 的 poll 回调函数。
  4. poll 函数从硬件接收描述符中批量读取数据包,构造 sk_buff 并送入协议栈。
  5. 如果 poll 函数处理完预算(budget)内的所有包,则重新使能中断;否则继续轮询。
// NAPI poll 回调函数模板
static int my_netdev_poll(struct napi_struct *napi, int budget) {
    struct my_priv *priv = container_of(napi, struct my_priv, napi);
    int work_done = 0;

    // 循环从硬件接收环中取出数据包,直到 budget 耗尽或环为空
    while (work_done < budget) {
        struct sk_buff *skb = my_retrieve_rx_packet(priv);
        if (!skb)
            break;

        // 将 skb 送入协议栈(netif_receive_skb)
        netif_receive_skb(skb);
        work_done++;
    }

    // 如果处理完所有包,退出轮询并重新使能中断
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        my_enable_rx_interrupt(priv);
    }

    return work_done;
}
优化关键: budget 参数控制每次 poll 最多处理的数据包数量。增大 budget 可以提升批量处理效率,但会增加单次软中断的延迟。通常建议在 64~256 之间调整,配合中断合并(coalescing)使用效果更佳。

2.4 数据包收发流程:从硬件到协议栈

理解数据包从网卡物理线缆到达内核协议栈的完整路径,是定位性能瓶颈的基础。下图展示了接收路径的核心环节。

数据包接收流程:从硬件到协议栈 1. 硬件接收 2. DMA 写入内存 3. 触发中断 (ISR) 4. NAPI 调度 5. 协议栈处理 网卡从线缆接收信号,进行 CRC 校验,解析 MAC 地址 硬件 DMA 引擎将数据从 FIFO 直接传输到主内存的环形缓冲区 网卡通过 PCIe 引脚向 CPU 发送中断信号,驱动 ISR 执行 ISR 调用 napi_schedule(),将 NAPI 实例加入软中断队列 NAPI poll 回调构造 sk_buff,调用 netif_receive_skb() 送入协议栈

2.5 发送路径:反向流程

发送路径相对接收路径更为直接,但同样存在关键优化点。核心函数是 ndo_start_xmit,由协议栈在调用 dev_queue_xmit 时触发。

  1. 协议栈封装: 上层协议(TCP/UDP)将数据封装成 sk_buff,调用 dev_queue_xmit。
  2. 排队规则: sk_buff 进入设备的发送队列(qdisc),进行流量整形和调度。
  3. 驱动发送: 驱动 ndo_start_xmit 将 sk_buff 中的数据映射到 DMA 描述符,触发硬件发送。
  4. 发送完成中断: 硬件发送完成后触发中断,驱动释放 sk_buff 并更新统计。
// 驱动发送函数模板
static netdev_tx_t my_netdev_start_xmit(struct sk_buff *skb,
                                          struct net_device *dev) {
    struct my_priv *priv = netdev_priv(dev);

    // 1. 将 skb 数据映射到 DMA 地址
    dma_addr_t dma_addr = dma_map_single(&dev->dev, skb->data,
                                          skb->len, DMA_TO_DEVICE);

    // 2. 将 DMA 地址和长度写入硬件发送描述符
    my_write_tx_desc(priv, dma_addr, skb->len);

    // 3. 触发硬件发送(写 Doorbell 寄存器)
    writel(1, priv->ioaddr + TX_DOORBELL);

    // 4. 如果发送环已满,停止队列
    if (my_tx_ring_full(priv))
        netif_stop_queue(dev);

    // 5. 记录 skb 以便在发送完成中断中释放
    priv->tx_skb[priv->tx_next] = skb;

    return NETDEV_TX_OK;
}
发送路径优化要点:
  • DMA 映射开销: 每次发送都调用 dma_map_single 有性能损耗。可考虑使用 skb_frag_dma_map 或预映射缓冲区。
  • 发送完成合并: 多个发送完成中断可以合并处理,减少 CPU 负载。
  • TSO/GSO 卸载: 如果硬件支持 TCP 分段卸载,驱动应在 features 中设置 NETIF_F_TSO,让硬件处理大包分段。

2.6 关键数据结构关系图

以下 SVG 展示了 net_device、NAPI、中断、sk_buff 和硬件描述符之间的协作关系。

驱动核心数据结构协作关系 net_device 设备抽象 / netdev_ops napi_struct poll 回调 / budget 中断处理 (ISR) 上半部 / 清中断 sk_buff 数据包 / 协议头 DMA 描述符环 物理地址 / 长度 网络协议栈 IP / TCP / UDP 协作说明 • net_device 持有 napi_list • ISR 调用 napi_schedule() • NAPI poll 从描述符环取包 • 构造 sk_buff 送入协议栈 • 发送路径反向:协议栈→skb →描述符环→硬件发送 • 所有组件通过指针互连

2.7 本章小结

组件 核心作用 性能关注点
net_device 设备抽象,提供操作接口和特性标志 正确设置 features 以启用硬件卸载
ISR 快速响应硬件事件,调度下半部 上半部必须极简,避免中断延迟
NAPI 中断/轮询混合模式,减少高负载下的中断开销 调整 budget 和使能/关闭阈值
sk_buff 内核网络数据包的标准载体 避免频繁分配/释放,使用缓存池
DMA 描述符环 硬件与内存之间的数据传输通道 环大小影响吞吐和延迟,需合理配置
下一阶段准备: 在掌握了驱动基础架构后,第 3 章将深入分析传统收发包路径中的性能瓶颈(如内存拷贝、上下文切换、锁竞争),为后续的零拷贝和中断合并策略提供量化依据。