Linux NIC驱动开发与网络收发链路调试 - 第1节
1.1 Linux 内核架构概览
Linux 内核是一个宏内核(Monolithic Kernel),但通过模块化设计实现了良好的可扩展性与可维护性。从网卡驱动开发的角度,我们需要重点关注内核中与设备管理、内存管理、进程调度以及网络协议栈交互的核心子系统。
核心视角: 网卡驱动位于内核最底层,直接操作硬件寄存器,并通过内核提供的标准接口(如 NAPI、net_device_ops)向上与网络协议栈交互。理解内核的整体架构是编写高质量驱动的第一步。
1.1.1 内核核心子系统
| 子系统 | 功能描述 | 与网卡驱动的关联 |
|---|---|---|
| 进程调度(Scheduler) | 管理 CPU 时间分配,决定哪个进程运行 | 驱动中断处理函数(ISR)运行在原子上下文,不能睡眠;而 tasklet/workqueue 可被调度 |
| 内存管理(MM) | 负责物理/虚拟内存分配、页表管理、DMA 映射 | 驱动需要分配 DMA 缓冲区(dma_alloc_coherent),并处理页对齐与缓存一致性 |
| 虚拟文件系统(VFS) | 提供统一文件操作接口 | 网络设备通过 socket 接口操作,但驱动本身不直接使用 VFS |
| 网络协议栈(Network Stack) | 实现 TCP/IP 协议族,提供套接字接口 | 驱动通过 net_device 结构体注册,并实现数据包收发回调(ndo_start_xmit) |
| 设备驱动模型(Driver Model) | 提供设备、驱动、总线的统一框架 | 网卡驱动需遵循 platform_driver 或 pci_driver 结构,实现 probe/remove 回调 |
1.1.2 内核空间与用户空间
网卡驱动完全运行在内核空间,拥有最高特权级(Ring 0)。用户空间的应用程序通过系统调用(如 send()、recv())陷入内核,经过协议栈处理后,最终由驱动完成数据收发。
// 用户空间 -> 内核空间 -> 驱动 -> 硬件
// 发送路径示例:
// send(sockfd, buf, len, 0)
// -> sys_sendto()
// -> inet_sendmsg()
// -> tcp_sendmsg()
// -> ip_queue_xmit()
// -> dev_queue_xmit()
// -> ndo_start_xmit() // 驱动入口
1.2 网络协议栈分层模型
理解 OSI 与 TCP/IP 模型是掌握网卡驱动位置的前提。驱动开发者最关心的是数据链路层与物理层,但必须清楚上下层之间的数据封装与传递过程。
1.2.1 OSI 七层模型 vs TCP/IP 四层模型
| OSI 七层 | TCP/IP 四层 | 典型协议/组件 | 驱动关注点 |
|---|---|---|---|
| 应用层 | 应用层 | HTTP, FTP, DNS | 不涉及 |
| 表示层 | SSL/TLS | 不涉及 | |
| 会话层 | NetBIOS, RPC | 不涉及 | |
| 传输层 | 传输层 | TCP, UDP | 不涉及(协议栈处理) |
| 网络层 | 网络层 | IP, ICMP, ARP | 不涉及(协议栈处理) |
| 数据链路层 | 网络接口层 | 以太网帧、MAC 地址 | 核心区域: 驱动负责封装/解析帧头,处理 CRC 校验 |
| 物理层 | 网线、信号、PHY 芯片 | 核心区域: 驱动控制 PHY 状态、链路检测、速率协商 |
1.2.2 数据封装流程(发送方向)
- 应用层: 生成数据流(如 HTTP 请求)。
- 传输层: 添加 TCP/UDP 头部(端口号、序列号)。
- 网络层: 添加 IP 头部(源/目的 IP 地址)。
- 数据链路层: 添加以太网帧头(源/目的 MAC 地址)和帧尾(FCS)。
- 物理层: 将比特流通过介质发送出去。
驱动开发者提示: 驱动在
ndo_start_xmit 中收到的 sk_buff 已经包含了完整的 L2/L3/L4 头部。驱动只需要将整个 skb 映射到 DMA 区域,并通知 DMA 引擎发送即可。不要试图修改协议头部!
1.3 网卡驱动在协议栈中的位置与作用
网卡驱动是内核网络子系统与物理硬件之间的桥梁。它不参与 TCP 拥塞控制、IP 路由选择等高层逻辑,而是专注于以下核心职责:
- 硬件初始化与配置: 探测 PCI 设备、分配 I/O 内存、设置 MAC 地址、启动 PHY。
- 数据包发送: 将协议栈下发的
sk_buff通过 DMA 送入硬件发送队列。 - 数据包接收: 从硬件接收队列取出数据,构造
sk_buff并上送协议栈。 - 中断处理: 响应硬件中断(如收到新包、发送完成、链路状态变化)。
- 统计与控制: 实现
ethtool接口,提供寄存器 dump、统计计数、环回测试等功能。
1.3.1 驱动与协议栈的接口:net_device 结构体
每个网卡在内核中对应一个 struct net_device 实例。驱动通过填充该结构体中的操作函数表 net_device_ops 来注册自己的回调:
static const struct net_device_ops my_netdev_ops = {
.ndo_open = my_netdev_open, // 启动网卡
.ndo_stop = my_netdev_stop, // 停止网卡
.ndo_start_xmit = my_netdev_start_xmit, // 发送数据包
.ndo_set_rx_mode = my_netdev_set_rx_mode, // 设置多播/混杂模式
.ndo_do_ioctl = my_netdev_ioctl, // 自定义 ioctl
.ndo_change_mtu = my_netdev_change_mtu, // 修改 MTU
.ndo_get_stats64 = my_netdev_get_stats64, // 获取统计信息
};
1.3.2 驱动在数据流中的位置(接收路径)
下图展示了从网线收到数据包到用户态应用程序读取的完整路径,其中驱动层负责最底层的硬件交互:
常见误区: 初学者常误以为驱动需要处理 IP 分片或 TCP 重传。实际上,驱动只负责“原始帧”的收发,所有协议逻辑由内核协议栈完成。驱动如果尝试干预协议处理,会导致严重的稳定性问题。
1.4 本章小结
- Linux 内核是模块化宏内核,网卡驱动运行在内核空间,通过
net_device_ops与协议栈交互。 - TCP/IP 模型中,驱动位于网络接口层,直接操作数据链路层(MAC)和物理层(PHY)。
- 驱动核心职责:硬件初始化、DMA 传输、中断处理、统计上报。不参与 L3/L4 协议处理。
- 理解数据封装与分层解耦,是后续编写正确、高效驱动的理论基础。