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

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持有整个报文的数据

在这里插入图片描述

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

在这里插入图片描述

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数据结构

相关字段如下:

字段大小描述
cacheline0
buf_addr8 bytes(void *)报文数据的起始地址(虚拟地址)
buf_iova8 bytes(rte_iova_t)报文数据的起始地址(物理地址),总是对齐8字节
rearm_data在RX描述重装时会自动清除接下来8个字节的数据
data_off2 bytes(uint16_t)报文数据的偏移量(相对于buf_addr)
refcnt2 bytes(uint16_t)mbuf引用计数, 用于广播报文零拷贝
nb_segs2 bytes(uint16_t)mbuf的分段sbuf链节点数目,对于一个mbuf不够的报文有效
port2 bytes(uint16_t)报文的输入/输出端口ID(port)
ol_flags8 bytes(uint64_t)网卡硬件卸载特性
rx_descriptor_fields1
packet_type4 bytes(uint32_t)报文数据类型(L2/L3/L4层和隧道信息)
l2_type(4 bits)外层报文的L2类型信息
l3_type(4 bits)外层报文的L3类型信息
l4_type(4 bits)外层报文的L4类型信息
tun_type(4 bits)隧道类型信息
inner_esp_next_proto(8 bit)inner_esp_next_proto(隧道内层信息)
inner_l2_type(4 bit)内层报文的L2类型信息
inner_l3_type(4 bit)内层报文的L3类型信息
inner_l4_type(4 bit)内层报文的L4类型信息
pkt_lenuint32_t(4 byte)整个报文长度(包括所有关联mbuf的数据长度之和)
data_lenuint16_t(2 byte)当前mbuf的报文数据长度(只计算当前mbuf的数据空间)
vlan_tciuint16_t(2 type)(cpu order)VLAN ID,如果RTE_MBUF_F_RX_VLAN存在
hash8 bytes(联合体)报文hash相关信息
rss(4 byte)网卡RSS哈希的结果
fdir(8 byte)网卡FDIR的标识信息 ,包括lo(hash+id) + hi
sched( 8byte)调度队列信息(queue id/traffic class/color)
txadater(8 bytes)dpdk事件设备(eventdev)的关联发送队列(txq)
vlan_tci_outeruint16_t(2 bytes)(cpu order)外层VLAN ID,如果RTE_MBUF_F_RX_QINQ存在
buf_lenuint16_t(2 bytes)当前mbuf的数据空间大小(初始化时给定值)
poolstruct rte_mempool*(8 bytes)当前mbuf所属的内存池
cacheline10 byte
nextstruct rte_mbuf *(8 bytes)长报文的分段mbuf链表,最后一个节点next需要为零
tx_offloaduint64_t (8 bytes)发送网卡硬件卸载信息
l2_lenL2 (MAC) Header Length for non-tunneling pkt。
Outer_L4_len + … + Inner_L2_len for tunneling pkt
l3_lenL3 (IP) Header Length.
l4_lenL4 (TCP/UDP) Header Length.
tso_segszTCP TSO segment size
outer_l3_lenOuter L3 (IP) Hdr Length
outer_l2_lenOuter L2 (MAC) Hdr Length
shinfo12 bytesstruct rte_mbuf_ext_shared_info,mbuf关联的外部数据信息。
priv_sizeuint16_t(2 bytes)mbuf私有数据大小,作为外层应用的mbuf的实际大小来使用。
timesyncuint16_t(2 bytes)Timesync flags for use with IEEE1588
dynfield19 * 4 bytes动态使用字段
2.3 mbuf操作函数定义

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

操作名称描述
rte_pktmbuf_mtod_offset返回在指定偏移量处的指定类型指针,使用需要注意mbuf数据地址的连续性(是否都在一个mbuf里),即mbuf->buf_addr + mbuf->data_off + offset位置。
rte_pktmbuf_mtod返回偏移量0处指定类型指针,即mbuf->buf_addr + mbuf->data_off位置。
rte_pktmbuf_iova_offset与rte_pktmbuf_mtod_offset类似,但返回的是物理地址(而不是虚拟地址)。
rte_pktmbuf_iova与rte_pktmbuf_mtod类似,但返回的是物理地址(而不是虚拟地址)。
rte_get_rx_ol_flag_name获取Rx offload标识的字符串化表示,一次一个。
rte_get_rx_ol_flag_list获取Rx offload标识的字符串化表示,可以多个标识。
rte_get_tx_ol_flag_name获取Tx offload标识的字符串化表示,一次一个。
rte_get_tx_ol_flag_list获取Rx offload标识的字符串化表示,可以多个标识。
rte_mbuf_prefetch_part1L1缓存预取mbuf第一个cache line的数据
rte_mbuf_prefetch_part2L1缓存预取mbuf第二个cache line的数据
rte_mbuf_iova_get获取mbuf数据的IOVA(虚拟IO地址,IOMMU管理和映射)(mbuf->buf_iova)。
如果RTE_IOVA_IN_MBUF未使能,则等于虚拟地址(mbuf->buf_addr)。
rte_mbuf_iova_set设置IOVA地址,如果RTE_IOVA_IN_MBUF使能,改变mbuf->buf_iova,否则无操作。
rte_mbuf_data_iova获取mbuf报文数据的起始IOVA地址,加上mbuf->data_off。
rte_mbuf_data_iova_default返回默认的mbuf报文数据的起始IOVA地址,固定data offset为RTE_PKTMBUF_HEADROOM(128)
rte_mbuf_from_indirect从一个indirect mbuf的bufffer addr获取其direct(源) mbuf的首地址(指针偏移得到)。
rte_mbuf_buf_addr从mbuf的地址和mempool对象获取mbuf buffer addr
rte_mbuf_data_addr_default从mbuf的地址和mempool对象获取mbuf buffer addr + RTE_PKTMBUF_HEADROOM地址
rte_mbuf_to_baddr获取mbuf buffer addr,从mbuf地址+mempool对象里的mbuf私有数据空间大小算出
rte_mbuf_to_priv返回mbuf私有数据空间的起始地址
rte_pktmbuf_priv_flags返回mbuf私有数据的flags,这是mbuf提供的一种标准私有数据定义。
rte_mbuf_sanity_checkmbuf合规检测,通常用于debug模式,有问题直接panic。
rte_mbuf_checkmbuf合规检测,通常用于debug模式,有问题返回错误原因字符串。
__rte_mbuf_raw_sanity_check原始mbuf合规检测(refcnt=1, next=null, nb_segs=1,通常用于debug模式,有问题直接panic。
rte_mbuf_refcnt_read读取mbuf引用计数,relaxed松散内存序。
rte_mbuf_refcnt_set写入mbuf引用计数,relaxed松散内存序。
__rte_mbuf_refcnt_update更新mbuf引用计数,获取-释放内存序(acquire &release )
rte_mbuf_ext_refcnt_read读取mbuf扩展共享数据的引用计数,relaxed松散内存序。
rte_mbuf_ext_refcnt_set设置mbuf扩展共享数据的引用计数,relaxed松散内存序。
rte_mbuf_ext_refcnt_update更新mbuf扩展共享数据的引用计数,获取-释放内存序(acquire &release )
rte_mbuf_raw_alloc原始方式从mempool申请一个mbuf,buf_addr/buf_iova/buf_len/refcnt=1/nb_segs/next/pool/priv_size等信息应该为重置值。
rte_mbuf_raw_free原始方式释放一个mbuf到mempool,不建议使用,除非优化性能。
rte_pktmbuf_initmempool创建时的初始化函数,用于初始化mbuf全局默认值。
priv_size/buf_addr/buf_iova/buf_len/data_off/pool/nb_segs/port/refcnt/next
其中data_off/nb_segs/port/refcnt/next属于会change的值,需要释放时reset。
rte_pktmbuf_pool_init初始化mempool中私有数据,主要是mbuf的数据空间和私有应用数据空间的大小。
rte_pktmbuf_pool_create创建一个mbuf mempool,提供mbuf数目,私有应用数据和报文数据空间大小等参数。
rte_pktmbuf_pool_create_by_ops与rte_pktmbuf_pool_create类似,但是可以指定mempool ops name。
rte_pktmbuf_pool_create_extbuf与rte_pktmbuf_pool_create类似,但是mbuf的数据存放于外部memory空间。
rte_pktmbuf_data_room_size获取mbuf的数据空间大小(包括RTE_PKTMBUF_HEADROOM)
rte_pktmbuf_priv_size获取mbuf的私有应用数据空间大小,在rte_mbuf和报文数据之间。
rte_pktmbuf_reset_headroom重置mbuf的data_off到默认的headroom起始位置。
rte_pktmbuf_reset重置mbuf的数据,next/pkt_len/tx_offload/vlan_tci/vlan_tci_outer/nb_segs/
port/ol_flags/packet_type/data_off/data_len已初始化。
rte_pktmbuf_alloc从内存池中申请一个mbuf,然后使用rte_pktmbuf_reset进行重置
rte_pktmbuf_alloc_bulk一次从内存池申请多个mbuf,然后使用rte_pktmbuf_reset进行重置
rte_pktmbuf_ext_shinfo_init_helper初始化mbuf的外部共享信息数据结构体,数据需要满足对齐关系。
rte_pktmbuf_attach_extbuf初始化mbuf的外部共享数据,改变buf_addr/buf_iova/buf_len/data_len/data_off=0/ol_flags/shinfo。
修改完成之后,可以调用rte_pktmbuf_reset_headroom/rte_pktmbuf_adj来修复data_len和data_off的值。
rte_pktmbuf_detach_extbufrte_pktmbuf_detach一致。
rte_mbuf_dynfield_copy拷贝rte_mbuf的动态字段dynfield的值。
__rte_pktmbuf_copy_hdr拷贝rte_mbuf的头部数据,port/vlan_tci/vlan_tci_outer/tx_offload/hash/packet_type。
调用rte_mbuf_dynfield_copy拷贝动态空间数据。
rte_pktmbuf_attach将一个mbuf attach到另外一个mbuf上,这种mbuf成为indirect mbuf。
indirect mbuf在attach之前,必须是direct mbuf,其refcnt计数必须为1。
目标mbuf是extbuf,则增加shared info的引用计数,且拷贝ol_flags和shinfo。
目标mbuf是indirect mbuf,增加其源mbuf的引用计数,和普通mbuf一样,拷贝priv_size和ol_flags。
调用**__rte_pktmbuf_copy_hdr**拷贝头部数据。
拷贝data_off/data_len/buf_iova/buf_addr/buf_len/next/pkt_len/nb_segs=1。
__rte_pktmbuf_free_extbuf释放mbuf的extbuf,通过shinfo信息,如果shinfo引用计数为0,则释放extbuf。
__rte_pktmbuf_free_direct减少direct mbuf的引用数目,如果引用计数为0,则直接释放掉direct mbuf。
next/nb_segs=1/refcnt=1, 然后放回mempool
rte_pktmbuf_detach解除一个indirect mbuf的attach状态。
如果mbuf存在extbuf,对于Pinned类型直接Pass。对于其他类型,则调用__rte_pktmbuf_free_extbuf
如果不存在extbuf,直接调用__rte_pktmbuf_free_direct,释放对应的direct mbuf。
重置priv_size,buf_addr,buf_iova,buf_len,data_off,data_len,ol_flags。
__rte_pktmbuf_pinned_extbuf_decrefmbuf释放过程的pinned extbuf处理,重置ol_flags = RTE_MBUF_F_EXTERNAL
如果引用计数为1,则直接结束。如果不为1,则先减去1,最后操作返回值为0的线程再重置为1。
rte_pktmbuf_prefree_seg减少mbuf引用计数,并且判断是否可以free mbuf segment。
mbuf引用计数为1,如果mbuf是indirect mbuf,调用rte_pktmbuf_detach
对于indirect mbuf且存在pinned extbuf,如果extbuf refcnt为1,则需要释放mbuf segment。
mbuf释放操作:next=null,nb_segs=1,返回mbuf(可以回收和释放)。
mbuf引用计数不为1,则减一后为0的线程继续上述释放操作,并且重置mbuf引用计数为1。
rte_pktmbuf_free_seg通过rte_pktmbuf_prefree_seg判读mbuf segment是否可以释放,通过rte_mbuf_raw_free来释放。
rte_pktmbuf_free释放mbuf到mempool池中,支持chain mbuf释放操作。
rte_pktmbuf_free_bulk批量释放mbuf到mempool池中,相当于可以临时缓存mbuf。
rte_pktmbuf_clone基于已有mbuf创建一个clone mbuf,每个mbuf segment都会attach,原mbuf会变成只读mbuf。
rte_pktmbuf_copy基于已有mbuf创建一个全量复制的mbuf,但是private data不会拷贝,两个mbuf将是独立存在的。
可以指定拷贝的报文数据长度,调用**__rte_pktmbuf_copy_hdr**来copy头部。
拷贝会去掉RTE_MBUF_F_INDIRECT和RTE_MBUF_F_EXTERNAL,拷贝后mbuf具有完整的数据。
拷贝mbuf不是逐个mbuf segment拷贝,而是会重新整合数据,尽量填充每个mbuf segment。
rte_pktmbuf_refcnt_update更新mbuf的每个mbuf segment的引用计数。
rte_pktmbuf_headroom获取mbuf的headroom长度,来自data_off。
rte_pktmbuf_tailroom获取mbuf的tailroom长度,来自buf_len - data_off - data_len。
rte_pktmbuf_lastseg获取mbuf的最后一个lastseg。
rte_pktmbuf_pkt_len获取mbuf的报文长度。
rte_pktmbuf_data_len获取mbuf的数据长度。
rte_pktmbuf_prependmbuf报文数据向前扩展指定字节,即尝试减少headroom空间,所以最大上限是headroom大小(data_off=0)。
rte_pktmbuf_append在mbuf后面添加指定字节的数据,尝试减少tailroom,如果空间不足,返回NULL。
rte_pktmbuf_adj移动mbuf的报文数据指针,即data_off,只在第一个mbuf segment移动,不能超过data_len。
rte_pktmbuf_trim裁剪mbuf的尾部数据,裁剪数据长度不能超过最后一个mbuf segment的数据长度(data_len)。
rte_pktmbuf_is_contiguous检查mbuf的数据空间是否连续,即mbuf->nb_segs==1。
__rte_pktmbuf_read在mbuf指定偏移处读取指定长度的数据。
rte_pktmbuf_read如果目标数据是连续的,那么直接返回对应地址的指针,如果非连续,则拷贝到用户buffer中。
rte_pktmbuf_chain将两个mbuf连接起来。
rte_mbuf_tx_offload基于给定的offload参数生成tx_offload值。
rte_validate_tx_offload检查tx_offload的正确性和完整性(ol_flags)。
rte_pktmbuf_linearize将mbuf的数据连续化,要求单个mbuf能放下数据,并且原来的多个mbuf segment被free掉了。
rte_pktmbuf_dump可以dump mbuf的数据。
rte_mbuf_sched_queue_get获取mbuf调度队列的ID。
rte_mbuf_sched_traffic_class_get获取mbuf的traffic class id。
rte_mbuf_sched_color_get获取mbuf的color字段ID。
rte_mbuf_sched_get获取mbuf的queue_id/traffic_class/color的值。
rte_mbuf_sched_queue_set设置mbuf的队列ID。
rte_mbuf_sched_traffic_class_set设置mbuf的class ID。
rte_mbuf_sched_color_set设置mbuf的color ID。
rte_mbuf_sched_set设置mbuf的queue_id, traffic_class and color。
  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值