2、内核网络设备驱动基础:struct net_device结构体详解、设备注册与注销流程

2.1 struct net_device 核心结构体

struct net_device 是 Linux 内核网络子系统中最重要的数据结构之一,它抽象了一个网络设备的所有属性和操作接口。每个网络设备(物理网卡、虚拟网卡、网桥、VLAN 接口等)在内核中都由一个 net_device 实例表示。

该结构体定义在 include/linux/netdevice.h 中,包含数百个字段。以下按功能模块分类列出驱动开发最常接触的核心字段:

分类 字段名 类型 说明
标识 name char[IFNAMSIZ] 设备名称,如 eth0、lo
dev_id int 设备实例 ID,用于区分同名设备
ifindex int 内核全局唯一的接口索引
配置 mtu unsigned int 最大传输单元,默认 1500
flags unsigned short 设备标志位(IFF_UP、IFF_RUNNING 等)
features netdev_features_t 设备硬件 offload 能力位图
操作 netdev_ops const struct net_device_ops * 设备操作函数表(ndo_* 回调)
header_ops const struct header_ops * L2 头部构建/解析操作
ethtool_ops const struct ethtool_ops * ethtool 工具支持的操作
数据路径 rx_handler rx_handler_func_t * 接收处理钩子(网桥/OVS 使用)
qdisc struct Qdisc * 根队列规则(TC 层)
napi_list struct list_head 挂载在该设备上的 NAPI 实例链表
统计 stats struct net_device_stats 基础统计(rx_packets、tx_bytes 等)
tstats struct pcpu_sw_netstats * per-CPU 统计,避免缓存行 bouncing
生命周期 reg_state enum netdev_reg_state 注册状态机(INIT/REGISTERED/UNREGISTERED)
dev_addr unsigned char * 设备 MAC 地址指针
驱动开发要点: 驱动通常不需要直接操作 net_device 的所有字段。核心工作是填充 netdev_ops、设置 features、初始化 dev_addr,然后通过 alloc_netdev() / register_netdev() 完成注册。

2.2 net_device_ops 操作函数表

struct net_device_ops 是驱动与内核网络栈交互的核心接口,所有 ndo_* 回调函数都通过该结构体注册。以下是驱动必须实现和常用可选的回调:

回调函数 原型 触发时机 必须实现
ndo_open int (*open)(struct net_device *) ifconfig up / ip link set up 是
ndo_stop int (*stop)(struct net_device *) ifconfig down / ip link set down 是
ndo_start_xmit netdev_tx_t (*start_xmit)(struct sk_buff *, struct net_device *) 上层协议栈发送数据包 是
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 N 推荐
ndo_set_mac_address int (*set_mac_address)(struct net_device *, void *) ip link set address XX:XX:XX:XX:XX:XX 可选
ndo_validate_addr int (*validate_addr)(struct net_device *) MAC 地址合法性检查 可选
ndo_do_ioctl int (*do_ioctl)(struct net_device *, struct ifreq *, int) 用户态 SIOCDEVPRIVATE 命令 可选
ndo_get_stats64 void (*get_stats64)(struct net_device *, struct rtnl_link_stats64 *) 读取统计信息 推荐
关键约束: ndo_start_xmit 必须在原子上下文中运行(持有 tx_lock 或 BH 禁用),不能睡眠。如果硬件发送队列满,应返回 NETDEV_TX_BUSY 并停止队列。

2.3 设备注册与注销完整流程

网络设备的生命周期分为三个阶段:分配 → 注册 → 注销。以下流程图展示了标准驱动中的完整路径:

网络设备注册与注销流程 网络设备注册与注销生命周期 alloc_netdev() / alloc_etherdev() 驱动初始化 net_device 字段 填充 netdev_ops / 设置 features / 配置 MAC 地址 register_netdev() / register_netdevice() 设备运行:数据收发 ndo_open → ndo_start_xmit / NAPI 轮询 unregister_netdev() / unregister_netdevice() free_netdev() 释放内存 ⚠ 注销后必须调用 free_netdev() 否则造成内存泄漏

2.4 分配与初始化代码模板

以下是一个典型 PCIe 网卡驱动的设备初始化代码片段:

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/pci.h>

static int mynic_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct net_device *ndev;
    struct mynic_priv *priv;
    int ret;

    /* 1. 分配 net_device,自动包含私有数据区 */
    ndev = alloc_etherdev(sizeof(struct mynic_priv));
    if (!ndev)
        return -ENOMEM;

    /* 2. 设置设备操作函数表 */
    ndev->netdev_ops = &mynic_netdev_ops;

    /* 3. 设置 ethtool 操作(可选) */
    ndev->ethtool_ops = &mynic_ethtool_ops;

    /* 4. 设置硬件特性 */
    ndev->features |= NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_TSO;
    ndev->hw_features = ndev->features;

    /* 5. 设置 MAC 地址(从 EEPROM 读取) */
    mynic_read_mac_from_eeprom(pdev, ndev->dev_addr);

    /* 6. 获取私有数据指针并初始化 */
    priv = netdev_priv(ndev);
    priv->pdev = pdev;
    priv->ndev = ndev;
    spin_lock_init(&priv->lock);

    /* 7. 注册设备到内核 */
    ret = register_netdev(ndev);
    if (ret) {
        free_netdev(ndev);
        return ret;
    }

    /* 8. 保存 ndev 到 pci_dev 的驱动数据 */
    pci_set_drvdata(pdev, ndev);

    return 0;
}

2.5 注销与清理代码模板

static void mynic_remove(struct pci_dev *pdev)
{
    struct net_device *ndev = pci_get_drvdata(pdev);
    struct mynic_priv *priv = netdev_priv(ndev);

    /* 1. 注销网络设备 */
    unregister_netdev(ndev);

    /* 2. 停止硬件(禁用中断、DMA 等) */
    mynic_hw_stop(priv);

    /* 3. 释放 DMA 缓冲区、内存池等 */
    mynic_free_rx_rings(priv);
    mynic_free_tx_rings(priv);

    /* 4. 释放 net_device 结构体 */
    free_netdev(ndev);
}
常见错误: 在 unregister_netdev() 之后立即调用 free_netdev() 是安全的,但必须确保没有其他内核路径(如 NAPI 轮询、中断处理)还在访问 net_device。建议在注销前先 netif_carrier_off() 并同步 NAPI。

2.6 注册状态机与错误处理

net_device 内部维护一个注册状态机 reg_state,驱动可以通过该字段判断设备当前所处的生命周期阶段:

状态值 枚举名 含义
0 NETREG_UNINITIALIZED 刚分配,尚未初始化
1 NETREG_REGISTERED 已注册到内核,设备可见
2 NETREG_UNREGISTERING 正在注销中(不可再发送数据)
3 NETREG_UNREGISTERED 已注销,等待释放
4 NETREG_RELEASED 已释放,不可再引用
调试技巧: 在 /sys/class/net/<ifname>/ 目录下可以查看设备的注册状态。如果驱动卸载后该目录仍然存在,说明 unregister_netdev() 未被正确调用。

2.7 设备注册与注销的 RTNL 锁要求

内核网络子系统要求所有设备注册/注销操作必须在 RTNL 锁(rtnl_lock)保护下进行。驱动开发中需注意以下规则:

  • register_netdev():内部会调用 rtnl_lock(),可在非 RTNL 上下文中调用。
  • register_netdevice():调用者必须已持有 rtnl_lock(),适用于批量注册场景。
  • unregister_netdev():内部会调用 rtnl_lock(),可在非 RTNL 上下文中调用。
  • unregister_netdevice():调用者必须已持有 rtnl_lock()。

以下流程图展示了 RTNL 锁在注册过程中的作用:

RTNL 锁与设备注册关系 RTNL 锁在设备注册中的角色 驱动 probe() 函数 register_netdev() rtnl_lock() 内部调用 注册流程在 RTNL 锁保护下完成 设备加入全局链表 / 触发 NETDEV_REGISTER 通知链 ⚠ 不要在持有 RTNL 锁时调用 可能引起睡眠的函数(如 kmalloc(GFP_KERNEL))

2.8 本章小结

  • struct net_device 是网络设备驱动的核心数据结构,驱动通过 netdev_ops 向内核注册操作回调。
  • 设备生命周期严格遵循 分配 → 初始化 → 注册 → 运行 → 注销 → 释放 的顺序。
  • alloc_etherdev() 自动为以太网设备分配 net_device 并附带私有数据区。
  • 注册/注销操作受 RTNL 锁保护,驱动应避免在锁内执行睡眠操作。
  • 注销后必须调用 free_netdev() 释放内存,否则造成资源泄漏。