DPDK源码分析之(1)libmbuf模块

Author:OnceDay Date:2024年7月2日

漫漫长路,有人对你笑过嘛…

全系列文档可参考专栏:源码分析_Once-Day的博客-CSDN博客

参考文档:

1. 概述
1.1 mbuf介绍

mbuf(消息缓冲区)库提供了分配和释放缓冲区(mbufs)的功能,这些缓冲区可以被DPDK应用程序用来存储消息。消息缓冲区存储在一个称为mempool(内存池)的结构中,而这个内存池是通过Mempool库来管理的。

通常情况下,rte_mbuf结构体用于承载网络数据包缓冲区,但实际上它可以存储任何类型的数据(例如控制数据、事件等)。为了提高性能,rte_mbuf头部结构被设计得尽可能小,目前只使用了两个缓存行(cache line),并且最常用的字段被放在第一个缓存行中。

  • 内存池(Mempool)管理:内存池库通过预先分配大量固定大小的内存对象(如mbufs),来提高内存分配和释放的效率。这种方法避免了频繁的动态内存分配,从而减少了内存碎片和分配开销。

  • 缓存行优化:缓存行是CPU缓存中存储数据的最小单位。将频繁访问的数据字段放在同一个缓存行中,可以减少CPU缓存未命中(cache miss)的次数,从而提高数据访问速度。

  • 灵活性rte_mbuf不仅能承载网络数据包,还能用于其他用途,如存储控制信息或事件消息。这使得它在各种DPDK应用程序中都能发挥作用。

1.2 mbuf设计概念

为了存储数据包(包括协议头部),考虑了两种方法:

  • 在单个内存缓冲区中嵌入元数据结构,后面跟一个固定大小的区域用于存储数据包。优点是只需要一次操作即可分配/释放整个数据包的内存表示。
  • 使用独立的内存缓冲区分别存储元数据结构和数据包。更灵活,允许元数据结构的分配与数据包缓冲区的分配完全分离。

DPDK选择了第一种方法,元数据包含控制信息,如消息类型、长度、数据起始位置的偏移量以及指向其他mbuf结构的指针,从而实现mbuf链的功能

用于承载网络数据包的消息缓冲区可以处理mbuf链,即需要多个缓冲区来保存完整数据包的情况。例如,巨型帧(jumbo frames)由多个通过next字段链接在一起的mbufs组成。

对于新分配的mbuf,数据的起始位置位于缓冲区开始后RTE_PKTMBUF_HEADROOM字节处,该位置是缓存对齐的。消息缓冲区可以用于在系统的不同实体之间传递控制信息、数据包、事件等。

消息缓冲区还可以使用其缓冲区指针指向其他消息缓冲区的数据部分或其他结构,称为间接mbuf,类似于上面的第二种方法

总结mbuf的设计

  1. 单一内存缓冲区,DPDK选择了在单一内存缓冲区中嵌入元数据结构的设计,简化了分配和释放的操作。
  2. 元数据包含控制信息,元数据结构中包含消息类型、长度、数据起始位置的偏移量以及用于缓冲区链的指针。
  3. 缓冲区链,支持缓冲区链,允许多个mbufs链接在一起,特别适用于需要多个缓冲区来保存完整数据包的情况(如巨型帧)。
  4. 缓存对齐,新分配的mbuf的数据起始位置经过缓存对齐,位于RTE_PKTMBUF_HEADROOM字节之后,优化了性能。
  5. 多用途,消息缓冲区不仅用于传输数据包,还可以在系统中传递控制信息、事件等,具有很高的灵活性。
  6. 完全标准的mbuf操作函数集合,遵循常见标准和原则的mbuf操作函数实现。

下面是单个段组成的mbuf,一个mbuf持有整个报文的数据

DPDK源码分析之(1)libmbuf模块-LMLPHP

下面是多个段组成的mbuf,一个主mbuf+多个子mbuf共同持有整个报文的数据

DPDK源码分析之(1)libmbuf模块-LMLPHP

mbuf的构成如下:

| rte_mbuf | ==> rte_mbuf priv size<== | rte_pktmbuf_headroom |     data(Packet data)        | 
|     ==>    struct mbuf     <==       ↓     Headroom(128)    |     dataroom(2176)           |
								   buf.addr    ---->  data_offset
									                          |       MBUF_RX_SIZE=2176      |

这是一个带有私有应用数据的mbuf,其默认headroom是128字节,数据空间是2176字节,rte_mbuf是控制元数据,两个cache line共128字节。

1.3 mbuf使用mempool

mbuf缓冲区管理器(Buffer Manager)使用内存池库(Mempool Library)来分配缓冲区。因此,它确保数据包头在不同的通道和内存组(ranks)之间以最佳方式交错排列,以便L3缓存处理。

一个mbuf包含一个字段,用于指示其来源的内存池。当调用rte_pktmbuf_free(m)时,mbuf会返回到其原始的内存池中。

(1) 内存池概述

  • 内存池是一种用于管理固定大小对象集合的高效机制,专门设计用于减少动态内存分配和释放的开销。
  • 在DPDK中,内存池库(Mempool Library)负责管理这些内存池,提供快速的对象分配和释放功能。

(2) 内存池的创建和使用

  • 内存池在初始化时分配一组固定大小的内存块。这些内存块可以被反复使用,以提高性能和减少内存碎片。
  • 应用程序通过调用rte_mempool_create()函数来创建一个新的内存池,并指定每个对象的大小、对象数量以及其他参数。

(3) mbuf和内存池的关系

  • 一个mbuf包含一个字段pool,用于指示该mbuf来源的内存池。
  • 当分配新的mbuf时,实际上是从指定的内存池中取出一个空闲的内存块。
  • 当释放mbuf时,通过调用rte_pktmbuf_free(m)函数,mbuf会被归还到其原始的内存池中,这样可以被再次使用。

(4) 优化性能

  • 内存池的设计考虑了缓存的优化,确保对象分配和释放操作尽可能地缓存友好,从而提高整体性能。
  • 数据包头在不同的内存通道和组之间交错排列,有助于优化L3缓存的处理。

(5) 共享和多用途

  • 内存池可以在多个核心之间共享,支持并行处理。
  • 除了用于网络数据包的存储,内存池还可以用于其他需要高效内存管理的场景,如事件处理和控制信息存储。
1.4 mbuf对象池构造过程

在DPDK中,消息缓冲区(mbuf)的构造过程由API提供的构造函数完成。以下是详细的构造过程:

(1) 内存池的创建,首先,需要创建一个内存池(mempool),用于存储mbuf对象。通过调用rte_mempool_create()函数来创建内存池。在创建内存池时,可以传递一个回调函数,用于初始化每个mbuf对象。

(2) mbuf的初始化,DPDK提供了一个默认的mbuf初始化函数rte_pktmbuf_init()

这个函数在创建内存池时作为回调函数传递给rte_mempool_create(),用于初始化每个mbuf对象。rte_pktmbuf_init()函数会初始化mbuf结构中的一些字段,这些字段一旦设置就不会被用户修改。具体初始化的字段包括:

  • mbuf类型(mbuf type)
  • 来源的内存池(origin pool)
  • 缓冲区的起始地址(buffer start address)
  • 其他一些需要初始化的字段
void rte_pktmbuf_init(struct rte_mempool *mp, void *opaque_arg, void *_m, unsigned i) {
    struct rte_mbuf *m = _m;

	memset(m, 0, mbuf_size);
	/* start of buffer is after mbuf structure and priv data */
	m->priv_size = priv_size; //mbuf应用数据空间大小,在私有应用数据之后才是报文数据存储空间
	m->buf_addr = (char *)m + sizeof(struct rte_mbuf) + priv_size; 
	rte_mbuf_iova_set(m, rte_mempool_virt2iova(m) + mbuf_size);
	m->buf_len = (uint16_t)buf_len; //mbuf数据空间大小, 包括headroom和tailroom

	/* keep some headroom between start of buffer and data */
	m->data_off = RTE_MIN(RTE_PKTMBUF_HEADROOM, (uint16_t)m->buf_len);

	/* init some constant fields */
	m->pool = mp;
	m->nb_segs = 1;
	m->port = RTE_MBUF_PORT_INVALID;
	rte_mbuf_refcnt_set(m, 1);
	m->next = NULL;
}
2. mbuf对象定义
2.1 mbuf数据结构

相关字段如下:

2.3 mbuf操作函数定义

相关操作函数和宏定义如下:

07-08 03:04