2. 网络设备核心数据结构

在 Linux 内核网络驱动开发中,struct net_device 是描述一个网络设备实例的核心数据结构。它位于 include/linux/netdevice.h,是驱动与内核协议栈之间的桥梁。理解其关键字段与操作函数集,是编写正确、高效网络驱动的第一步。

核心认知: 每个网络接口(如 eth0、wlan0)在内存中对应一个 net_device 实例。驱动通过填充该结构体并向内核注册,使设备能被协议栈识别和管理。

2.1 结构体概览与内存布局

struct net_device 是一个巨型结构体(约 1.5~2 KB),按功能可划分为以下几大区域:

区域 典型字段 功能说明
标识与名称 name, ifindex, dev_id 设备名称、内核索引、物理位置标识
配置与特征 flags, features, priv_flags 设备状态标志、硬件 offload 能力、私有特性
地址信息 dev_addr, broadcast, perm_addr MAC 地址、广播地址、永久地址
队列管理 tx_queue_len, ingress_queue 发送队列长度、入口队列(用于 tc)
NAPI 相关 napi_list, poll_controller NAPI 实例链表、轮询控制函数(旧式)
操作函数集 netdev_ops, ethtool_ops 设备操作方法、ethtool 控制方法
统计信息 stats 收发字节、包数、错误计数
私有数据 priv_destructor, ml_priv 驱动私有数据指针、销毁回调
内存布局提示: 驱动私有数据通常通过 netdev_priv() 宏获取,该宏返回 net_device 结构体末尾的预留空间。分配时使用 alloc_netdev() 或 alloc_etherdev() 系列函数。
struct net_device 内存布局示意图 struct net_device (约 1.5~2 KB) 标识与名称:name[16], ifindex, dev_id 配置与特征:flags, features, priv_flags 地址信息:dev_addr, broadcast, perm_addr 队列与 NAPI:tx_queue_len, napi_list, poll_controller 操作函数集:netdev_ops, ethtool_ops 私有数据区:priv_destructor, ml_priv / netdev_priv() 低地址 高地址

2.2 关键字段深度解析

2.2.1 设备标识与状态

/* 设备名称,如 eth0、wlan0 */
char                    name[IFNAMSIZ];

/* 内核分配的唯一接口索引 */
int                     ifindex;

/* 设备标志位,定义在 <linux/if.h> */
unsigned short          flags;      // IFF_UP, IFF_BROADCAST, IFF_RUNNING 等

/* 设备特性位掩码,定义在 <linux/netdev_features.h> */
netdev_features_t       features;   // NETIF_F_SG, NETIF_F_TSO, NETIF_F_LRO 等
常见陷阱: flags 中的 IFF_UP 表示设备已被用户通过 ifconfig up 启用,而 IFF_RUNNING 表示物理链路状态正常。驱动应在链路状态变化时调用 netif_carrier_on() / netif_carrier_off() 来更新 IFF_RUNNING。

2.2.2 地址信息

/* 设备 MAC 地址(当前使用) */
unsigned char           dev_addr[MAX_ADDR_LEN];

/* 广播地址,通常为 FF:FF:FF:FF:FF:FF */
unsigned char           broadcast[MAX_ADDR_LEN];

/* 永久 MAC 地址(出厂固化) */
unsigned char           perm_addr[MAX_ADDR_LEN];

/* 地址长度,以太网为 ETH_ALEN (6) */
unsigned char           addr_len;

2.2.3 NAPI 相关字段

/* NAPI 实例链表头,驱动可注册多个 NAPI 实例 */
struct list_head        napi_list;

/* 轮询权重,决定一次 poll 最多处理多少包 */
int                     weight;

/* 旧式轮询控制器(非 NAPI 驱动使用) */
int                     (*poll_controller)(struct net_device *dev);
NAPI 注册流程: 驱动在探测函数中调用 netif_napi_add() 将 napi_struct 添加到 napi_list,并在中断处理中通过 napi_schedule() 触发轮询。后续章节将详细展开。

2.3 操作函数集:struct net_device_ops

net_device_ops 是驱动必须实现的核心操作表,内核协议栈通过该结构体调用驱动提供的功能。以下是现代驱动必须实现的几个关键回调:

回调函数 原型 触发时机 驱动职责
ndo_open int (*open)(struct net_device *) ifconfig up 或 ip link set dev up 申请 DMA 缓冲区、注册中断、启动 TX/RX 队列
ndo_stop int (*stop)(struct net_device *) ifconfig down 停止队列、释放中断、回收 DMA 缓冲区
ndo_start_xmit netdev_tx_t (*start_xmit)(struct sk_buff *, struct net_device *) 内核协议栈有数据包要发送 将 sk_buff 映射到 DMA、触发硬件发送
ndo_set_rx_mode void (*set_rx_mode)(struct net_device *) 设备加入/离开多播组或设置混杂模式 更新硬件过滤表(如多播地址列表)
ndo_change_mtu int (*change_mtu)(struct net_device *, int) ip link set mtu 验证新 MTU 值,更新 dev->mtu
ndo_tx_timeout void (*tx_timeout)(struct net_device *, unsigned int) 发送超时(内核检测到队列停滞) 复位硬件、重置 TX 队列、统计超时计数
ndo_get_stats64 void (*get_stats64)(struct net_device *, struct rtnl_link_stats64 *) 用户读取 /proc/net/dev 或使用 ip -s link 填充收发统计信息(字节、包数、错误等)
net_device_ops 回调与内核协议栈交互流程 用户空间 (ip/ifconfig) 内核协议栈 struct net_device_ops ndo_open / ndo_stop / ndo_start_xmit / ... 硬件驱动实现 物理 NIC / 虚拟设备 系统调用 查找 ops 表 回调调用 硬件操作

2.4 辅助结构与 ethtool_ops

除了 net_device_ops,驱动通常还需要实现 ethtool_ops,用于支持 ethtool 工具查询和配置:

static const struct ethtool_ops my_ethtool_ops = {
    .get_drvinfo     = my_get_drvinfo,      // 驱动版本、固件版本
    .get_link        = my_get_link,         // 链路状态
    .get_ringparam   = my_get_ringparam,    // 环形缓冲区大小
    .set_ringparam   = my_set_ringparam,    // 设置环形缓冲区大小
    .get_coalesce    = my_get_coalesce,     // 中断合并参数
    .set_coalesce    = my_set_coalesce,     // 设置中断合并
};

/* 在 net_device 中注册 */
dev->ethtool_ops = &my_ethtool_ops;
最佳实践: 即使驱动不实现所有 ethtool 回调,也应至少实现 get_drvinfo 和 get_link,以便运维人员快速诊断链路状态和驱动版本。

2.5 设备注册与注销流程

驱动使用以下 API 管理 net_device 的生命周期:

  1. 分配: struct net_device *alloc_etherdev(sizeof_priv) 或 alloc_netdev()
  2. 初始化: 填充 net_device_ops、ethtool_ops、features、dev_addr 等字段
  3. 注册: register_netdev(dev) — 使设备对协议栈可见
  4. 注销: unregister_netdev(dev) — 移除设备
  5. 释放: free_netdev(dev) — 回收内存
static int my_probe(struct platform_device *pdev)
{
    struct net_device *ndev;
    struct my_priv *priv;

    /* 1. 分配 net_device + 私有数据 */
    ndev = alloc_etherdev(sizeof(struct my_priv));
    if (!ndev)
        return -ENOMEM;

    /* 2. 设置操作函数集 */
    ndev->netdev_ops = &my_netdev_ops;
    ndev->ethtool_ops = &my_ethtool_ops;

    /* 3. 设置硬件特性 */
    ndev->features |= NETIF_F_SG | NETIF_F_IP_CSUM;

    /* 4. 设置 MAC 地址(从设备树或 EEPROM 读取) */
    eth_hw_addr_set(ndev, mac_addr);

    /* 5. 注册设备 */
    ret = register_netdev(ndev);
    if (ret) {
        free_netdev(ndev);
        return ret;
    }

    platform_set_drvdata(pdev, ndev);
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    struct net_device *ndev = platform_get_drvdata(pdev);
    unregister_netdev(ndev);
    free_netdev(ndev);
    return 0;
}
注销顺序: 必须先调用 unregister_netdev() 再调用 free_netdev()。在 unregister_netdev() 返回后,内核保证不再调用任何 net_device_ops 回调,此时才能安全释放硬件资源。

2.6 本章小结

  • struct net_device 是网络驱动的核心数据结构,包含标识、配置、地址、队列、NAPI、操作函数集和私有数据等区域。
  • net_device_ops 是驱动与内核协议栈交互的主要接口,ndo_open、ndo_stop、ndo_start_xmit 是必须实现的三个核心回调。
  • ethtool_ops 提供用户态诊断和配置能力,建议至少实现 get_drvinfo 和 get_link。
  • 设备生命周期管理遵循“分配 → 初始化 → 注册 → 注销 → 释放”的固定顺序,不可颠倒。