1、Mbuf
Mbuf主要用来封装网络帧缓存,也可用来封装通用控制信息缓存(缓存类型需使用CTRL_MBUF_FLAG来指定)。现在介绍两个概念:网络帧元数据、网络帧。
1.1 基本概念
网络帧(Network Frame): 网络帧通常指的是在网络传输中携带数据的基本单位,它是符合特定网络协议(如以太网、IP、TCP、UDP等)格式的数据包。在网络设备(如网卡)处理的过程中,网络帧是从物理链路层接收到的实际数据,包含了帧头、有效载荷数据以及可能的帧尾(如校验和等)。在DPDK中,网络帧通常通过struct rte_mbuf(即上文提到的mbuf)来表示和处理,其中包含了指向实际网络帧数据的指针以及其他相关信息
网络帧元数据(Metadata): 元数据则是与网络帧本身数据内容相独立的相关信息,它描述了网络帧的一些属性或状态,但不直接包含在帧本身的协议字段内。在DPDK中,rte_mbuf结构体内除了包含指向帧数据的指针外,还包括了许多用来描述网络帧元数据的字段,例如:
-
数据包的长度
-
数据包的头部和尾部偏移量
-
数据包所属的内存池标识
-
数据包是否是多段缓冲区的一部分
-
可用于应用程序自定义用途的标记或扩展字段等
mbuf将频繁访问的数据放在第一个cache line,而将功能性扩展的数据放在第二个cache line。Mbuf报头包含包处理所需的所有数据,对于单个Mbuf存放不下的巨型帧(Jumbo Frame), Mbuf还有指向下一个Mbuf结构的指针来形成帧链表结构。dpdk将网络帧元数据和帧本身放在同一段缓存中。
1.2 Mbuf结构
如图所示,这是一个单帧Mbuf结构,Mbuf头部的大小为两个Cache Line,之后的部分为缓存内容,其起始地址存储在Mbuf结构的buffer_addr指针中,获取网络数据帧中的内容,需要在buffer_addr的基础上加head_room的大小,从而得到网络数据帧的内容。head room的作用可以用于扩展头,或者增加一些属性供研发人员二次开发使用 。数据帧的实际长度可通过调用rte_pktmbuf_pktlen (Mbuf)或rte_pktmbuf_datalen (Mbuf)获得,但这仅限于单帧Mbuf。
2 Mempool
2.1 原理
在DPDK中,数据包的内存操作对象被抽象化为Mbuf结构,而有限的rte_mbuf结构对象则存储在内存池中。内存池使用环形缓存区来保存空闲对象。一个网络帧被网卡接收时,DPDK的网卡驱动将其存储在一个高效的环形缓存区中,同时在Mbuf的环形缓存区中创建一个Mbuf对象。当然,两个行为都不涉及向系统申请内存,这些内存已经在内存池被创建时就申请好了。
内存池(Memory Pool)采用的双环形缓存区结构主要是为了高效地管理和复用内存缓冲区(通常是rte_mbuf结构体实例),特别是在高性能网络场景下处理数据包时,这种结构提供了很高的性能和灵活性。
双环形缓存区通常包含两个独立的环形缓冲区结构:
-
空闲缓冲区环(Free Buffer Ring):存储所有可供分配的空闲内存缓冲区。每当DPDK需要一个新的rte_mbuf实例时,可以从这个环中取出一个空闲缓冲区进行使用。
-
已用缓冲区环(Used Buffer Ring):当一个内存缓冲区被填充了数据(例如接收到一个网络数据包)并处理完毕后,会将其归还到已用缓冲区环中。随后,其他处理单元(如另一个CPU核心或硬件队列)可以从已用缓冲区环中获取已处理完的数据包,进行下一步处理或释放
使用流程如下:
-
应用程序首先从空闲缓冲区环中申请一个rte_mbuf实例,填充数据。
-
网卡通过DMA(Direct Memory Access)将接收到的数据直接写入到已分配的rte_mbuf指向的内存区域中。
-
当数据包被处理完毕后,应用程序将该rte_mbuf放回到已用缓冲区环中。
-
另一个工作线程可以从已用缓冲区环中获取处理过的rte_mbuf,继续处理或释放资源。
这种双环形缓存区的设计有效地避免了锁竞争,实现了无锁并发操作,非常适合多核并行处理环境,有助于提高数据包处理的性能。同时,它也简化了内存管理,确保了内存的有效复用。
2.2 内存池的优化
(1)多核访问同一个内存池或者缓冲区时,尽管使用无锁环在一定程度上提升了性能,但是无锁环通过CAS进行比较时,也会消耗很大的性能,在此基础之上,给每个线程申请了一个缓冲池,这样做的好处时实现真正的无锁,没有竞争,缺点就是存在资源的浪费。由于可以手动设置无锁环的大小,可以在一定程度上减少资源的浪费。
(2)内存池通过内存通道/Rank对齐辅助方法,提升性能。所谓的内存通道/rank对齐是指在现代多通道内存架构中,内存控制器可以同时从多个内存通道中读写数据,从而提高内存带宽。为了最大化利用这一特性,内存池在分配内存时会尽可能的确保内存块与其所在的内存通道的边界对齐,这样做的好处是当数据从多个通道间并行传输出,减少跨通道访问带来的延迟。