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 中几个关键字段之间的协作关系,以及它们如何连接驱动与内核协议栈:

struct net_device 核心字段关系图 struct net_device name flags features netdev_ops 说明: name 标识设备;flags 控制设备状态;features 声明硬件 offload 能力;netdev_ops 是驱动与内核协议栈的操作接口。 ndo_open ndo_start_xmit ndo_stop ndo_get_stats64

2.4 驱动初始化流程中的字段填充顺序

在驱动 probe 函数中,填充 net_device 字段有固定的推荐顺序,违反顺序可能导致内核警告或功能异常:

  1. 分配:调用 alloc_etherdev() 或 alloc_netdev() 分配 net_device 实例。
  2. 设置名称:使用 dev_set_name() 设置设备名称模板。
  3. 挂接操作函数:赋值 ndev->netdev_ops,指向驱动实现的函数表。
  4. 设置特性:填充 ndev->features 和 ndev->hw_features。
  5. 设置标志:按需设置 ndev->flags(如 IFF_MULTICAST)。
  6. 设置 MAC 地址:如果硬件有固定 MAC,使用 eth_hw_addr_set() 设置。
  7. 设置 MTU:如果硬件有特殊限制,修改 ndev->mtu 和 ndev->min_mtu/max_mtu。
  8. 注册:调用 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 收包的基础。