网络设备驱动(1)
网络设备驱动(Network Device Driver)是操作系统内核与网络硬件(物理网卡或虚拟网卡)之间的软件桥梁,负责屏蔽不同硬件的实现细节,并为上层网络协议栈提供统一的数据收发接口。

网络协议栈:

驱动不属于网络协议栈,驱动属于对接物理层和数据链路层的内容。
网卡与PHY的关系?
网卡由三部分组成:PHY,MAC控制器,变压器与RJ45接口。
1.网络设备驱动
1.1核心架构分层
Linux 网络子系统采用自顶向下的分层架构:
-
网络协议栈层:负责 TCP/IP、UDP 等协议的处理,通过
sk_buff结构传递数据包。 -
网络设备接口层:内核抽象出统一的
net_device结构体,定义了标准的网卡行为(启动、关闭、发送)。 -
驱动实现层:特定网卡芯片的驱动代码,负责初始化硬件、配置寄存器、处理 DMA 和硬件中断。
-
物理总线与硬件层:包含 MAC/PHY 芯片及 PCIe、USB 或 MDIO 等总线架构。
网络协议栈层是操作系统内核中纯逻辑的软件子系统,负责实现 TCP/IP 等网络协议的规则与算法(如数据封包/解包、路由选择、拥塞控制、丢包重传等)。网络协议栈层对上层暴露接口,socket(),connect(),send()等,网络设备驱动层不对外暴露接口。
1.2核心数据结构与接口
-
sk_buff(Socket Buffer):网络数据包在内核中的核心数据载体,包含数据指针、协议头偏移量及套接字信息,在协议栈与驱动间流动时无需拷贝实际数据。 -
net_device:代表一个网络接口实体(如eth0),包含 MAC 地址、MTU、状态标志以及底层驱动的回调函数指针集。 -
net_device_ops:驱动向内核注册的操作接口集,核心函数包括:-
ndo_open():打开网卡,申请 DMA 缓冲区和注册中断处理函数。 -
ndo_stop():停止网卡并释放硬件资源。 -
ndo_start_xmit():内核发送数据包的核心入口。
-
1.3网络数据包收发全流程
数据从应用程序发起,自顶向下逐层封装并推送到物理介质:
[应用层: send()] ➔ [Socket TX Buffer] ➔ [TCP/UDP 传输层] ➔ [IP 网络层] ➔ [TC/邻居层] ➔ [驱动 ndo_start_xmit] ➔ [DMA 搬运至网卡] ➔ [PHY 发送]
1. 应用层与 Socket 接口层
-
发起调用:应用程序(如 Nginx)调用
send()/write()系统调用。 -
内存拷贝:CPU 将数据从用户空间缓冲区拷贝至内核空间的 Socket 发送缓冲区(TX Buffer)。
-
构建控制块:内核为数据分配
sk_buff结构体,用于在后续协议栈传输中记录头指针、尾指针与协议元数据。
2. 传输层(TCP / UDP)
-
TCP 切片与封装:TCP 根据 MSS(最大报文段长度)对数据进行分段,为每个
sk_buff添加 TCP 头部(包含源/目的端口、序列号、确认号等)。 -
状态与重传机制:数据被挂入 TCP 送出队列,启动重传定时器;同时计算 TCP 校验和(若网卡支持 Checksum Offload,则留给硬件计算)。
3. 网络层(IP Layer)
-
路由查找(Routing Lookup):查询内核路由表,确定出接口(Egress Interface)及下一跳 IP 地址。
-
IP 头部封装:添加 IP 头部(包含源/目的 IP、TTL、协议号等)。
-
防火墙挂钩(Netfilter/iptables):触发
LOCAL_OUT和POST_ROUTING链(如执行 SNAT 或数据包过滤)。 -
IP 分片:若数据包尺寸大于 Path MTU 且网卡不支持 TSO(TCP Segmentation Offload),在网络层拆分为多个 IP 分片。
4. 邻居子系统与数据链路层(TC & ARP)
-
MAC 地址解析:邻居子系统(ARP 表)解析下一跳 IP 对应的目标 MAC 地址;构建以太网帧头。
-
流量控制(Traffic Control / qdisc):数据包进入网卡的排队规则(qdisc)队列,执行 QoS 限速、整形或丢包策略。
5. 网卡驱动层
-
触发发送:内核调用驱动注册的回调函数
ndo_start_xmit()。 -
DMA 映射:驱动将
sk_buff在主存中的物理地址写入 TX Ring Buffer(TX 描述符环形缓冲区)。 -
通知硬件:驱动写入网卡寄存器(如 Doorbell/Tail 寄存器),提示网卡有新数据待发送。
6. 物理硬件层
-
DMA 提取:网卡控制器直接通过 DMA 从主存的 TX Ring Buffer 提取帧数据到网卡内部的 TX FIFO 缓冲区。
-
物理发包:网卡 MAC/PHY 芯片将数字数据转换为电信号/光信号发往网络介质。
-
清理资源:网卡发送完成后触发 TX 完成中断,CPU 响应硬中断并调度软中断,驱动清理已发送的
sk_buff并释放 TX 描述符。
简述发送流程:(1)应用层调用send系统调用,CPU将数据从用户空间缓冲区拷贝至内核空间Socket缓冲区(TX-Buffer),内核为这块数据构建sk_buff结构体;(2)进入网络协议栈层,对sk_buff增添TCP包头、IP包头、以太网包头等内容;(3)调用驱动中的ndo_statr_xmit()回调:把sk_buffer映射为物理地址,并将其物理地址和数据长度包装为句柄写入TX Ring Buffer,驱动写入网卡寄存器(如Doorbell)通知网卡有新数据到来,网卡控制器通过DMA将TX Ring Buffer提取帧数据到网卡内部的FIFO缓冲区,然后PHY发包。
2.最小网络设备驱动框架
不依赖真实 PHY / MAC 硬件的最小虚拟网卡驱动,下面这个驱动加载后会注册一个类似 vnet0 的网卡。它不能真正把数据发到网线上,只是看清楚网络驱动结构:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
static struct net_device *vnet_dev;
/*
* 打开网卡
* 对应:
* ip link set vnet0 up
*/
static int vnet_open(struct net_device *dev)
{
printk(KERN_INFO "vnet: open\n");
/* 启动发送队列 */
netif_start_queue(dev);
return 0;
}
/*
* 关闭网卡
* 对应:
* ip link set vnet0 down
*/
static int vnet_stop(struct net_device *dev)
{
printk(KERN_INFO "vnet: stop\n");
/* 停止发送队列 */
netif_stop_queue(dev);
return 0;
}
/*
* 发送函数
*
* 上层有数据需要发送时,
* 内核最终会调用这里。
*/
static netdev_tx_t vnet_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
printk(KERN_INFO "vnet: send packet, len = %u\n", skb->len);
/*
* 真正的网卡驱动:
*
* skb
* ↓
* DMA descriptor
* ↓
* MAC
* ↓
* PHY
* ↓
* 网线
*
* 这里没有真实硬件,
* 所以直接释放数据包。
*/
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
/*
* 网络设备操作函数
*/
static const struct net_device_ops vnet_ops = {
.ndo_open = vnet_open,
.ndo_stop = vnet_stop,
.ndo_start_xmit = vnet_start_xmit,
};
static int __init vnet_init(void)
{
int ret;
/*
* 分配一个 Ethernet 网络设备
*
* 最终名字:
* vnet0、vnet1 ...
*/
vnet_dev = alloc_netdev(0,
"vnet%d",
NET_NAME_UNKNOWN,
ether_setup);
if (!vnet_dev)
return -ENOMEM;
/*
* 注册网络设备操作函数
*/
vnet_dev->netdev_ops = &vnet_ops;
/*
* 设置一个随机 MAC 地址
*/
eth_hw_addr_random(vnet_dev);
/*
* 注册到 Linux 网络子系统
*/
ret = register_netdev(vnet_dev);
if (ret) {
free_netdev(vnet_dev);
return ret;
}
printk(KERN_INFO "vnet: registered as %s\n",
vnet_dev->name);
return 0;
}
static void __exit vnet_exit(void)
{
unregister_netdev(vnet_dev);
free_netdev(vnet_dev);
printk(KERN_INFO "vnet: removed\n");
}
module_init(vnet_init);
module_exit(vnet_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("demo");
MODULE_DESCRIPTION("Minimal Linux virtual network driver");
执行
sudo ip link set vnet0 up
会调用 vnet_open();
open中会调用netif_start_queue(dev),意思是告诉 Linux 网络协议栈:这个网卡现在可以开始接收上层下发的待发送数据了。
3.核心架构
3.1net_device结构体
net_device 是 Linux 内核对“网络设备”的统一抽象。像这些:
eth0
eth1
wlan0
lo
vnet0
在内核里基本都对应一个:
struct net_device
net_device结构体中包含内容:
struct net_device
├── 网卡名字
│ └── name,例如 eth0
│
├── MAC 地址
│ └── dev_addr
│
├── MTU
│ └── mtu
│
├── 网络设备操作函数
│ └── netdev_ops
│
├── 发送队列相关信息
│
├── 网卡运行状态
│
├── statistics
│ └── 收发包、错误等统计
│
└── 驱动私有数据
└── netdev_priv()
其中最重要的还是netdev_ops,如:
static const struct net_device_ops vnet_ops = {
.ndo_open = vnet_open,
.ndo_stop = vnet_stop,
.ndo_start_xmit = vnet_start_xmit,
};
3.2net_device注册流程
初始化net_device的步骤是:
分配 net_device
↓
获取私有数据
↓
设置 net_device 基本属性
↓
绑定 net_device_ops
↓
初始化驱动私有资源
↓
注册 net_device
struct my_priv *priv;
struct net_device *ndev;
int ret;
/* 1. 分配 Ethernet net_device + 私有数据 */
ndev = alloc_etherdev(sizeof(struct my_priv));
if (!ndev)
return -ENOMEM;
/* 2. 获取驱动私有数据 */
priv = netdev_priv(ndev);
/* 3. 保存关联关系 */
priv->ndev = ndev;
/* 4. 设置网卡操作函数 */
ndev->netdev_ops = &my_netdev_ops;
/* 5. 设置 MAC 地址 */
eth_hw_addr_random(ndev);
/* 也可以设置其它属性 */
ndev->mtu = 1500;
/* 6. 初始化驱动自己的资源 */
priv->irq = irq;
priv->base = base;
/* 例如初始化 NAPI */
netif_napi_add(ndev, &priv->napi, my_poll);
/* 7. 注册到 Linux 网络子系统 */
ret = register_netdev(ndev);
if (ret) {
free_netdev(ndev);
return ret;
}
驱动私有数据:
真实驱动不可能只靠 net_device,还需要私有数据,net_device存放通用的数据,对于不同的网卡有不同的硬件细节,需要放到私有数据中,如:
-
PCIe 网卡:需要存 TX/RX DMA 描述符环形缓冲区指针、PCIe 寄存器映射基地址(MMIO)、MSI-X 中断向量表等。
-
USB 网卡:需要存 USB URB(USB Request Block)结构体、控制/数据端点(Endpoint)等。
-
Wi-Fi 网卡:需要存 SSID、信道、加密密钥、信号强度(RSSI)、Beacon 定时器等。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)