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() 系列函数。
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 |
填充收发统计信息(字节、包数、错误等) |
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 的生命周期:
- 分配:
struct net_device *alloc_etherdev(sizeof_priv)或alloc_netdev() - 初始化: 填充
net_device_ops、ethtool_ops、features、dev_addr等字段 - 注册:
register_netdev(dev)— 使设备对协议栈可见 - 注销:
unregister_netdev(dev)— 移除设备 - 释放:
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。- 设备生命周期管理遵循“分配 → 初始化 → 注册 → 注销 → 释放”的固定顺序,不可颠倒。