DPDK : 内存管理和分配的解析

说明

       这一篇文章主要是对DPDK的EAL(Environment Abstraction Layer)中内存管理和分配的解析,这是DPDK中ring, mempool, mbuf等的基础。
       由于个人水平所限,若所写的博文中存在错误,希望大家能帮忙指出。

一,DPDK的内存模式

       DPDK存在两种内存模式:

	1, legacy mode : 这一种为静态内存模式,即在初始化过程就根据配置分配所有的内存,并且这些内存页在application结束之前
不会归还给OS;而且也不会再向OS申请更多的内存。并且application使用DPDK的rte_malloc库申请内存时,若剩余可用的内存不足,则
直接返回错误。这种模式的优点是若一段内存空间的虚拟内存地址是连续的,那么其物理内存地址也是连续的。

	2, dynamic mode:这一种为动态内存模式,在初始化过程根据配置分配所需要的内存,并且这些内存页在application结束之前不会
归还给OS。若application使用DPDK的rte_malloc库申请内存时,若剩余可用的内存不足,则会先向操作系统申请内存,再将这些内存分配
给application。由于会动态地向OS申请内存,所以虚拟内存连续并不意味着物理内存连续。
	(对于向操作系统动态申请的内存,在application释放这些内存时,DPDK系统会将其归还给OS,不必等到application结束)

二,相关结构体的说明

       下面,先对一些结构体的字段进行说明(参考后面的图例能更容易的理解),能够帮助对后面的理解:

**********************************malloc_elem.h*****************************
struct malloc_elem {
	struct malloc_heap *heap;
	struct malloc_elem *volatile prev; //指向上一个malloc_elem
	/**< points to prev elem in memseg */
	struct malloc_elem *volatile next;  // 指向下一个malloc_elem
	/**< points to next elem in memseg */
	LIST_ENTRY(malloc_elem) free_list; //指向下一个free malloc_elem
	/**< list of free elements in heap */
	struct rte_memseg_list *msl;
	volatile enum elem_state state; //表明malloc_elem的状态: free, 或者busy
	uint32_t pad; //填充无效的内存区域
	size_t size; // 表示malloc_elem所表示的内存区域的大小(包含struct malloc_elem), 以字节为单位
	struct malloc_elem *orig_elem;  //指向最初的malloc_elem(即内存初始化时的malloc_elem)
	size_t orig_size;	//最初的malloc_elem的内存区域的大小
#ifdef RTE_MALLOC_DEBUG
	uint64_t header_cookie;         /* Cookie marking start of data */
	                                /* trailer cookie at start + size */
#endif
} __rte_cache_aligned;
**********************************rte_malloc_heap.h*****************************
struct malloc_heap {
	rte_spinlock_t lock;
	LIST_HEAD(, malloc_elem) free_head[RTE_HEAP_NUM_FREELISTS];
	/*
		free_head用于存放free malloc_elem的位置
		free_head[0] 存放由大小为(0, 2^8]的free malloc_elem组成的链表头指针
		free_head[1] 存放由大小为(2^8, 2^10]的free malloc_elem组成的链表头指针
		free_head[2] 存放由大小为(2^10, 2^12]的free malloc_elem组成的链表头指针
		free_head[3] 存放由大小为(2^12, 2^14]的free malloc_elem组成的链表头指针
		······
		以此类推
	*/
	struct malloc_elem *volatile first;	//指向heap的第一个malloc_elem
	struct malloc_elem *volatile last; // 指向heap的最后一个malloc_elem

	unsigned alloc_count; //已经分配的malloc_elem的数量
	unsigned int socket_id;
	size_t total_size; //表示此heap所包含的内存(以字节为单位)
	char name[RTE_HEAP_NAME_MAX_LEN];
} __rte_cache_aligned;

三,DPDK内存管理结构

       在这一篇文章中,给出了一个初始化后的heap结构图。
       在这里,我们抛弃struct rte_memseg_list, struct rte_memseg的观点。单纯地从struct malloc_heap和struct malloc_elem的角度来看DPDK的内存管理,如下图所示 :
DPDK : 内存管理和分配的解析_第1张图片
       这是一个刚初始化完成的heap,只包含free malloc_elem。
       可以看出DPDK的内存管理主要由一个双向链表组成:malloc_heap包含first, last指针,分别指向第一个malloc_elem和最后一个malloc_elem;每一个malloc_elem有一个prev和next指针(如图中黑色的线条所示)。图中蓝色的线表示的是由malloc_heap中的 free_head 和malloc_elem中的 free_list 构成的free malloc_elem的链表。

       DPDK中的rte_malloc库中,application使用 rte_malloc() 方法向DPDK的运行时系统申请内存,rte_free() 释放内存。其它向heap申请或释放内存的方法基本也是调用这两个方法 :
       1,rte_malloc() : 这个方法主要是调用了 malloc_heap_alloc()(位于malloc_heap.c) 进行分配内存。并且每次分配时,都会从一个free malloc_elem的末尾开始分配
       2,rte_free() : 这个方法主要是调用了 malloc_heap_free()(位于malloc_heap.c) 将内存释放回DPDK系统。
       (关于代码,后面将给出相关的函数调用图,能够帮助理解DPDK的源代码)



       下面用一个场景说明内存的分配和释放过程:
       一, rte_malloc()分配一块内存后,malloc_heap变化如下 :
DPDK : 内存管理和分配的解析_第2张图片
       分配完后的结果如上图所示,每一次分配的基本步骤如下:
       1, 根据需要分配的内存的大小从对应的free_head中查找到合适的free malloc_elem,如果没有,则向OS申请额外的内存(通过eal_memalloc_alloc_seg_bulk() 申请),作为新的free malloc_elem加入到heap中,对其进行扩展。
       2, 从free malloc_elem的末尾开始分配内存,然后将free malloc_elem划分为两个malloc_elem, 后面一个标记为busy malloc_elem
       3, 修改malloc_elem的prev, next指针,并且将剩余的free malloc_elem加入到合适的free链表的头部。

       二,执行rte_free()释放刚才分配的内存,malloc_heap的变化如下:
DPDK : 内存管理和分配的解析_第3张图片
       释放完后的结果如上图所示,每一次释放的基本步骤如下:
       1, 将要进行释放的busy malloc_elem的状态标记为free
       2, 检查有没有在内存上并且也是free的malloc_elem, 存在的话就合并为一个大的free malloc, 然后加入到合适的free链表的头部。
       3, 检查能否归还向OS申请的额外内存,如果可以的话,就将其归还给OS(通过 eal_memalloc_free_seg_bulk() 归还)。

四,底层实现

       在这一篇文章提到 : DPDK在memalloc时有两种模式single-file-segmentspage-per-file。 每一种都在hugetlbfs的挂载点上有相应的文件形式(即存在于内存中的文件),这样在内存分配时可以使用对file descriptor操作的系统调用对内存进行操作。
       下面对这两种模式进行进一步的解释 :
       single-file-segment的映射关系如下:DPDK : 内存管理和分配的解析_第4张图片
       蓝色表示的是内存文件所占用的物理内存,对于一个rte_memseg_list会创建一个文件,rte_memseg_list中的mem_segment会和文件的其中一部分进行映射。

       page-per-file的映射关系如下:

DPDK : 内存管理和分配的解析_第5张图片
       蓝色表示的是内存文件所占用的物理内存,对于一个mem_segment会创建一个文件,并且mem_segment会和对应的文件进行映射。

1,对于legacy mode,DPDK系统会在hugetlbfs的挂载点上创建相应的内存文件。	
2,对于dynamic mode,关于内存文件有两种形式:
	  若internal_config.in_memory(位于eal_internal_cfg.h)为true,则使用memfd_create(),
   或MAP_ANONYMOUS | MAP_HUGETLB 创建anonymous file。
	  若internal_config.in_memory(位于eal_internal_cfg.h)为false, 则在hugetlbfs的挂载点上创建相应的内存文件。

   这样,DPDK系统能够通过一系列和文件相关的接口(比如open, close, fallocate, ftruncate等)对内存文件进行操作。
所以在dynamic mode下,DPDK向系统申请内存,其实就是通过fallocate, 或者ftruncate修改内存文件的大小,从而获取更多的物理内存。
而将内存归还给OS,就是使用unlink(), 删除对应的内存文件,释放文件所占用的物理内存。
	从application的角度看,每次使用rte_malloc()申请内存时,DPDK会分配一个合适的malloc_elem管理这一段申请的内存(一个hugepage中可能包含一个或多个malloc_elem)。
而使用rte_free()释放内存时,就是将malloc_elem的状态设置为free,然后合并相邻的free malloc_elem即可。

五,总结

       这篇文章简单地介绍了DPDK的内存管理和分配:
       1,每一个malloc_heap都由一个由malloc_elem为节点的双向链表组成。
       2,DPDK根据free malloc_elem所管理内存的大小将free malloc_elem划分到不同的free_head中。每次在分配内存时,可以快速地找到合适的free malloc_elem, 而不用遍历所有的free malloc_elem。采取的策略是首次适应算法(First Fit):从合适的free_head的头节点开始查找该表,把最先能够满足要求的空闲区分配给用户。
       3, DPDK的内存管理的底层实现是通过memory-file,和相关的文件接口实现的。

六,函数调用图

       下面给出的是内存管理和分配相关的代码的函数调用图(所阅读的代码是来自DPDK官方最新的代码https://github.com/DPDK/dpdk, 如果官方代码更新,文章也会尽快地更新)
       1,malloc_elem : http://naotu.baidu.com/file/cda0a195d0f53d9a1c01052cedd91a3b?token=7100881a9cdcaf7e
       2,malloc_heap :http://naotu.baidu.com/file/0617659be9481990d1e5c18986b0af32?token=227982f46fc62c42
       3,eal_memalloc :http://naotu.baidu.com/file/39af99626733c35dad7850de8b4f11f9?token=07d7fb5bf46d49f6

你可能感兴趣的:(DPDK源代码的解析)