2. Linux 网络设备驱动基础:核心结构与生命周期管理
网络设备驱动是 Linux 内核网络子系统的基石。本章聚焦于驱动开发者必须掌握的三个核心环节:net_device 结构体的组成与配置、网络设备的注册与注销流程、以及设备的打开与关闭操作。理解这些基础是后续实现中断处理与 DMA 传输协同的前提。
2.1 net_device 结构体深度解析
struct net_device 是 Linux 内核中描述网络设备的巨型结构体,定义在 include/linux/netdevice.h 中。它聚合了设备的所有属性、操作函数指针、统计信息和数据队列。驱动开发者无需理解每个字段,但必须掌握以下几类核心成员。
2.1.1 标识与配置字段
| 字段名 | 类型 | 说明 |
|---|---|---|
name | char[IFNAMSIZ] | 设备名称,如 eth0、enp0s3 |
dev_id | int | 用于区分同一名称下的多个设备 |
ifindex | int | 内核全局唯一的接口索引 |
mtu | unsigned int | 最大传输单元,默认 1500 |
type | unsigned short | 硬件类型,如 ARPHRD_ETHER |
addr_len | unsigned char | 硬件地址长度,以太网为 6 |
features | netdev_features_t | 设备能力位图,如 NETIF_F_SG、NETIF_F_TSO |
2.1.2 操作函数集:netdev_ops
netdev_ops 是驱动与内核网络协议栈交互的核心接口表。每个网络设备驱动必须实现以下关键回调:
- ndo_open:打开设备,申请硬件资源、注册中断、启动 DMA 引擎。
- ndo_stop:关闭设备,释放资源、注销中断、停止 DMA。
- ndo_start_xmit:发送数据包,将 sk_buff 传递给硬件发送队列。
- ndo_set_rx_mode:设置接收模式(单播/多播/混杂)。
- ndo_change_mtu:动态修改 MTU 值。
- ndo_tx_timeout:发送超时处理,通常用于复位硬件。
关键提示:在注册 net_device 之前,驱动必须将 netdev_ops 指针赋值给 dev->netdev_ops。内核在打开设备、发送数据包时直接调用这些回调,不会检查是否为 NULL,因此遗漏赋值将导致内核崩溃。
2.1.3 数据路径与中断相关字段
irq:设备使用的中断号,由驱动在探测时设置。base_addr:设备 I/O 内存或端口基地址。rx_queue/tx_queue:接收和发送队列,用于流量控制。napi_struct:NAPI 轮询结构体,用于高效中断处理(后续章节详述)。stats:struct net_device_stats,存放收发包计数、错误计数等统计信息。
2.2 网络设备注册与注销流程
设备注册是驱动向内核网络子系统宣告“我在这里”的过程。注销则是设备移除时的清理操作。这两个流程必须成对出现,且遵循严格的顺序。
2.2.1 设备注册步骤
- 分配 net_device:调用
alloc_netdev()或alloc_etherdev()分配结构体内存。驱动可指定私有数据区大小。 - 初始化字段:设置设备名称、MAC 地址、MTU、netdev_ops、ethtool_ops 等。此时不能操作硬件。
- 注册设备:调用
register_netdev(dev),内核将设备加入全局网络设备链表,创建 sysfs 条目,并可能调用 ndo_open。 - 后续操作:注册成功后,设备对用户空间可见(如 ifconfig -a)。驱动可继续设置硬件参数。
2.2.2 设备注销步骤
- 停止设备:确保设备已关闭(调用 ndo_stop),释放中断和 DMA 资源。
- 注销设备:调用
unregister_netdev(dev),内核从链表中移除设备,删除 sysfs 条目。 - 释放结构体:调用
free_netdev(dev)释放内存。此函数必须在注销之后调用。
常见错误:在设备仍处于打开状态时直接调用 unregister_netdev(),会导致内核访问已释放的中断处理函数。正确的顺序是:先关闭设备 → 注销 → 释放。另外,不要在中断上下文中调用 register_netdev() 或 unregister_netdev(),这些函数可能睡眠。
2.3 网络设备打开与关闭流程
设备的打开(open)和关闭(stop)是驱动生命周期中最频繁的操作。用户执行 ifconfig eth0 up/down 或 ip link set eth0 up/down 时,内核会调用对应的回调函数。
2.3.1 ndo_open 回调实现要点
当用户启用网络接口时,内核调用驱动的 ndo_open 函数。驱动在此函数中必须完成以下操作:
- 申请硬件资源:如 I/O 内存、DMA 缓冲区、描述符环。
- 注册中断处理函数:调用
request_irq(),注册设备的中断服务程序。 - 启动硬件:使能 DMA 引擎、设置接收/发送描述符、开启硬件中断。
- 启动 NAPI:如果使用 NAPI,调用
napi_enable()。 - 更新链路状态:调用
netif_carrier_on()或netif_start_queue()通知内核可以开始收发数据。
static int my_ndo_open(struct net_device *dev) {
struct my_priv *priv = netdev_priv(dev);
int ret;
/* 1. 申请 DMA 描述符环 */
ret = my_alloc_dma_rings(priv);
if (ret) return ret;
/* 2. 注册中断 */
ret = request_irq(dev->irq, my_interrupt_handler, 0, dev->name, dev);
if (ret) {
my_free_dma_rings(priv);
return ret;
}
/* 3. 启动硬件 */
my_hw_start(priv);
/* 4. 启用 NAPI */
napi_enable(&priv->napi);
/* 5. 通知内核可以发送 */
netif_start_queue(dev);
return 0;
}
2.3.2 ndo_stop 回调实现要点
关闭设备时,驱动必须执行与打开相反的操作,且顺序要严格对称:
- 停止发送队列:调用
netif_stop_queue()阻止内核继续提交数据包。 - 禁用 NAPI:调用
napi_disable(),确保轮询函数不再运行。 - 停止硬件:关闭 DMA 引擎、屏蔽硬件中断。
- 释放中断:调用
free_irq(dev->irq, dev)。 - 释放 DMA 资源:回收描述符环和缓冲区。
- 更新链路状态:调用
netif_carrier_off()。
static int my_ndo_stop(struct net_device *dev) {
struct my_priv *priv = netdev_priv(dev);
/* 1. 停止发送队列 */
netif_stop_queue(dev);
/* 2. 禁用 NAPI */
napi_disable(&priv->napi);
/* 3. 停止硬件 */
my_hw_stop(priv);
/* 4. 释放中断 */
free_irq(dev->irq, dev);
/* 5. 释放 DMA 资源 */
my_free_dma_rings(priv);
/* 6. 关闭链路 */
netif_carrier_off(dev);
return 0;
}
最佳实践:在 ndo_open 中,如果某个步骤失败,必须回滚所有已成功的操作(释放已申请的资源)。建议使用 goto 错误处理模式,确保资源不会泄漏。ndo_stop 必须能够处理设备未完全初始化的情况(例如中断尚未注册),避免重复释放。
2.4 关键数据结构与函数速查表
| 函数/宏 | 头文件 | 作用 |
|---|---|---|
alloc_etherdev(sizeof_priv) | linux/etherdevice.h | 分配以太网 net_device 及私有数据区 |
register_netdev(dev) | linux/netdevice.h | 注册网络设备到内核 |
unregister_netdev(dev) | linux/netdevice.h | 从内核注销网络设备 |
free_netdev(dev) | linux/netdevice.h | 释放 net_device 结构体 |
netdev_priv(dev) | linux/netdevice.h | 获取设备私有数据区指针 |
netif_start_queue(dev) | linux/netdevice.h | 启用发送队列 |
netif_stop_queue(dev) | linux/netdevice.h | 停用发送队列 |
netif_carrier_on/off(dev) | linux/netdevice.h | 设置链路载波状态 |
2.5 本章小结
本章详细介绍了 Linux 网络设备驱动的三大基础模块:
- net_device 结构体:作为设备驱动的核心数据结构,聚合了标识、操作函数集、数据路径和中断相关字段。
- 注册与注销流程:必须遵循“分配 → 初始化 → 注册”和“停止 → 注销 → 释放”的严格顺序。
- 打开与关闭流程:ndo_open 负责申请资源并启动硬件,ndo_stop 负责停止硬件并释放资源,两者必须对称实现。