2. Linux网络驱动架构:网络设备子系统分层、net_device结构体、sk_buff缓冲区、NAPI轮询机制

Linux网络驱动是内核中最为成熟且复杂的子系统之一。它采用分层设计思想,将硬件控制、协议栈交互与数据包管理解耦,使得驱动开发者只需关注底层硬件操作,而无需关心上层协议细节。本章将深入剖析网络设备子系统的四层架构、核心数据结构net_device与sk_buff,以及高性能收包机制NAPI。

2.1 网络设备子系统分层架构

Linux网络驱动自下而上分为四层,每一层职责清晰,接口稳定:

层次 核心模块/文件 主要职责
网络协议栈层 net/ipv4/, net/core/ 实现TCP/IP、UDP、ICMP等协议,通过dev_queue_xmit()发送,通过netif_receive_skb()接收
网络设备核心层 net/core/dev.c 管理net_device注册/注销、链路状态变更、NAPI调度、流量控制队列
网络设备驱动层 具体网卡驱动(如drivers/net/ethernet/) 实现net_device_ops回调(ndo_open, ndo_start_xmit, ndo_do_ioctl等),操作硬件寄存器
PHY/MDIO设备层 drivers/net/phy/ 通过MDIO总线读写PHY寄存器,完成链路协商、速率/双工模式检测、中断处理
驱动开发聚焦点: 对于嵌入式Linux开发者,最常接触的是网络设备驱动层和PHY设备层。MAC控制器通常集成在SoC内部,而外部PHY芯片通过RGMII/RMII接口连接。驱动层负责配置MAC控制器,PHY层负责物理链路协商。

下图展示了数据包从网卡到协议栈的完整路径,以及各层之间的接口关系:

Linux网络设备子系统分层架构与数据流 Linux网络设备子系统分层架构与数据流 网络协议栈层 TCP/IP · UDP · ICMP · dev_queue_xmit() · netif_receive_skb() 网络设备核心层 (net/core/dev.c) net_device管理 · NAPI调度 · 流量控制 · 链路状态通知 网络设备驱动层 (net_device_ops) ndo_open · ndo_stop · ndo_start_xmit · ndo_do_ioctl · 中断处理 PHY/MDIO设备层 (drivers/net/phy/) PHY寄存器读写 · 链路协商 · 速率/双工检测 · 中断处理 发送 接收 关键接口 发送:协议栈 → dev_queue_xmit() → ndo_start_xmit() → MAC → PHY → 网线 接收:网线 → PHY → MAC → 中断/NAPI → netif_receive_skb() → 协议栈

2.2 net_device结构体:网络设备的“身份证”

struct net_device是Linux网络驱动中最重要的数据结构,每个网络接口(eth0、wlan0等)对应一个实例。它包含了设备的所有属性、操作函数指针和统计信息。该结构体定义在include/linux/netdevice.h中,字段超过200个,以下按功能分类列出核心成员:

分类 字段名 说明
标识与名称 char name[IFNAMSIZ] 设备名称,如eth0
int ifindex 全局唯一接口索引
unsigned int flags 设备标志(IFF_UP, IFF_RUNNING, IFF_PROMISC等)
操作函数集 const struct net_device_ops *netdev_ops 驱动核心回调表(打开、关闭、发送、ioctl等)
const struct ethtool_ops *ethtool_ops ethtool工具回调(链路状态、寄存器dump、EEE等)
void *ml_priv 驱动私有数据指针,通常指向自定义结构体
链路与PHY struct phy_device *phydev 关联的PHY设备指针
int (*phy_connect)(...) PHY连接回调(通过PHY层API完成)
NAPI struct napi_struct napi NAPI轮询实例(或使用netif_napi_add()注册)
int napi_weight NAPI轮询权重,通常为64
统计 struct net_device_stats stats 收发包统计(rx_packets, tx_bytes, rx_errors等)
struct rtnl_link_stats64 *stats64 64位扩展统计
驱动开发要点: 在驱动初始化时,通常使用alloc_etherdev(sizeof(priv))分配net_device,私有数据区紧跟在结构体之后。然后填充netdev_ops、ethtool_ops,最后调用register_netdev(dev)注册到内核。

2.3 sk_buff缓冲区:网络数据的“快递包裹”

struct sk_buff(简称skb)是Linux网络子系统中管理数据包的核心结构体。它贯穿协议栈的每一层,承载着从网卡到应用层的所有网络数据。skb的设计充分考虑了数据包处理中的头部添加/移除、分片重组和零拷贝需求。

skb的核心成员分为三组:

  • 指针管理: head(数据区起始)、data(当前协议层数据起始)、tail(数据结束)、end(数据区结束)。通过移动data和tail指针,实现协议头部的push/pull操作,无需拷贝数据。
  • 长度与协议: len(当前数据长度)、protocol(L3协议类型,如ETH_P_IP)、pkt_type(PACKET_HOST/BROADCAST/MULTICAST等)。
  • 控制与状态: dev(接收/发送设备)、sk(关联socket)、cb[48](控制块,供各层存放私有信息)、ip_summed(校验和状态)。

skb的典型生命周期如下:

  1. 分配: 驱动在中断或NAPI上下文中调用netdev_alloc_skb()或napi_alloc_skb()分配skb。
  2. 填充: DMA引擎将数据从FIFO搬运到skb的data区域,驱动设置len和protocol。
  3. 递交: 调用netif_receive_skb()将skb送入协议栈(或通过NAPI的napi_gro_receive())。
  4. 释放: 协议栈处理完毕后调用kfree_skb()释放skb。
常见陷阱: 驱动在接收路径中必须确保skb的data指针已对齐(通常为NET_IP_ALIGN=2字节偏移),否则会导致ARM等平台上的非对齐访问异常。使用netdev_alloc_skb()会自动处理对齐。

2.4 NAPI轮询机制:告别“中断风暴”

传统网络驱动在每次收到数据包时都触发中断,在高吞吐场景下会导致CPU频繁被中断打断,产生“中断风暴”,严重影响系统性能。NAPI(New API)通过中断+轮询混合模式解决了这一问题。

NAPI的核心思想:

  • 当第一个数据包到达时,驱动正常触发中断。
  • 中断处理函数中,关闭该设备的接收中断,并将设备注册到CPU的轮询列表中。
  • 内核在软中断上下文(NET_RX_SOFTIRQ)中调用驱动的poll回调,批量读取数据包。
  • 当poll回调处理完所有数据包(或达到预算budget),重新开启中断。

NAPI的注册与使用流程:

/* 1. 在驱动初始化中注册NAPI */
netif_napi_add(netdev, &priv->napi, my_poll, 64);

/* 2. 中断处理函数中调度NAPI */
if (napi_schedule_prep(&priv->napi)) {
    /* 关闭接收中断 */
    writel(0, priv->ioaddr + INT_MASK);
    __napi_schedule(&priv->napi);
}

/* 3. poll回调实现 */
int my_poll(struct napi_struct *napi, int budget) {
    struct my_priv *priv = container_of(napi, struct my_priv, napi);
    int work_done = 0;

    while (work_done < budget) {
        struct sk_buff *skb = get_rx_packet(priv);
        if (!skb) break;

        /* 设置skb协议、设备等 */
        skb->protocol = eth_type_trans(skb, priv->netdev);
        napi_gro_receive(napi, skb);  /* 或 netif_receive_skb(skb) */
        work_done++;
    }

    if (work_done < budget) {
        /* 所有包处理完毕,重新开启中断 */
        napi_complete_done(napi, work_done);
        writel(RX_INT_EN, priv->ioaddr + INT_MASK);
    }

    return work_done;
}

NAPI与传统中断模式的对比:

对比维度 传统中断模式 NAPI轮询模式
触发方式 每个数据包触发一次中断 首包中断,后续轮询
高吞吐性能 差(中断风暴导致CPU满载) 优(批量处理,减少上下文切换)
低吞吐延迟 优(立即响应) 略差(需等待轮询调度)
CPU占用率 高(频繁中断切换) 低(批量处理,亲和性更好)
适用场景 低速网络(< 100Mbps) 高速网络(≥ 1Gbps)
GRO与NAPI: 现代驱动通常使用napi_gro_receive()替代netif_receive_skb()。GRO(Generic Receive Offload)在NAPI上下文中将多个相同流的小包合并为一个大包,减少协议栈处理次数,显著提升TCP吞吐量。驱动开发者只需调用napi_gro_receive(),GRO合并由内核自动完成。

下图展示了NAPI从中断触发到轮询结束的完整状态机:

NAPI状态机与收包流程 NAPI状态机与收包流程 中断使能 等待数据包到达 中断触发 ISR中调度NAPI NAPI调度 关闭中断,加入轮询列表 poll()回调 批量读取skb并递交 napi_complete() 重新使能中断 数据包到达 napi_schedule() 软中断调度 work_done < budget 重新使能中断 关键参数 budget = 64 weight = 64

2.5 本章小结

Linux网络驱动架构通过清晰的分层设计,将协议栈、设备核心、驱动和PHY解耦。开发者需要掌握三个核心数据结构:

  • net_device: 网络接口的抽象,承载操作函数集、链路状态和统计信息。
  • sk_buff: 数据包的载体,通过指针操作实现高效的协议头处理。
  • NAPI: 高性能收包机制,通过中断+轮询混合模式避免中断风暴,是现代千兆/万兆网卡的标配。