DPDK数据包与内存专题-mempool内存池

摘要:
在设计大型数据结构时,可以使用内存池来分配内存。同时,mempool还提供了内存获取和释放等操作接口。对于数据包mempool,它甚至提供了一个更详细的接口-rte_pktmbuf_pool_Create()。接下来,重点分析与内存池相关的一般内容。这是内存分配的连续地址空间,用于存储mempool的obj对象。它的功能是将对象指针存储在mempool中,提供了一种使用mempool访问空间的便捷方式。然后,我们遵循mempool的三个关键部分。

前言:DPDK提供了内存池机制,使得内存的管理的使用更加简单安全。在设计大的数据结构时,都可以使用mempool分配内存,同时,mempool也提供了内存的获取和释放等操作接口。对于数据包mempool甚至提供了更加详细的接口-rte_pktmbuf_pool_create(),接下来重点分析通用的内存池相关内容。使用DPDK-17.02版本。

一. mempool的创建

内存池的创建使用的接口是rte_mempool_create()。在仔细分析代码之前,先说明一下mempool的设计思路:在DPDK-17.02版本中(和2.1等先前版本在初始化略有差异),总体来说,mempool的组织是通过3个部分实现的

  • 1.mempool头结构。mempool由名字区分,挂接在struct rte_tailq_elem rte_mempool_tailq全局队列中,可以根据mempool的名字进行查找,使用rte_mempool_lookup()接口即可。这只是个mempool的指示结构,mempool分配的内存区并不在这里面,只是通过物理和虚拟地址指向实际的内存地址。
  • 2.mempool的实际空间。这就是通过内存分配出来的地址连续的空间,用来存储mempool的obj对象。
  • 3.ring队列。ring是个环形无锁队列,关于这个话题,可以参考官方文档来了解。其作用就是存放mempool中的对象指针,提供了方便存取使用mempool的空间的办法。

接下来,就来具体看看mempool的创建和初始化过程。
先注意一下rte_mempool_create的参数中的两个-mp_initobj_init,前者负责初始化mempool中配置的私有参数,如在数据包中加入的我们自己的私有结构;后者负责初始化每个mempool对象。我们然后按照mempool的3个关键部分展开说明。

  • 1.mempool头结构的创建
    在DPDK-17.02中,mempool头结构包含3个部分:struct rte_mempool,cache和mempool private。创建是在rte_mempool_create_empty()中完成的,看这个函数,先进行了对齐的检查
RTE_BUILD_BUG_ON((sizeof(struct rte_mempool) & RTE_CACHE_LINE_MASK) != 0); 

然后从mempool队列中取出头节点,我们创建的mempool结构填充好,就挂接在这个节点上。接下来做一些检查工作和创建flag的设置。

rte_mempool_calc_obj_size()计算了每个obj的大小,这个obj又是由三个部分组成的,objhdr,elt_size,objtlr,即头,数据区,尾。在没有开启RTE_LIBRTE_MEMPOOL_DEBUG调试时,没有尾部分;头部分的结构为:struct rte_mempool_objhdr,通过这个头部,mempool中的obj都是链接到队列中的,所以,提供了遍历obj的方式(尽管很少这么用)。函数返回最后计算对齐后的obj的大小,为后面分配空间提供依据。

然后分配了一个mempool队列条目,为后面挂接在队列做准备。

te = rte_zmalloc("MEMPOOL_TAILQ_ENTRY", sizeof(*te), 0);
	if (te == NULL) {
		RTE_LOG(ERR, MEMPOOL, "Cannot allocate tailq entry!
");
		goto exit_unlock;
	}

接下来,就是计算整个mempool头结构多大,吐槽这里的命名!

	mempool_size = MEMPOOL_HEADER_SIZE(mp, cache_size);
	mempool_size += private_data_size;
	mempool_size = RTE_ALIGN_CEIL(mempool_size, RTE_MEMPOOL_ALIGN);

mempool_size这个名字太有误导性,这里指的是计算mempool的头结构的大小。而不是内存池实际的大小。在这里可以清晰的看出这个mempool头结构是由三部分组成的。cache计算的是所有核上的cache之和。

然后,分配这个mempool头结构大小的空间,填充mempool结构体,并把mempool头结构中的cache地址分配给mempool。初始化这部分cache.

最后就是挂接mempool结构。TAILQ_INSERT_TAIL(mempool_list, te, next);

  • 2.mempool实际空间的创建和ring的创建

这部分的创建是在函数rte_mempool_populate_default() 中完成的。

首先计算了每个elt的总共的大小

total_elt_sz = mp->header_size + mp->elt_size + mp->trailer_size;

然后计算为这些元素需要分配多大的空间,rte_mempool_xmem_size(n, total_elt_sz, pg_shift);

接着rte_memzone_reserve_aligned()分配空间。
终于到关键的一步了,rte_mempool_populate_phys()把元素添加到mempool,实际上就是把申请的空间分给每个元素。

先看到的是这么一段代码:

 
if ((mp->flags & MEMPOOL_F_POOL_CREATED) == 0) {
		ret = rte_mempool_ops_alloc(mp);
		if (ret != 0)
			return ret;
		mp->flags |= MEMPOOL_F_POOL_CREATED;
} 

这就是创建ring的过程咯,其中的函数rte_mempool_ops_alloc()就是实现。那么,对应的ops->alloc()在哪注册的呢?

if ((flags & MEMPOOL_F_SP_PUT) && (flags & MEMPOOL_F_SC_GET))
		rte_mempool_set_ops_byname(mp, "ring_sp_sc", NULL);
	else if (flags & MEMPOOL_F_SP_PUT)
		rte_mempool_set_ops_byname(mp, "ring_sp_mc", NULL);
	else if (flags & MEMPOOL_F_SC_GET)
		rte_mempool_set_ops_byname(mp, "ring_mp_sc", NULL);
	else
		rte_mempool_set_ops_byname(mp, "ring_mp_mc", NULL);

就是根据ring的类型,来注册对应的操作函数,如默认的就是ring_mp_mc,多生产者多消费者模型,其操作函数不难找到:

static const struct rte_mempool_ops ops_mp_mc = {
	.name = "ring_mp_mc",
	.alloc = common_ring_alloc,
	.free = common_ring_free,
	.enqueue = common_ring_mp_enqueue,
	.dequeue = common_ring_mc_dequeue,
	.get_count = common_ring_get_count,
};

接下来,又分配了一个struct rte_mempool_memhdr *memhdr;结构的变量,就是这个变量管理着mempool的实际内存区,它记录着mempool实际地址区的物理地址,虚拟地址,长度等信息。

再然后,就是把每个元素对应到mempool池中了:mempool_add_elem()。在其中,把每个元素都挂在了elt_list中,可以遍历每个元素。最后rte_mempool_ops_enqueue_bulk(mp, &obj, 1);,最终,把元素对应的地址入队,这样,mempool中的每个元素都放入了ring中。

创建完成!!!

二 . mempool的使用

mempool的常见使用是获取元素空间和释放空间。

  • rte_mempool_get可以获得池中的元素,其实就是从ring取出可用元素的地址。
  • rte_mempool_put可以释放元素到池中。
  • rte_mempool_in_use_count查看池中已经使用的元素个数
  • rte_mempool_avail_count查看池中可以使用的元素个数

三. 后记

mempool是DPDK内存管理的重要组件,这篇重点介绍了 mempool创建使用的过程,对于系统如何做大页映射,统一地址并没有涉及,希望在后面的篇幅中,关注一下大页的映射和共享内存等。再往后,会介绍驱动与收发包等联系较大的内容。

免责声明:文章转载自《DPDK数据包与内存专题-mempool内存池》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【微信小游戏】文件系统,远程加载资源打破4M限制Autodesk的照片建模云服务—Autodesk ReCap 360 photo下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CIOCPServer的数据结构定义及内存池方案

为了避免频繁的申请释放内存,使用内存池来管理缓冲区对象和客户上下文对象使用的内存。 使用指针保存所有空闲的内存块,形成空闲列表。 申请内存时,这个指针不为NULL,就从空闲列表中取出一个来使用,如果取完,就真正的申请内存。                   1 缓冲区对象                程序使用CIOCPBuffer来描述per-IO数...

dpdk kni二

[root@localhost dpdk-19.11]# ./usertools/dpdk-devbind.py -b igb_uio 0000:05:00.0 [root@localhost dpdk-19.11]# ./examples/kni/build/app/kni -c 0xFFFFF -n 4 -- -P -p 0x01 --config=...

DPDK 网络加速在 NFV 中的应用

目录 文章目录 目录 前文列表 传统内核协议栈的数据转发性能瓶颈是什么? DPDK DPDK 基本技术 DPDK 架构 DPDK 核心组件 应用 NUMA 亲和性技术减少跨 NUMA 内存访问 应用 CPU 绑核技术减少上下文切换损耗 应用大页内存技术减少 TLB miss 应用 PMD 轮询技术减少网卡外设的硬件中断 应用 UIO 和内存池技术减...

超全!iOS 面试题汇总

作者:Job_Yang 之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家。(题目来源于网络,侵删) 1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承;可以实现多个接口,通过实现多...

一种高效快速的内存池实现(附源码)

此算法灵感来自于apache内存池实现原理,不过读者如果没有看过apache内存池实现也无关系,因为本算法相对apache内存池算法更为简单而且易懂,个人认为某些场合也更为高效,或许真正到了apache服务器上性能不如,但是这套设计思想应该还是可以借鉴到更多场合的。 我们在调用malloc函数时,操作系统内部会查找一个所谓的空闲链表,当找到足够大的空闲空间...

初学者:ACE学习

ACE的配置(window) (使用VC++)安装: 1. 从网上下载相应源码――――根据提示编辑config.h文件,并放置在ACE_ROOT\ace 目录下。 2. 用VC打开ACE_ROOT\ace\ace.dsw ,并编译,编译后会在ACE_ROOT\lib 目录下生成两个库:ACEd.dll(动态库)和ACEd.lib(静态库)。 3. 链接:...