2. 网络设备核心结构:struct net_device 详解
struct net_device 是 Linux 内核网络子系统中最重要的数据结构之一,它抽象了每一个网络设备(物理或虚拟)。无论是千兆网卡、Wi-Fi 接口,还是 VLAN 子接口、Bridge、TUN/TAP,在内核中都以一个 net_device 实例表示。理解其核心字段是编写网络设备驱动的第一步。
2.1 结构体概览与生命周期
struct net_device 定义在 include/linux/netdevice.h 中,是一个巨型结构体(超过 200 个字段)。驱动开发者不需要掌握所有字段,但必须熟悉以下几类:
- 标识字段:设备名称、索引、类型。
- 配置字段:标志位、功能特性、MTU、MAC 地址。
- 操作函数集:
netdev_ops、ethtool_ops。 - 数据路径字段:NAPI 结构、队列、统计信息。
- 链表与引用:全局设备链表、引用计数。
probe() 中分配 net_device,填充关键字段,然后注册到内核。在 remove() 中注销并释放。生命周期由引用计数管理。
2.2 重要字段详解
2.2.1 name — 设备名称
设备名称是用户空间可见的网络接口名,如 eth0、wlan0、lo。内核通过 dev_name() 或直接访问 dev->name 获取。
| 特性 | 说明 |
|---|---|
| 类型 | char name[IFNAMSIZ],IFNAMSIZ 通常为 16 字节 |
| 命名方式 | 内核默认使用 eth%d、wlan%d 格式,%d 由内核分配 |
| 自定义名称 | 驱动可在注册前通过 dev_set_name() 设置固定名称 |
| 用户重命名 | 通过 ip link set name 或 udev 规则修改 |
/* 驱动中设置设备名称的典型方式 */
struct net_device *ndev;
ndev = alloc_etherdev(sizeof(struct my_priv));
if (!ndev)
return -ENOMEM;
/* 使用 dev_set_name 设置名称模板 */
dev_set_name(ndev, "myeth%d");
2.2.2 flags — 设备标志位
flags 字段(unsigned short)存储网络设备的当前状态和特性,定义在 include/uapi/linux/if.h。驱动通过 dev->flags 读取,通过 dev_change_flags() 或 ndo_change_rx_flags 回调修改。
| 标志常量 | 值 | 含义 |
|---|---|---|
IFF_UP |
0x1 | 接口已启用(ip link set up) |
IFF_BROADCAST |
0x2 | 支持广播地址 |
IFF_LOOPBACK |
0x8 | 环回接口 |
IFF_PROMISC |
0x100 | 混杂模式(接收所有帧) |
IFF_ALLMULTI |
0x200 | 接收所有多播帧 |
IFF_NOARP |
0x80 | 不支持 ARP(如点对点链路) |
IFF_MULTICAST |
0x1000 | 支持多播 |
ip link set dev eth0 promisc on 时,内核调用驱动的 ndo_set_rx_mode() 回调,驱动需根据 dev->flags & IFF_PROMISC 设置硬件过滤寄存器。
2.2.3 features — 设备功能特性
features 字段(netdev_features_t,通常为 u64)声明设备硬件支持哪些 offload 功能。这些特性直接影响数据路径的处理方式:如果硬件支持某项 offload,内核协议栈可以跳过对应软件处理,大幅提升性能。
| 特性常量 | 含义 | 性能影响 |
|---|---|---|
NETIF_F_SG |
支持分散/聚合 DMA | 减少内存拷贝 |
NETIF_F_IP_CSUM |
IPv4 TCP/UDP 校验和硬件计算 | 节省 CPU |
NETIF_F_HW_VLAN_CTAG_RX |
硬件 VLAN 剥离(接收) | 减少软件处理 |
NETIF_F_TSO |
TCP 分段卸载 | 大幅提升发送吞吐 |
NETIF_F_GRO |
通用接收卸载(软件 GRO) | 减少上层处理次数 |
NETIF_F_LRO |
大型接收卸载(硬件聚合) | 提升接收吞吐 |
/* 驱动在 probe 中设置 features 的典型代码 */
struct net_device *ndev;
ndev = alloc_etherdev(sizeof(struct my_priv));
ndev->features |= NETIF_F_SG | NETIF_F_IP_CSUM;
ndev->features |= NETIF_F_TSO | NETIF_F_GRO;
ndev->hw_features = ndev->features; /* 声明硬件真正支持的特性 */
features 和 hw_features。hw_features 表示硬件真实能力,内核在特性协商时会以此为准。如果只设置 features 而不设置 hw_features,用户通过 ethtool -K 关闭特性后可能无法重新开启。
2.2.4 netdev_ops — 网络设备操作函数表
netdev_ops 是驱动与内核协议栈之间的核心接口,类型为 const struct net_device_ops *。内核通过这个结构体调用驱动的具体实现。每个网络设备驱动都必须实现 ndo_start_xmit(发送数据包),其他回调按需实现。
| 回调函数 | 原型 | 触发时机 |
|---|---|---|
ndo_open |
int (*ndo_open)(struct net_device *) |
ip link set up 或设备激活 |
ndo_stop |
int (*ndo_stop)(struct net_device *) |
ip link set down 或设备关闭 |
ndo_start_xmit |
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *, struct net_device *) |
内核协议栈有数据包要发送 |
ndo_set_rx_mode |
void (*ndo_set_rx_mode)(struct net_device *) |
设备标志或地址列表变化 |
ndo_change_mtu |
int (*ndo_change_mtu)(struct net_device *, int) |
用户修改 MTU |
ndo_do_ioctl |
int (*ndo_do_ioctl)(struct net_device *, struct ifreq *, int) |
用户空间 SIOCDEVPRIVATE 命令 |
ndo_get_stats64 |
void (*ndo_get_stats64)(struct net_device *, struct rtnl_link_stats64 *) |
查询设备统计信息 |
/* 一个最小驱动的 netdev_ops 实现示例 */
static const struct net_device_ops my_netdev_ops = {
.ndo_open = my_open,
.ndo_stop = my_stop,
.ndo_start_xmit = my_start_xmit,
.ndo_set_rx_mode = my_set_rx_mode,
.ndo_change_mtu = my_change_mtu,
.ndo_get_stats64 = my_get_stats64,
};
static int my_probe(struct platform_device *pdev)
{
struct net_device *ndev;
ndev = alloc_etherdev(sizeof(struct my_priv));
if (!ndev)
return -ENOMEM;
ndev->netdev_ops = &my_netdev_ops; /* 挂接操作函数表 */
ndev->flags |= IFF_MULTICAST; /* 声明支持多播 */
/* 设置硬件特性 */
ndev->features |= NETIF_F_SG | NETIF_F_IP_CSUM;
ndev->hw_features = ndev->features;
return register_netdev(ndev); /* 注册到内核 */
}
2.3 核心字段关系图
下图展示了 struct net_device 中几个关键字段之间的协作关系,以及它们如何连接驱动与内核协议栈:
2.4 驱动初始化流程中的字段填充顺序
在驱动 probe 函数中,填充 net_device 字段有固定的推荐顺序,违反顺序可能导致内核警告或功能异常:
- 分配:调用
alloc_etherdev()或alloc_netdev()分配net_device实例。 - 设置名称:使用
dev_set_name()设置设备名称模板。 - 挂接操作函数:赋值
ndev->netdev_ops,指向驱动实现的函数表。 - 设置特性:填充
ndev->features和ndev->hw_features。 - 设置标志:按需设置
ndev->flags(如IFF_MULTICAST)。 - 设置 MAC 地址:如果硬件有固定 MAC,使用
eth_hw_addr_set()设置。 - 设置 MTU:如果硬件有特殊限制,修改
ndev->mtu和ndev->min_mtu/max_mtu。 - 注册:调用
register_netdev(ndev)将设备注册到内核网络子系统。
net_device 的字段填充集中在一个初始化函数中,便于审查和维护。使用 netdev_info() 打印关键字段值,有助于调试注册阶段的问题。
2.5 本章小结
struct net_device是网络驱动的核心数据结构,驱动开发者需重点掌握name、flags、features、netdev_ops四个字段。name标识设备,flags控制状态,features声明硬件能力,netdev_ops是驱动与内核的契约接口。- 驱动在
probe中按固定顺序填充这些字段,最后调用register_netdev()完成注册。 - 理解这些字段的语义和交互关系,是后续学习
ndo_start_xmit发送和 NAPI 收包的基础。