PCIe
PCI Express(Peripheral Component Interconnect Express)
又称PCIe,它是一种高速串行通信互联标准。PCIe规范遵循开放系统互联参考模型(OSI),自上而下分为事务传输层、数据链路层、物理层。对于特定的网卡,PCIe一般作为处理器外部接口。一般网卡采用DMA控制器通过PCIe Bus访问内存,除了对以太网数据内容的读写外,还有DMA描述符操作相关的读写,这些操作也由MRd/MWr来完成。
PCIe包格式示例如下:物理层开始和结束各有1B的标记,整个数据链路层占用6B。TLP头部64位寻址占用16B(32位寻址占用12B),TLP中的ECRC为可选位。对于一个完整的TLP包来说,除去有效载荷,额外还有24B的开销(TLP头部以16B计算)。
网卡DMA描述符环形队列
DMA(Direct Memory Access,直接存储器访问)是一种高速的数据传输方式,允许在外部设备和存储器之间直接读写数据。数据既不通过CPU,也不需要CPU干预。整个数据传输操作在DMA控制器的控制下进行。网卡DMA控制器通过环形队列与CPU交互。环形队列的内容部分位于主存中,控制部分通过访问外设寄存器的方式完成。
环形队列由一组控制寄存器和一块物理上连续的缓存构成,关键寄存器包括:
- Base:设置环形队列的起始地址,并通告给DMA控制器。
- Size:指定环形队列的总大小。
- Head:显示硬件当前正在处理的描述符位置(通常对软件是只读的)。
- Tail:由软件更新,用来通知DMA控制器已准备好供硬件访问的描述符位置。
无论进行收包还是发包,网卡驱动软件需要完成最基本的操作包括:
- 填充缓冲区地址到描述符:确保数据传输正确地指向目标地址。
- 移动尾指针:更新描述符,标记新的数据接收或发送位置。
- 判断描述符完成状态:检查数据包处理状态,确认数据包是否已完成处理。
- 缓冲区管理:收包时需要申请新的缓冲区,发包时需要释放已发送的数据缓冲区。
还有一些必需的操作是对于描述符写回内容或者包的描述控制头(mbuf)的解析、处理和转换(例如,Scatter-Gather、RSS flag、Offloading flag等)。
转发操作
从CPU的角度来看,主要的操作分为系统内存(可能是处理器的缓存)的直接访问和对外部寄存器MMIO的操作。对于MMIO的操作需经过PCIe总线的传输。由于外部寄存器访问的数据宽度有限(例如,32bit的Tail寄存器),其PCIe事务有效传输率很低。另外由于PCIe总线访问的高时延特性,在数据包收发中应该尽量减少操作来提高效率。
从PCIe设备上DMA控制器的角度来看,其操作有访问系统内存和PCIe设备上的片上内存。绝大多数收发包的PCIe带宽都被这类访问系统内存操作消耗。
优化
减少MMIO访问的频度:接收包时,尾寄存器(tail register)的更新发生在新缓冲区分配以及描述符重填之后。只要将每包分配并重填描述符的行为修改为滞后的批量分配并重填描述符,接收侧的尾寄存器更新次数将大大减少。
DPDK是在判断空置率小于一定值后才触发重填来完成这个操作的。发送包时,就不能采用类似的方法。因为只有及时地更新尾寄存器,才会通知网卡进行发包。但仍可以采用批量发包接口的方式,填充一批等待发送的描述符后,统一更新尾寄存器。
提高PCIe传输的效率:如果能把4个操作合并成整Cache Line大小来作为PCIe的事务请求(PCIe净荷为64Byte),带宽利用率就能得到提升。另外,在发送方向,发送完成后回写状态到描述符。使用批量写回方式(例如,网卡中的RS bit),可以用一次PCIe的事务来完成批量(例如,32个为一组)的完成确认。
网卡中的RS(Ring Summary)位用于指示一组描述符已经完成,从而允许批量写回,这样可以优化对状态更新的效率。
尽量避免Cache Line的部分写:Cache Line的部分写会引发内存访问read-modify-write的合并操作,增加额外的读操作,也会降低整体性能。所以,DPDK在Mempool中分配buffer的时候,会要求对齐到Cache Line大小。
Mbuf
为了高效访问数据,DPDK将内存封装在Mbuf(struct rte_mbuf)
结构体内。Mbuf主要用来封装网络帧缓存,也可用来封装通用控制信息缓存(缓存类型需使用CTRL_MBUF_FLAG
来指定)。网络帧元数据的一部分内容由DPDK的网卡驱动写入。这些内容包括VLAN标签、RSS哈希值、网络帧入口端口号以及巨型帧所占的Mbuf个数等。对于巨型帧,网络帧元数据仅出现在第一个帧的Mbuf结构中,其他的帧该信息为空。
单帧结构如下:
巨帧结构:
Mempool
在DPDK中,数据包的内存操作对象被抽象化为Mbuf结构,而有限的rte_mbuf结构对象则存储在内存池中。内存池使用环形缓存区来保存空闲对象。
当一个网络帧被网卡接收时,DPDK的网卡驱动将其存储在一个高效的环形缓存区中,同时在Mbuf的环形缓存区中创建一个Mbuf对象。当然,两个行为都不涉及向系统申请内存,这些内存已经在内存池被创建时就申请好了。Mbuf对象被创建好后,网卡驱动根据分析出的帧信息将其初始化,并将其和实际帧对象逻辑相连。对网络帧的分析处理都集中于Mbuf,仅在必要的时候访问实际网络帧。
为增加对Mbuf的访问效率,内存池还拥有内存通道/Rank对齐辅助方法。内存池还允许用户设置核心缓存区大小来调节环形内存块读写的频率。
内存通道对齐:现代处理器通常有多个内存通道(memory channels)来提升内存带宽。为了提高性能,内存池会尝试将内存块对齐到内存通道的边界,以减少内存访问冲突,提高数据传输速度。
Rank对齐:内存模块由多个内存Rank(内存行)组成,对齐到内存Rank的边界可以减少内存访问延迟和提升带宽利用率。这种对齐方式帮助减少内存控制器的负担,并优化数据访问效率。
多核CPU访问同一个内存池或者同一个环形缓存区时,因为每次读写时都要进行Compare-and-Set操作来保证期间数据未被其他核心修改,所以存取效率较低。
DPDK的解决方法是使用单核本地缓存一部分数据,实时对环形缓存区进行块读写操作,以减少访问环形缓存区的次数。单核CPU对自己缓存的操作无须中断,访问效率因而得到提高。
该方法要求每个核CPU都有自己私用的缓存(大小可由用户定义,也可为0,或禁用该方法),而这些缓存在绝大部分时间都没有能得到百分之百运用,因此一部分内存空间将被浪费。