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 数据封装流程(发送方向)

  1. 应用层: 生成数据流(如 HTTP 请求)。
  2. 传输层: 添加 TCP/UDP 头部(端口号、序列号)。
  3. 网络层: 添加 IP 头部(源/目的 IP 地址)。
  4. 数据链路层: 添加以太网帧头(源/目的 MAC 地址)和帧尾(FCS)。
  5. 物理层: 将比特流通过介质发送出去。
驱动开发者提示: 驱动在 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 驱动在数据流中的位置(接收路径)

下图展示了从网线收到数据包到用户态应用程序读取的完整路径,其中驱动层负责最底层的硬件交互:

网卡驱动在数据接收路径中的位置 物理层(PHY/网线) 网卡驱动(NIC Driver) 数据链路层(MAC 层) 网络层(IP 层) 传输层(TCP/UDP) 应用层(Socket) 硬件信号 -> 数字比特流 PHY 芯片完成模数转换、时钟恢复 中断处理 -> DMA 传输 -> sk_buff 构造 驱动将数据从硬件 FIFO 搬到内存 校验 MAC 地址 -> 去除帧头/帧尾 由内核 netif_receive_skb() 处理 IP 校验 -> 路由查找 -> 分片重组 由 ip_rcv() / ip_rcv_finish() 处理 端口匹配 -> 拥塞控制 -> 数据重组 由 tcp_v4_rcv() / udp_rcv() 处理 数据从内核缓冲区拷贝到用户缓冲区 应用程序通过 recv() 读取
常见误区: 初学者常误以为驱动需要处理 IP 分片或 TCP 重传。实际上,驱动只负责“原始帧”的收发,所有协议逻辑由内核协议栈完成。驱动如果尝试干预协议处理,会导致严重的稳定性问题。

1.4 本章小结

  • Linux 内核是模块化宏内核,网卡驱动运行在内核空间,通过 net_device_ops 与协议栈交互。
  • TCP/IP 模型中,驱动位于网络接口层,直接操作数据链路层(MAC)和物理层(PHY)。
  • 驱动核心职责:硬件初始化、DMA 传输、中断处理、统计上报。不参与 L3/L4 协议处理。
  • 理解数据封装与分层解耦,是后续编写正确、高效驱动的理论基础。