DPDK_API_rte_malloc源码分析-16.11
来源:互联网 发布:怎么样提升淘宝销量 编辑:程序博客网 时间:2024/04/30 13:08
概念:
librte_malloc库提供了一套用于管理内存空间的API接口,它管理的内存是hugepages上创建出来的memzone,而不是系统的堆空间。通过这套接口,可以提高系统访问内存的命中率,防止了在使用Linux用户空间环境的4K页内存管理时容易出现TLB miss。
以下内容基于DPDK 16.11版本。
接口函数:
void∗ rte_malloc( void ∗ptr, size_t size, unsigned align )
:用来替代malloc,从内存的huge_page中分配所需内存空间,分配的空间未被初始化。当size为0或者align不是2的整数倍的时候,返回NULL。如果align为0,则对齐方式为任意长。若空间不足或者参数错误(size=0或者align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。void ∗rte_zmalloc (const char∗type, size_t size, unsigned align)
:和rte_malloc基本相同,只是额外将申请的内存空间初始化为0。若空间不足或者参数错误(size=0或者align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。void ∗rte_calloc (const char∗type, size_t num, size_t size, unsigned align)
:用来替代calloc,和rte_malloc基本相同,申请的总空间大小为num * size,申请了num个连续空间,每个空间的大小为size。内存空间初始化为0。若空间不足或者参数错误(size=0或者align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。void ∗rte_realloc (void∗ptr, size_t size, unsigned align)
:用来替代realloc,重新分配ptr指向的内存空间的大小。如果size为0,则释放此ptr指向空间。若空间不足或者参数错误(align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。int rte_malloc_validate (void∗ptr, size_t∗size)
:如果debug宏被打开,那么此函数会检查ptr指向的内存空间的header和trailer标记是否正常,如果正常则将这个空间的长度写入到size中。当ptr无效或者pte指向的内存空间有错误时,函数返回-1,否则返回0。void rte_free( void ∗ptr )
:释放ptr指向的内存空间,和free函数基本一致。如果ptr为NULL,则不做任何改变。void rte_malloc_dump_stats (const char∗type)
:将指定的type的信息转存到控制台。如果type为NULL,则转存所有的内存type。int rte_malloc_set_limit (const char∗type, size_t max)
:设置type所能分配的最大内存。若执行成功则返回0,否则返回-1。
源码分析:
rte_malloc()
代码如下所示:
void *rte_malloc(const char *type, size_t size, unsigned align){ //直接调用rte_malloc_socket处理,指定的堆为程序运行的lcore所在的socket的堆 return rte_malloc_socket(type, size, align, SOCKET_ID_ANY);}
- rte_malloc_socket():在指定的堆上分配内存。
void *rte_malloc_socket(const char *type, size_t size, unsigned align, int socket_arg){ //声明mcfg,指向 rte_config->mem_config struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config; int socket, i; void *ret; /* return NULL if size is 0 or alignment is not power-of-2 */ //如果申请的内存size为0或者align对齐参数为0或者align不是2的整数倍,直接返回NULL if (size == 0 || (align && !rte_is_power_of_2(align))) return NULL; //如果 internal_config.no_hugetlbfs =1即程序在初始化的时候已经设置了不存在hugepage //则将socket_arg设为SOCKET_ID_ANY if (!rte_eal_has_hugepages()) socket_arg = SOCKET_ID_ANY; //如果socket_arg参数为SOCKET_ID_ANY,则获取当前运行程序的lcore对应的socket的id if (socket_arg == SOCKET_ID_ANY) socket = malloc_get_numa_socket(); //否则socket还为函数入参socket_arg else socket = socket_arg; /* Check socket parameter */ //参数检查。检查socket的Id是否可能大于最大值RTE_MAX_NUMA_NODES if (socket >= RTE_MAX_NUMA_NODES) return NULL; //调用malloc_heap_alloc开始处理内存申请 ret = malloc_heap_alloc(&mcfg->malloc_heaps[socket], type, size, 0, align == 0 ? 1 : align, 0); if (ret != NULL || socket_arg != SOCKET_ID_ANY) return ret; /* try other heaps */ //如果在指定的socket上或者在当前运行程序的lcore对应的socket上未能成功申请到内存 //则尝试其他的socket for (i = 0; i < RTE_MAX_NUMA_NODES; i++) { /* we already tried this one */ if (i == socket) continue; ret = malloc_heap_alloc(&mcfg->malloc_heaps[i], type, size, 0, align == 0 ? 1 : align, 0); if (ret != NULL) return ret; } return NULL;}
- malloc_heap_alloc()函数如下所示:
void *malloc_heap_alloc(struct malloc_heap *heap, const char *type __attribute__((unused)), size_t size, unsigned flags, size_t align, size_t bound){ struct malloc_elem *elem; //size参数和align参数做对齐操作,以RTE_CACHE_LINE_SIZE的大小做对齐 size = RTE_CACHE_LINE_ROUNDUP(size); align = RTE_CACHE_LINE_ROUNDUP(align); //自旋锁,加锁操作 rte_spinlock_lock(&heap->lock); //从堆中找到一个合适的元素用来作为malloc所申请的内存块 elem = find_suitable_element(heap, size, flags, align, bound); if (elem != NULL) { //将内存划分出去 elem = malloc_elem_alloc(elem, size, align, bound); /* increase heap's count of allocated elements */ heap->alloc_count++; } rte_spinlock_unlock(&heap->lock); //这里需要十分注意,return的指针指向的是data部分的头指针,即这块内存块向后偏移了1个elem结构体长度 //&elem[1]即elem的指针向后偏移 sizeof(elem)长度。 return elem == NULL ? NULL : (void *)(&elem[1]);}
- find_suitable_element()函数如下所示,
static struct malloc_elem *find_suitable_element(struct malloc_heap *heap, size_t size, unsigned flags, size_t align, size_t bound){ size_t idx; struct malloc_elem *elem, *alt_elem = NULL; //获取一个index,这个index和堆中的free list有关,free list如下所示: //在释放的时候,已经把相似大小的元素分类放在了不同的free list中: /* heap->free_head[0] - (0 , 2^8] * heap->free_head[1] - (2^8 , 2^10] * heap->free_head[2] - (2^10 ,2^12] * heap->free_head[3] - (2^12, 2^14] * heap->free_head[4] - (2^14, MAX_SIZE] */ //所以在申请的时候,我们就可以直接快速根据要申请的空间大小来匹配合适的free list。 //如果在该free list中没有找到合适的用来申请的空间元素,则会从更大的free list中查找。 for (idx = malloc_elem_free_list_index(size); idx < RTE_HEAP_NUM_FREELISTS; idx++) { //获取该free list对应的第一个元素(头节点之后的第一个节点) for (elem = LIST_FIRST(&heap->free_head[idx]); !!elem; elem = LIST_NEXT(elem, free_list)) { //判断是否可以获取到合适的elem用来作为申请的内存空间 if (malloc_elem_can_hold(elem, size, align, bound)) { //在这里flags的值为1,所以此函数直接返回了1 if (check_hugepage_sz(flags, elem->ms->hugepage_sz)) return elem; if (alt_elem == NULL) alt_elem = elem; } } } if ((alt_elem != NULL) && (flags & RTE_MEMZONE_SIZE_HINT_ONLY)) return alt_elem; return NULL;}
- malloc_elem_free_list_index():这个函数其实就是根据size的大小来返回对应的index值,运算规则其实是根据:
/* heap->free_head[0] - (0 , 2^8] * heap->free_head[1] - (2^8 , 2^10] * heap->free_head[2] - (2^10 ,2^12] * heap->free_head[3] - (2^12, 2^14] * heap->free_head[4] - (2^14, MAX_SIZE] */
来进行运算的。比如,如果size为2^7,则返回的index为0,如果size为2^10+1,则返回的index为3。
代码如下所示:
size_tmalloc_elem_free_list_index(size_t size){#define MALLOC_MINSIZE_LOG2 8#define MALLOC_LOG2_INCREMENT 2 size_t log2; size_t index; if (size <= (1UL << MALLOC_MINSIZE_LOG2)) return 0; /* Find next power of 2 >= size. */ log2 = sizeof(size) * 8 - __builtin_clzl(size-1); /* Compute freelist index, based on log2(size). */ index = (log2 - MALLOC_MINSIZE_LOG2 + MALLOC_LOG2_INCREMENT - 1) / MALLOC_LOG2_INCREMENT; return index <= RTE_HEAP_NUM_FREELISTS-1? index: RTE_HEAP_NUM_FREELISTS-1;}
- malloc_elem_can_hold():这个函数其实是直接调用的函数 elem_start_pt()。
- elem_start_pt():在elem中尝试分配所需要的size的内存空间,并且是按照对齐规则进行的。返回值为在elem节点中分配了所需的size之后的包含size所在空间的新elem。需要注意的是,每个elem对应的空间都有一段空间(MALLOC_ELEM_HEADER_LEN)是用来存放header信息的,这个header信息就是elem结构体。如果debug开关打开,每个elem对应的空间还会保存MALLOC_ELEM_TRAILER_LEN长度的一段信息。在malloc中,由于bound=0,所以check boundary这段程序实际上不会执行。
static void *elem_start_pt(struct malloc_elem *elem, size_t size, unsigned align, size_t bound){ //在malloc中,由于bound为0,所以bmask为0 const size_t bmask = ~(bound - 1); //计算elem的底部指针。elem->size是包含头部信息和MALLOC_ELEM_TRAILER_LEN、以及数据部分三个部分。 //这里的end_pt指向MALLOC_ELEM_TRAILER_LEN之前的空间。 uintptr_t end_pt = (uintptr_t)elem + elem->size - MALLOC_ELEM_TRAILER_LEN; //从elem的底部指针(不包含MALLOC_ELEM_TRAILER_LEN)向前倒推size长度,按照对齐方式进行, //new_data_start是倒推后得到的所要分配的空间的指针,此空间不包含头部信息 uintptr_t new_data_start = RTE_ALIGN_FLOOR((end_pt - size), align); uintptr_t new_elem_start; /* check boundary */ if ((new_data_start & bmask) != ((end_pt - 1) & bmask)) { end_pt = RTE_ALIGN_FLOOR(end_pt, bound); new_data_start = RTE_ALIGN_FLOOR((end_pt - size), align); if (((end_pt - 1) & bmask) != (new_data_start & bmask)) return NULL; } //new_elem_start是包含头部信息的,所要分配的空间的指针 new_elem_start = new_data_start - MALLOC_ELEM_HEADER_LEN; /* if the new start point is before the exist start, it won't fit */ //判断elem空间是否支持此new_elem存在 return (new_elem_start < (uintptr_t)elem) ? NULL : (void *)new_elem_start;}
malloc_elem_alloc():在前面的过程中,如果在尝试时发现可以在某个elem对应的空间中成功获得所要分配的空间,则调用此函数进行真正的分配操作。代码如下:
“`C
struct malloc_elem *
malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
size_t bound)
{
//在elem中根据所需的大小获得新的分配的空间,新的空间的头部信息为new_elem
//这一步一定是在之前已经成功执行过的,才会在此函数中再次进行
struct malloc_elem *new_elem = elem_start_pt(elem, size, align, bound);//elem在分出去new_elem后所剩余的部分,其实就是两个头指针相减
const size_t old_elem_size = (uintptr_t)new_elem - (uintptr_t)elem;//由于在分配内存的时候按照对齐进行的,所以有可能分配的新的new_elem的实际空间是大于size+sizeof(new_elem),
//那么old_elem_size实际上等于elem->size - size - align所需 小了一个对齐所用的空间
//所以trailer_size实际上就是align所需的多分配出来的空间,是小于align的
const size_t trailer_size = elem->size - old_elem_size - size -
MALLOC_ELEM_OVERHEAD;//将elem从free list中去除
elem_free_list_remove(elem);//如果trailer_size大于 MALLOC_ELEM_HEADER_LEN + MALLOC_ELEM_TRAILER_LEN + RTE_CACHE_LINE_SIZE
//也就是大于一个最小数据单元外加头部节点和MALLOC_ELEM_TRAILER_LEN的大小,就需要进行拆分
if (trailer_size > MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
/* split it, too much free space after elem *///获取需要再次截取的new_free_elem的地址,即new_elem向后偏移一个//MALLOC_ELEM_HEADER_LEN + MALLOC_ELEM_TRAILER_LEN,然后偏移new_elem的sizestruct malloc_elem *new_free_elem = RTE_PTR_ADD(new_elem, size + MALLOC_ELEM_OVERHEAD);//将new_free_elem从elem中分拆出来split_elem(elem, new_free_elem);//将new_free_elem加入free list,加入的时候还要遵循上面列出的free list表大小规则,加入到对应的表中malloc_elem_free_list_insert(new_free_elem);
}
//如果旧的old_elem的剩余空间不足MALLOC_ELEM_HEADER_LEN + MALLOC_ELEM_TRAILER_LEN + MIN_DATA_SIZE
//则不进行拆分
if (old_elem_size < MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
/* don’t split it, pad the element instead */
elem->state = ELEM_BUSY;
elem->pad = old_elem_size;/* put a dummy header in padding, to point to real element header *///填充头部结构体信息if (elem->pad > 0){ /* pad will be at least 64-bytes, as everything * is cache-line aligned */ new_elem->pad = elem->pad; new_elem->state = ELEM_PAD; new_elem->size = elem->size - elem->pad; set_header(new_elem);}return new_elem;
}
/* we are going to split the element in two. The original element
- remains free, and the new element is the one allocated.
- Re-insert original element, in case its new size makes it
- belong on a different list.
*/
//如果旧的old_elem的剩余空间大于等于MALLOC_ELEM_HEADER_LEN + MALLOC_ELEM_TRAILER_LEN + MIN_DATA_SIZE
//则进行拆分
split_elem(elem, new_elem);
new_elem->state = ELEM_BUSY;
malloc_elem_free_list_insert(elem);
return new_elem;
}
## rte_zmalloc() 代码如下所示: ```Cvoid *rte_zmalloc_socket(const char *type, size_t size, unsigned align, int socket){ return rte_malloc_socket(type, size, align, socket);}void *rte_zmalloc(const char *type, size_t size, unsigned align){ return rte_zmalloc_socket(type, size, align, SOCKET_ID_ANY);}<div class="se-preview-section-delimiter"></div>
和rte_malloc()完全一致,这里没有做memset处理将内存空间初始化为0,不晓得为什么,难道是BUG?
rte_calloc()
代码如下所示:
void *rte_calloc_socket(const char *type, size_t num, size_t size, unsigned align, int socket){ return rte_zmalloc_socket(type, num * size, align, socket);}void *rte_calloc(const char *type, size_t num, size_t size, unsigned align){ return rte_zmalloc(type, num * size, align);}<div class="se-preview-section-delimiter"></div>
和rte_malloc()完全一致,神奇的是,这个函数的注释竟然和rte_zmalloc一样:“Allocate zero’d memory on specified heap.”,并且直接调用的是rte_zmalloc(),那么函数rte_calloc_socket()是准备干嘛? … are you kidding me?
rte_realloc()
代码如下所示:
void *rte_realloc(void *ptr, size_t size, unsigned align){ if (ptr == NULL) return rte_malloc(NULL, size, align); //获取到elem的地址。传入的ptr指向的是那块内存空间的data部分的起始地址,在那个之前还有elem结构体 //故需要偏移elem的长度来获取elem结构体的首地址,即整个内存区块的首地址 struct malloc_elem *elem = malloc_elem_from_data(ptr); if (elem == NULL) rte_panic("Fatal error: memory corruption detected\n"); //将size和align按照RTE_CACHE_LINE_SIZE做对齐操作 size = RTE_CACHE_LINE_ROUNDUP(size), align = RTE_CACHE_LINE_ROUNDUP(align); /* check alignment matches first, and if ok, see if we can resize block */ //检查ptr是否是以align为对齐的,并尝试直接在原内存空间中直接进行resize操作 if (RTE_PTR_ALIGN(ptr,align) == ptr && malloc_elem_resize(elem, size) == 0) return ptr; /* either alignment is off, or we have no room to expand, * so move data. */ //若ptr不以align为对齐或者原内存空间中直接resize失败,则重新malloc,调用rte_malloc函数 void *new_ptr = rte_malloc(NULL, size, align); if (new_ptr == NULL) return NULL; //将ptr中的数据搬移到新申请的内存空间中 const unsigned old_size = elem->size - MALLOC_ELEM_OVERHEAD; rte_memcpy(new_ptr, ptr, old_size < size ? old_size : size); //释放之前ptr指向的内存空间 rte_free(ptr); return new_ptr;}<div class="se-preview-section-delimiter"></div>
intmalloc_elem_resize(struct malloc_elem *elem, size_t size){ //所需的大小,即size加上 MALLOC_ELEM_HEADER_LEN 和 MALLOC_ELEM_TRAILER_LEN const size_t new_size = size + MALLOC_ELEM_OVERHEAD; /* if we request a smaller size, then always return ok */ //当前的内存区块的大小,减去填充的大小 const size_t current_size = elem->size - elem->pad; //如果这个resize实际上是将原来的内存块大小变小,则直接返回成功 //但是我觉得这里是不是应该有点问题?难道不需要重新做拆分或者做pad吗? //什么都不做直接返回合适吗? if (current_size >= new_size) return 0; //获取紧跟在此elem的后一个elem的头指针 struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size); rte_spinlock_lock(&elem->heap->lock); //判断是否是free状态 if (next ->state != ELEM_FREE) goto err_return; //判断这两个elem加起来的大小是否可以满足resize的需求,如果不足返回失败 //那么问题来了,仅仅判断相邻的两个elem,不判断三个或更多的情况吗? if (current_size + next->size < new_size) goto err_return; /* we now know the element fits, so remove from free list, * join the two */ //将next elem从free list中去除,并合并这两个elem elem_free_list_remove(next); join_elem(elem, next); //如果剩余空间过大,则进行拆分 if (elem->size - new_size >= MIN_DATA_SIZE + MALLOC_ELEM_OVERHEAD){ /* now we have a big block together. Lets cut it down a bit, by splitting */ struct malloc_elem *split_pt = RTE_PTR_ADD(elem, new_size); split_pt = RTE_PTR_ALIGN_CEIL(split_pt, RTE_CACHE_LINE_SIZE); split_elem(elem, split_pt); malloc_elem_free_list_insert(split_pt); } rte_spinlock_unlock(&elem->heap->lock); return 0;err_return: rte_spinlock_unlock(&elem->heap->lock); return -1;}
rte_malloc_validate()
代码如下所示:
intrte_malloc_validate(const void *ptr, size_t *size){ const struct malloc_elem *elem = malloc_elem_from_data(ptr); if (!malloc_elem_cookies_ok(elem)) return -1; if (size != NULL) *size = elem->size - elem->pad - MALLOC_ELEM_OVERHEAD; return 0;}
此函数会检查ptr指向的内存空间的header和trailer标记是否正常,如果正常则将这个空间的长度写入到size中。
rte_free()
代码如下所示:
void rte_free(void *addr){ if (addr == NULL) return; if (malloc_elem_free(malloc_elem_from_data(addr)) < 0) rte_panic("Fatal error: Invalid memory\n");}
如果传入的指针为空,则不作任何处理。
如果传入的指针不为空,则找到这块内存块的真正起始地址(通过malloc_elem_from_data(),将addr向前偏移一个elem结构体长度),然后调用malloc_elem_free()处理。
释放的过程其实主要就是做了内存块free链表的操作,将新释放的内存块加入到free链表中。加入的策略如下:
- 如果此被释放的内存块的next指针指向的内存块为free,则将它和后一个内存块合并,并将后一个内存块从free链表中去除;
- 如果此被释放的内存块的pre指针指向的内存块为free,则将它和前一个内存块亦进行合并;
- 将合并后(或不满足合并条件则不合并)的内存块,插入到对应free链表的头部。这里的对应值得是根据此内存块的大小进行匹配的,上文中已经描述过。
- DPDK_API_rte_malloc源码分析-16.11
- DPDK_API_rte_malloc源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 【Android应用源码分析】HandlerThread 源码分析
- 【Android应用源码分析】IntentService 源码分析
- java源码分析01-Object源码分析
- VC++源码分析 - 中国象棋源码分析
- [Java源码分析]ArrayList源码分析
- LeetCode-29. Divide Two Integers
- CentOS安装jdk的三种方法
- java多线程
- ListView复用和优化详解
- Mac中使用MATLAB进行simulink代码生成
- DPDK_API_rte_malloc源码分析-16.11
- Override和Overload的含义去区
- c++ 二维动态数组初始化及作为参数传递
- rapid-db-conn: 用于快速开发的数据库连接类库(Java)
- 树莓派网易云音乐播放器
- gcd的一些性质
- React-Router 中文简明教程(中)
- 0-1背包问题(电子科大饭卡)dp
- 2017年伊始,你需要尝试的25个Android第三方库