前言
本文将结合源码分析 Linux Kernel 实现异步地从网卡收取数据包的过程。
流程总览
sk_buff
.-> gro
gro ---> stack
关键数据结构
环形缓冲区描述符
TODO
环形缓冲区
TODO
net_dev
网络设备实例
softnet
Per-CPU 收包队列
sk_buff
用于存储一个完整的数据包
napi
用于实现轮询收包的实例
Intel I350 Ethernet Controller 网卡收包驱动程序分析
![](./pic/other_intel_nic.jpg)
我们分析的网卡是搭载了 Intel 82599ES 以太网控制器的 10-Gigabit SFI/SFP+ 网卡,通过运行 lspci -v
命令,结果如下所示,我们可以发现它所使用的驱动是 ixgbe
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 04:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Physical Slot: 2
Flags: bus master, fast devsel, latency 0, IRQ 46, NUMA node 0
Memory at d0a80000 (64-bit, non-prefetchable) [size=512K]
I/O ports at 3020 [size=32]
Memory at d0b10000 (64-bit, non-prefetchable) [size=16K]
Expansion ROM at d0a00000 [disabled] [size=512K]
Capabilities: <access denied>
Kernel driver in use: ixgbe
Kernel modules: ixgbe
04:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Physical Slot: 2
Flags: bus master, fast devsel, latency 0, IRQ 162, NUMA node 0
Memory at d0980000 (64-bit, non-prefetchable) [size=512K]
I/O ports at 3000 [size=32]
Memory at d0b00000 (64-bit, non-prefetchable) [size=16K]
Expansion ROM at d0900000 [disabled] [size=512K]
Capabilities: <access denied>
Kernel driver in use: ixgbe
Kernel modules: ixgbe
从 Intel 官方的 Intel® Ethernet Controller Products 27.6 Release Notes intel_ethernet_product 也能查到其使用的驱动的相关信息,因此下面我们就以 ixgbe
驱动为例展开分析。
网卡收包
TODO: 描述一下大致流程。
网卡上的硬件处理
TODO: 网卡的部分卸载功能。
TODO: On-NIC 队列是怎么回事?
写入主机内存
接收描述符环形缓冲区
完成网卡硬件部分的数据帧处理后,网卡会将数据帧使用 PCIe DMA 的方式异步传输到主机内存中,同时网卡会向位于主机内存中的
![](./pic/recv_desp_ring_buffer.png)
可以看出来,Ring Buffer 就是网卡和主机 CPU 之间实现异步的数据交换的用于存放管理信息的一段内存。Ring Buffer 是在主机探测到网卡设备,调用网卡驱动初始化的时候,由网卡驱动进行分配的。Intel 82599 网卡所维护的 Ring Buffer 的形式如
RDBA
: 这个寄存器存放了 Ring Buffer 在主机内存中的起始物理地址;RDLEN
: 这个寄存器存放了 Ring Buffer 在主机内存中的长度,以字节为单位;RDH
: 这个寄存器存放了网卡第一个可以写入的 Descriptor 在 Ring Buffer 中距离RDBA
的偏移量,以字节为单位;RDT
: 这个寄存器存放了主机第一个可以读取的 Descriptor (i.e. 网卡最后一个可用的 Descriptor 之后的一个 Descriptor) 在 Ring Buffer 中距离RDBA
的偏移量,以字节为单位;
通过维护这四个简单的寄存器,网卡和主机之间就能够实现多个 Descriptors 的异步传输。RDBA
和 RDLEN
寄存器是在网卡驱动分配完 Ring Buffer 的空间之后被初始化的。而当网卡完成 Descriptor 的写入后,则会自动地递推 RDH
寄存器的值至下一个可写入的 Descriptor 的起始位置; 同样地,当主机完成 Descriptor 的写入后,则会自动地递推 RDT
寄存器的值至下一个可读取的 Descriptor 的起始位置。
Intel 82599 Ethernet Controller 官方文档用
![](./pic/other_recv_desp_ring_buffer_official.png)
现在来看 Ring Buffer 存储的 Descriptor 的格式。Descriptor 中记录了网卡通过 PCIe DMA 向主机内存中写入的数据帧的基本信息。对于 Intel 82599 网卡来说,其支持两种格式的 Descriptor,即 SRRCTL
寄存器的 DRSCTYPE
域进而选择使用某种格式的 Descriptor。
Legacy Descriptor 格式
82599 网卡 Legacy Descriptor 的格式如
![](./pic/legacy_recv_descriptor.png)
Legacy Descriptor 中最重要的字段就是低 8-Bytes 的 Buffer Address[63:0]
,该字段存储的是网卡通过 PCIe DMA 将当前数据帧存储到主机内存中的
Advanced Descriptor 格式
相比于 Legacy Descriptor,Advanced Descriptor 可以用来支持更多的功能特性,如分离数据帧有效负载和数据帧头等。Advanced Descriptor 由于需要支持更多的功能特性,所以分为了
先来看下 Advanced Descriptor 中 Read Format 的定义,如
![](./pic/advanced_recv_descriptor_read.png)
Advanced Descriptor (Read Format) 低 8-Bytes 的存储的是网卡通过 PCIe DMA 将数据帧中封装的 Packet Payload 存储到主机内存中的起始地址;高 8-Bytes 存放的是
GRO
参考 kk_blog_gro