2. 网卡硬件基础
在深入Linux网络驱动源码之前,必须牢固掌握网卡硬件的核心组成与数据通路。本章将拆解现代网卡的四个关键硬件模块:PCIe总线接口、MAC与PHY芯片、寄存器与DMA引擎以及Ring Buffer(环形缓冲区)。理解这些基础是编写高效、稳定网卡驱动的先决条件。
2.1 PCIe总线与网卡接口
绝大多数独立网卡(如Intel I350、Mellanox ConnectX系列)通过PCI Express(PCIe)总线与主机CPU和内存相连。PCIe采用点对点串行差分信号传输,取代了传统的并行PCI总线,提供了更高的带宽和更低的延迟。
2.1.1 PCIe拓扑与配置空间
在系统启动时,BIOS/UEFI会枚举PCIe总线树,为每个设备分配总线号、设备号、功能号(BDF)以及内存/IO地址空间。网卡驱动通过读取PCI配置空间(Configuration Space)来获取设备ID、厂商ID、中断线、BAR(基地址寄存器)等信息。
| PCIe配置空间字段 | 偏移 | 说明 |
|---|---|---|
| Vendor ID | 0x00 | 厂商编号,如Intel为0x8086 |
| Device ID | 0x02 | 设备型号编号 |
| Command/Status | 0x04 | 控制IO、内存、总线主控使能 |
| BAR0 ~ BAR5 | 0x10 ~ 0x24 | 基地址寄存器,映射MMIO或IO端口 |
| Capabilities Pointer | 0x34 | 指向PCIe能力链表 |
| Interrupt Line | 0x3C | 中断向量号(传统INTx) |
pci_alloc_irq_vectors() 用于分配MSI/MSI-X中断向量,而 pci_iomap() 用于将BAR映射的MMIO地址转换为CPU可访问的虚拟地址。
2.1.2 PCIe数据流与DMA
网卡通过PCIe总线进行DMA(直接内存访问),将网络数据包直接写入主机内存(RX路径),或从主机内存读取数据发送到网络(TX路径),无需CPU逐字节拷贝。PCIe事务层包(TLP)负责承载内存读写请求和完成报文。
PCI_COMMAND_MASTER 标志位。
2.2 MAC与PHY芯片功能
网卡的数据链路层和物理层功能通常由两个核心芯片(或集成在一个芯片内)完成:MAC(Media Access Control) 和 PHY(Physical Layer)。
2.2.1 MAC芯片
MAC层负责数据帧的封装与解封装、地址过滤(如MAC地址匹配)、帧校验(FCS)、流量控制(Pause帧)以及CSMA/CD(半双工模式)。在驱动层面,MAC通常通过内部总线(如MII/GMII/RGMII)与PHY连接。
2.2.2 PHY芯片
PHY层负责信号的编码/解码、串行化/解串行化、链路协商(Auto-Negotiation)、以及物理介质接口(如RJ45、SFP+光模块)。PHY通过MDIO/MDC管理接口被MAC或CPU配置。
| 特性 | MAC | PHY |
|---|---|---|
| 主要功能 | 帧封装、地址过滤、流量控制 | 信号编码、链路协商、介质接口 |
| 管理接口 | PCIe(与主机通信) | MDIO/MDC(与MAC通信) |
| 寄存器 | 控制/状态寄存器(CSR) | PHY特定寄存器(如BMCR、BMSR) |
| 驱动关注点 | DMA引擎、Ring Buffer、中断 | 链路状态、速率、双工模式 |
2.3 网卡寄存器与DMA引擎
网卡内部包含一组控制/状态寄存器(CSR),驱动通过MMIO(Memory-Mapped I/O)访问这些寄存器来配置网卡、启动DMA、查询状态。DMA引擎是网卡的核心数据搬运模块。
2.3.1 关键寄存器分类
- 控制寄存器: 全局复位、中断使能、链路控制、流控配置。
- 状态寄存器: 链路状态、中断状态、错误计数、队列状态。
- 描述符基址寄存器: 指向Ring Buffer在内存中的物理地址。
- DMA控制寄存器: 启动/停止DMA引擎、设置DMA阈值。
2.3.2 DMA引擎工作模式
现代网卡支持多队列DMA,每个队列对应一个独立的Ring Buffer对(TX/RX)。DMA引擎根据哈希或流分类将数据包分发到不同队列,实现多核CPU负载均衡(RSS/Flow Director)。
dma_alloc_coherent() 或 dma_map_single()),并将物理地址写入网卡描述符基址寄存器。网卡DMA引擎自动从Ring Buffer中取描述符执行传输。
2.4 Ring Buffer原理
Ring Buffer(环形缓冲区)是网卡驱动与硬件DMA引擎之间交换数据描述符的核心数据结构。它本质上是一个在主机内存中分配的固定大小的环形数组,每个元素称为一个描述符(Descriptor)。
2.4.1 描述符结构
每个描述符通常包含以下字段(以Intel I350为例):
struct e1000_tx_desc {
__le64 buffer_addr; // 数据缓冲区的物理地址
__le16 length; // 数据长度
__le8 cso; // 校验和卸载设置
__le8 cmd; // 命令(如EOP、RS)
__le16 olinfo_status; // 状态与卸载信息
};
2.4.2 发送(TX)流程
- 驱动将待发送的数据包放入DMA缓冲区,填写TX描述符(地址、长度、命令)。
- 驱动更新尾指针寄存器(Tail Register),通知网卡有新的描述符待处理。
- 网卡DMA引擎从Ring Buffer中读取描述符,根据描述符中的地址发起DMA读操作,将数据从内存搬运到网卡内部FIFO。
- 网卡发送完成后,更新描述符的状态字段,并触发中断(或通过写回状态描述符)。
- 驱动在中断处理或轮询中回收已完成的描述符,释放缓冲区。
2.4.3 接收(RX)流程
- 驱动预先分配空的DMA缓冲区,填写RX描述符(地址、长度),并更新尾指针。
- 网卡收到数据包后,DMA引擎将数据写入描述符指向的缓冲区。
- 网卡更新描述符状态(如长度、错误标志),并触发中断或写回描述符。
- 驱动在NAPI轮询中遍历RX Ring Buffer,处理已接收的数据包,并重新填充新的空描述符。
ethtool -g eth0 可查看当前Ring Buffer参数。
2.4.4 头尾指针机制
Ring Buffer通过头指针(Head Pointer)和尾指针(Tail Pointer)管理生产者和消费者关系:
- 驱动(生产者TX/消费者RX): 驱动通过写尾指针通知网卡有新的描述符可用(TX),或通过读头指针知道哪些描述符已被网卡消费(RX)。
- 网卡(消费者TX/生产者RX): 网卡通过读尾指针知道有新的TX描述符待处理,通过写头指针通知驱动RX描述符已填充数据。
wmb() 写屏障),网卡硬件则保证描述符读取的顺序性。
掌握Ring Buffer的运作机制,是理解NAPI、中断合并、零拷贝等高级网卡驱动技术的基础。下一层我们将深入Linux内核的网络设备驱动框架,将这里的硬件知识转化为实际的驱动代码。