第一百四十五章:网络设备
合道期涉及内核源码:
一
林小源在设备山脉的最高峰处,发现了一片悬浮在空中的领域。
这里没有山路,没有桥梁,只有无数光点在空中穿梭——每一个光点都是一个数据包,封装着源地址、目的地址、协议头和有效载荷。光点从四面八方汇聚到一个发光的核心,又被分发到四面八方。
"网络设备。"守山老者说,"这是设备山脉中最特殊的一类——它们不处理字节流,不处理数据块,只处理数据包。"
核心处站着一个身穿银色铠甲的武者,他的铠甲表面不断有数据包流过。每当一个数据包从远处飞来,他就伸出手,将数据包接住,封装成一个 ,然后传递给身后的协议栈。每当协议栈递给他一个 sk_buff,他就把数据包发射出去。
"我是网络设备驱动。"武者说,"收发数据包是我的全部职责。"
/*
* 网络设备驱动的关键结构:
*
* struct net_device_ops:
* ndo_open — 打开设备
* ndo_stop — 关闭设备
* ndo_start_xmit — 发送数据包
* ndo_set_rx_mode — 设置接收模式
* ndo_set_mac_address — 设置 MAC 地址
*
* 注册网络设备:
* alloc_netdev — 分配 net_device
* register_netdev — 注册设备
*
* 发送数据包:
* ndo_start_xmit(skb, dev)
* 把 sk_buff 发送到硬件
*
* 接收数据包:
* 从硬件读取数据
* 构造 sk_buff
* netif_rx(skb) — 传递给协议栈
*/
printf("=== 网络设备驱动 ===\n\n");
printf("网络设备驱动:\n\n");
printf("--- net_device_ops ---\n");
printf("struct net_device_ops {\n");
printf(" .ndo_open = my_open,\n");
printf(" .ndo_stop = my_stop,\n");
printf(" .ndo_start_xmit = my_xmit,\n");
printf(" .ndo_set_rx_mode = my_set_rx,\n");
printf("};\n\n");
printf("--- 注册步骤 ---\n");
printf("1. 分配 net_device:\n");
printf(" dev = alloc_netdev(sizeof_priv, name, ...)\n\n");
printf("2. 设置操作函数:\n");
printf(" dev->netdev_ops = &my_ops;\n\n");
printf("3. 注册设备:\n");
printf(" register_netdev(dev)\n\n");
printf("--- 发送路径 ---\n");
printf("协议栈:\n");
printf(" dev_queue_xmit(skb)\n");
printf(" ↓\n");
printf("驱动:\n");
printf(" ndo_start_xmit(skb, dev)\n");
printf(" ↓\n");
printf("硬件:\n");
printf(" 发送数据包\n\n");
printf("--- 接收路径 ---\n");
printf("硬件:\n");
printf(" 收到数据包\n");
printf(" ↓\n");
printf("驱动:\n");
printf(" 构造 sk_buff\n");
printf(" netif_rx(skb)\n");
printf(" ↓\n");
printf("协议栈:\n");
printf(" 处理数据包\n\n");
printf("--- NAPI ---\n");
printf("高流量时:\n");
printf(" 中断触发后切换到轮询\n");
printf(" 批量处理数据包\n");
printf(" 减少中断开销\n");#include <stdio.h>
/*
* 网络设备驱动的关键结构:
*
* struct net_device_ops:
* ndo_open — 打开设备
* ndo_stop — 关闭设备
* ndo_start_xmit — 发送数据包
* ndo_set_rx_mode — 设置接收模式
* ndo_set_mac_address — 设置 MAC 地址
*
* 注册网络设备:
* alloc_netdev — 分配 net_device
* register_netdev — 注册设备
*
* 发送数据包:
* ndo_start_xmit(skb, dev)
* 把 sk_buff 发送到硬件
*
* 接收数据包:
* 从硬件读取数据
* 构造 sk_buff
* netif_rx(skb) — 传递给协议栈
*/
int main() {
printf("=== 网络设备驱动 ===\n\n");
printf("网络设备驱动:\n\n");
printf("--- net_device_ops ---\n");
printf("struct net_device_ops {\n");
printf(" .ndo_open = my_open,\n");
printf(" .ndo_stop = my_stop,\n");
printf(" .ndo_start_xmit = my_xmit,\n");
printf(" .ndo_set_rx_mode = my_set_rx,\n");
printf("};\n\n");
printf("--- 注册步骤 ---\n");
printf("1. 分配 net_device:\n");
printf(" dev = alloc_netdev(sizeof_priv, name, ...)\n\n");
printf("2. 设置操作函数:\n");
printf(" dev->netdev_ops = &my_ops;\n\n");
printf("3. 注册设备:\n");
printf(" register_netdev(dev)\n\n");
printf("--- 发送路径 ---\n");
printf("协议栈:\n");
printf(" dev_queue_xmit(skb)\n");
printf(" ↓\n");
printf("驱动:\n");
printf(" ndo_start_xmit(skb, dev)\n");
printf(" ↓\n");
printf("硬件:\n");
printf(" 发送数据包\n\n");
printf("--- 接收路径 ---\n");
printf("硬件:\n");
printf(" 收到数据包\n");
printf(" ↓\n");
printf("驱动:\n");
printf(" 构造 sk_buff\n");
printf(" netif_rx(skb)\n");
printf(" ↓\n");
printf("协议栈:\n");
printf(" 处理数据包\n\n");
printf("--- NAPI ---\n");
printf("高流量时:\n");
printf(" 中断触发后切换到轮询\n");
printf(" 批量处理数据包\n");
printf(" 减少中断开销\n");
return 0;
}二
"发送数据包的流程是什么?"林小源问。
银色武者从空中接住一个从协议栈飞来的 sk_buff,向林小源展示:"协议栈把数据封装好——TCP 头、IP 头、以太网头——全部在 sk_buff 里。然后调用 dev_queue_xmit(skb),把 sk_buff 递给我。"
"然后呢?"
"我调用 。"武者说,"这个函数从 sk_buff 中取出数据,写入硬件的发送缓冲区,然后告诉硬件:'发吧。'硬件把数据转成电信号或光信号,发送到网线上。"
"接收呢?"
武者指向远处飞来的一个光点:"硬件收到数据包后,触发中断。我在中断处理函数中——或者更准确地说,在 NAPI 的轮询函数中——从硬件的接收缓冲区读出数据,构造一个 sk_buff,然后调用 netif_rx(skb) 把它传递给协议栈。"
"sk_buff 就是驱动和协议栈之间的桥梁。"林小源说。
"没错。"武者点头,"sk_buff 是网络子系统的核心数据结构。它不只是一个数据缓冲区——里面还包含了协议头的位置、校验和状态、时间戳、优先级等元数据。从驱动到协议栈,从协议栈到用户空间,所有网络数据都通过 sk_buff 传递。"
三
"你提到了 NAPI?"林小源想起武者刚才说的话。
"对。"武者的表情变得严肃,"网络设备面临一个独特的问题——数据包来得太快。"
他指向天空。果然,光点的密度越来越高——每秒有成千上万个数据包飞来。如果每个数据包都触发一次中断,CPU 会被中断淹没——刚处理完一个中断,下一个又来了,永远处理不完。
"NAPI 的解决方案是:第一个数据包来了,触发中断。处理完第一个后,不关中断,而是切换到轮询模式——直接从硬件缓冲区批量读取数据包,不再每个包都触发中断。直到缓冲区读空了,再切回中断模式。"
"中断和轮询的混合模式。"林小源说。
"对。"武者赞许地点头,"低流量时用中断——省 CPU,响应快。高流量时用轮询——批量处理,减少中断开销。NAPI 就是这种混合策略的实现。"
林小源望着天空中那些穿梭的光点,心中浮现出网络设备驱动的全貌:发送时,协议栈通过 sk_buff 把数据交给驱动,驱动通过 ndo_start_xmit 发送到硬件;接收时,硬件通过中断或 NAPI 轮询把数据交给驱动,驱动通过 netif_rx 传递给协议栈。网络设备驱动就是这条双向通道上的守门人。
道藏笔记
内核启示
网络设备驱动负责收发数据包。
网络设备驱动的关键操作:
ndo_open/stop— 打开/关闭设备- — 发送数据包
- — 接收数据包
发送路径:
- 协议栈 → dev_queue_xmit → ndo_start_xmit → 硬件
接收路径:
- 硬件 → sk_buff → netif_rx → 协议栈
网络设备驱动是"数据包"的"通道"——连接硬件和协议栈。
网络设备驱动之试
网络设备驱动把数据包交给硬件发送时,net_device_ops 里的关键回调是什么?