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 设备注册与注销完整流程
网络设备的生命周期分为三个阶段:分配 → 注册 → 注销。以下流程图展示了标准驱动中的完整路径:
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 锁在注册过程中的作用:
2.8 本章小结
struct net_device是网络设备驱动的核心数据结构,驱动通过netdev_ops向内核注册操作回调。- 设备生命周期严格遵循 分配 → 初始化 → 注册 → 运行 → 注销 → 释放 的顺序。
alloc_etherdev()自动为以太网设备分配net_device并附带私有数据区。- 注册/注销操作受 RTNL 锁保护,驱动应避免在锁内执行睡眠操作。
- 注销后必须调用
free_netdev()释放内存,否则造成资源泄漏。