2. Linux网络驱动架构
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层负责物理链路协商。
下图展示了数据包从网卡到协议栈的完整路径,以及各层之间的接口关系:
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的典型生命周期如下:
- 分配: 驱动在中断或NAPI上下文中调用
netdev_alloc_skb()或napi_alloc_skb()分配skb。 - 填充: DMA引擎将数据从FIFO搬运到skb的
data区域,驱动设置len和protocol。 - 递交: 调用
netif_receive_skb()将skb送入协议栈(或通过NAPI的napi_gro_receive())。 - 释放: 协议栈处理完毕后调用
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从中断触发到轮询结束的完整状态机:
2.5 本章小结
Linux网络驱动架构通过清晰的分层设计,将协议栈、设备核心、驱动和PHY解耦。开发者需要掌握三个核心数据结构:
- net_device: 网络接口的抽象,承载操作函数集、链路状态和统计信息。
- sk_buff: 数据包的载体,通过指针操作实现高效的协议头处理。
- NAPI: 高性能收包机制,通过中断+轮询混合模式避免中断风暴,是现代千兆/万兆网卡的标配。