(转)DPDK内存管理 04 ---- rte_malloc内存管理

内存分配组织结构
一个socket上所有的可用内存为一个堆。每个堆由大小不同的块组成,每个块是一个连续的存储器片。申请内存时可以指定堆(socket),或者任意堆,当指定为任意堆时,会优先使用本地堆(本地socket)。

结合前面说的dpdk内存初始化,每个堆的原始块是相同socket_id的所有memseg。每个memseg段就是一个地址连续的块。内存申请(rte_malloc)就是把一个堆中合适大小的块分割成申请大小的块返回,剩下的块可以继续被申请使用,成为空闲块;内存释放(rte_free)就是把一个申请的块返回到内存,放置到空闲链表中(可能会合并相邻的空闲块)。

dpdk的内存分配采用内存管理中常见的隐式空闲链表方法。内存分配的结构可以用下图表示:

说明:

  • malloc_heaps[]表示堆数组,每个数组元素malloc_heaps[i]表示一个socket i上的堆。
  • 每个堆(struct malloc_heap malloc_heaps[i])都有一个空闲堆数组free_heap[]。再次划分空闲堆数组,是为了方便的定位到合适大小的块,free_heap[]是按块的大小划分块所属的free_heap的。例如,free_heap[3]维护的是块的大小在2^12~2^14之间的所有空闲块。

空闲链表
空闲堆数组中保存着一个空闲链表,所有的空闲链表维护的内存块,就是该堆上可用内存。而其中的每个空闲块是由一个空闲链表组织起来的。每个节点都有一个内存块头(struct malloc_elem{})组成,当开启debug模式时,块的尾部还会有一个trailer尾。注意:在最开始初始化堆时,每个块都是最大的连续块,这时把这个最大的内存块在尾部设置一个哨兵元素(为了分割、合并空闲块),该哨兵元素同样是struct malloc_elem结构,但它的可用状态永远是已分配(busy)状态,大小为0。

查找空闲块

其中struct malloc_elem{}的几个比较重要的字段。

  • prev指向内存块上一个内存块的头。
  • state:busy/free。表示该内存块是否是空闲块。
  • size表示该空闲块的大小(包括内存块头和尾(如果debug模式))。

通过prev字段,索引到上个相邻的内存块(合并),通过内存头地址va+size索引到下个相邻的内存块。这样所有相邻的内存块都联系起来了。

分割空闲块(申请内存)
一个空闲块在空闲链表中的结构如下图:

注意:在非debug模式下,trailer为0。

首次分割空闲块
如下图:


主要步骤:

  1. 把哨兵内存块的prev指针指向上个空闲块的head。
  2. 把空闲块的prev指针设置为NULL。
  3. 把空闲块的size设置为head到下个内存块(哨兵)head的长度。
  4. 把哨兵的state设置为busy,把空闲块的state设置为free。
  5. 根据size,把空闲块插入到合适的空闲链表中。哨兵(busy状态)并不加入空闲链表中,空闲块和哨兵之间是通过隐式链连接起来的。

后续分割空闲块
如下图:

  1. 当一个应用请求一个K字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。搜索的方式有多种(首次适配,下次适配,最佳适配),称为放置策略。dpdk的放置策略是先找到首个满足大小k的区间,例如是2^12~2^14;再从区间2^12~2^14对应的空闲链表中依次检查节点是否符合,如果不符合再从下个区间搜索。假设找到空闲块H。
  2. 把H从空闲链表中删除。
  3. 根据malloc指定的align、bound参数,调整B的data大小,但必须要大于请求的K。
  4. 把块B的prev指针指向块A的head。设置块B的状态为busy。
  5. 根据H块的size找到相邻的busy块C,把C的prev修改为指向B。
  6. 调整A的size。
  7. 把A加入空闲链表中。

合并空闲块(释放内存)

释放malloc块B。

释放B有几种情况:

a)     A、C都是free状态。

b)     A是free状态,B是busy状态。

c)     A是busy状态,B是free状态。

d)     A、B都是busy状态。

假如是d),则B不需要合并,直接找到合适的空闲链表,插入即可。

现假如A是free,C是busy举例说明:

  1. 根据B的head+size找到C的head,发现状态是busy,不合并。
  2. 根据B的prev找到A,发现A的状态是free,则合并。
  3. 把A从空闲链表中删除。
  4. A、B合并之后的size是A、B的size之和。
  5. 并且把C的prev指向A的head。
  6. 把B内存清零。
  7. 把A、B合并之后的空闲块插入到合适的空闲链表中。
     

===============================================================================

rte_malloc()为程序运行过程中分配内存,模拟从堆中动态分配内存空间。

1 void *
2 rte_malloc(const char *type, size_t size, unsigned align)
3 {
4     return rte_malloc_socket(type, size, align, SOCKET_ID_ANY);
5 }

rte_malloc()函数调用关系如下图:

rte_malloc_socket():指定从哪个socket上分配内存空间,默认是指定SOCKET_ID_ANY,即,程序在哪个socket上运行,就从哪个socket上分配内存。如果指定的socket上没有合适的内存空间,就再从其它socket上分配。

malloc_heap_alloc():从rte_config.mem_config->malloc_heaps[]数组中找到指定socket对应的堆(使用struct malloc_heap描述堆),即,从这个堆中分配空间。如果该堆是第一次使用,还没有被初始化过,则调用malloc_heap_init()初始化;首先,调用find_suitable_element()在堆中查找是否有合适内存可以分配,如果没有,则调用malloc_heap_add_memzone()在rte_config.mem_config->memzone[]中给堆分配一块内存。最后,调用malloc_elem_alloc()在堆中,将需要分配的内存划分出去。

 1 void *
 2 malloc_heap_alloc(struct malloc_heap *heap,
 3         const char *type __attribute__((unused)), size_t size, unsigned align)
 4 {
 5     if (!heap->initialised)
 6         malloc_heap_init(heap);
 7 
 8     size = CACHE_LINE_ROUNDUP(size);
 9     align = CACHE_LINE_ROUNDUP(align);
10     rte_spinlock_lock(&heap->lock);
11     struct malloc_elem *prev, *elem = find_suitable_element(heap,
12             size, align, &prev);
13     if (elem == NULL){
14         if ((malloc_heap_add_memzone(heap, size, align)) == 0)
15             elem = find_suitable_element(heap, size, align, &prev);
16     }
17 
18     if (elem != NULL){
19         elem = malloc_elem_alloc(elem, size, align, prev);
20         /* increase heap's count of allocated elements */
21         heap->alloc_count++;
22     }
23     rte_spinlock_unlock(&heap->lock);
24     return elem == NULL ? NULL : (void *)(&elem[1]);
25 
26 }

malloc_heap_init():主要是为struct malloc_heap数据结构的各个成员变量赋初始值,并将该堆的状态设置为INITIALISED。

malloc_heap_add_memzone():调用rte_memzone_reserve(),在rte_config.mem_config->memzone[]中分配合适大小的内存。分配的内存的大小是mz_size = MAX(min_size, 11M),其中,min_size = size + align + MALLOC_ELEM_OVERHEAD * 2; size是rte_malloc()指定的需要分配内存的大小。如果memzone[]中没有合适的内存块,将mz_size减半,再次查找。

1 do {
2     mz = rte_memzone_reserve(mz_name, mz_size, numa_socket,
3                  mz_flags);
4     if (mz == NULL)
5         mz_size /= 2;
6 } while (mz == NULL && mz_size > min_size);

find_suitable_element():在堆中找到一块合适大小的内存,分配的内存是从堆的底部开始查找的。如果堆剩余内存不够分配的,会再次调用malloc_heap_add_memzone()扩展堆的大小。

malloc_elem_alloc():查找到合适大小的内存块后,将这一块内存从堆中划分出去。

还是直接上图直接点。。。。

猜你喜欢

转载自blog.csdn.net/armlinuxww/article/details/89705645