概念:

librte_malloc库提供了一套用于管理内存空间的API接口,它管理的内存是hugepages上创建出来的memzone,而不是系统的堆空间。通过这套接口,可以提高系统访问内存的命中率,防止了在使用Linux用户空间环境的4K页内存管理时容易出现TLB miss。

这是基于老版本(INTELDPDK.L.1.2.3_3版本)所写的,后来又看了下16.11版本的,已经有了极大的变化。
如果想了解新版本的机制,请看另一篇文章《DPDK_API_rte_malloc源码分析-16.11》。


接口函数:

  1. 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,若执行成功则返回分配的内存空间的起始地址。
  2. void ∗rte_zmalloc (const char∗type, size_t size, unsigned align):和rte_malloc基本相同,只是额外将申请的内存空间初始化为0。若空间不足或者参数错误(size=0或者align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。
  3. 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,若执行成功则返回分配的内存空间的起始地址。
  4. void ∗rte_realloc (void∗ptr, size_t size, unsigned align):用来替代realloc,重新分配ptr指向的内存空间的大小。如果size为0,则释放此ptr指向空间。若空间不足或者参数错误(align不是2的整数倍)则函数返回NULL,若执行成功则返回分配的内存空间的起始地址。
  5. int rte_malloc_validate (void∗ptr, size_t∗size):如果debug宏被打开,那么此函数会检查ptr指向的内存空间的header和trailer标记是否正常,如果正常则将这个空间的长度写入到size中。当ptr无效或者pte指向的内存空间有错误时,函数返回-1,否则返回0。
  6. void rte_free( void ∗ptr ):释放ptr指向的内存空间,和free函数基本一致。如果ptr为NULL,则不做任何改变。
  7. void rte_malloc_dump_stats (const char∗type):将指定的type的信息转存到控制台。如果type为NULL,则转存所有的内存type。
  8. 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)
{
    unsigned malloc_socket = malloc_get_numa_socket();
    /* return NULL if size is 0 or alignment is not power-of-2 */
    if (size == 0 || !rte_is_power_of_2(align))
        return NULL;
    return malloc_heap_alloc(&malloc_heap[malloc_socket], type,
            size, align == 0 ? 1 : align);
}

主要包含两个函数调用:
- malloc_get_numa_socket():获取程序分配的socket的id。程序在哪个socket上运行,就从哪个socket上分配内存。
- malloc_heap_alloc():将传入的需要malloc的空间大小和align按照CACHE_LINE_SIZE做了对齐。

size = CACHE_LINE_ROUNDUP(size);
align = CACHE_LINE_ROUNDUP(align);

在rte_config.mem_config->malloc_heaps[]数组里,此socket对应的堆中,进行匹配,查找是否有合适内存可以分配。find_suitable_element()用来在堆中找到一块合适大小的内存,分配的内存是从堆的底部开始查找的。

struct malloc_elem *prev, *elem = find_suitable_element(heap,
size, align, &prev);

如果没有找到合适的空间,则需要调用malloc_heap_add_memzone()在rte_config.mem_config->memzone[]中给堆分配一块内存:

if (elem == NULL){
    malloc_heap_add_memzone(heap, size, align);
    elem = find_suitable_element(heap, size, align, &prev);
}

调用malloc_elem_alloc()在堆中,将需要分配的内存划分出去。

if (elem != NULL)
    elem = malloc_elem_alloc(elem, size, align, prev);

注意,在malloc_elem_alloc()中,采用的也是动态划分内存块的方式,即,如果当前适用于分配的内存块大于MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE,就会将这块内存拆分开,前一部分继续还放在原有的可用于分配的内存堆中,后一部分作为malloc的内存块。详见malloc_elem_alloc()函数。

rte_zmalloc()

代码如下所示:

void *
rte_zmalloc(const char *type, size_t size, unsigned align)
{
    void *ptr = rte_malloc(type, size, align);

    if (ptr != NULL)
        memset(ptr, 0, size);
    return ptr;
}

直接调用的rte_malloc(),仅仅是做了memset处理,将分配的内存空间初始化为0。

rte_calloc()

代码如下所示:

void *
rte_calloc(const char *type, size_t num, size_t size, unsigned align)
{
    return rte_zmalloc(type, num * size, align);
}

直接调用的rte_zmalloc,仅仅是输入参数不同而已。

rte_realloc()

代码如下所示:

void *
rte_realloc(void *ptr, size_t size, unsigned align)
{
    if (ptr == NULL)
        return rte_malloc(NULL, size, align);

    struct malloc_elem *elem = malloc_elem_from_data(ptr);
    if (elem == NULL)
        rte_panic("Fatal error: memory corruption detected\n");

    size = CACHE_LINE_ROUNDUP(size), align = CACHE_LINE_ROUNDUP(align);
    /* check alignment matches first, and if ok, see if we can resize block */
    if (RTE_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. */
    void *new_ptr = rte_malloc(NULL, size, align);
    if (new_ptr == NULL)
        return NULL;
    const unsigned old_size = elem->size - MALLOC_ELEM_OVERHEAD;
    rte_memcpy(new_ptr, ptr, old_size < size ? old_size : size);
    rte_free(ptr);

    return new_ptr;
}

如果输入参数ptr为NULL,那么功能和rte_malloc完全一致。
首先会尝试从当前传入的内存块之后,找到一个连续的可分配空间。调用malloc_elem_resize()来实现,在当前传入的内存块之后,找到一个连续的可分配空间,如果当前内存块的大小和这个连续内存块的可用空间之和满足分配的要求,那么就进行分配。
如果未能成功分配,则会调用rte_malloc()进行分配,之后把传入的内存块进行free,并将原内存中的数据搬移到新分配的内存块中。

rte_malloc_validate()

代码如下所示:

int
rte_malloc_validate(void *ptr, size_t *size)
{
    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()),然后调用malloc_elem_free()处理。
释放的过程其实主要就是做了内存块free链表的操作,将新释放的内存块加入到free链表中。加入的策略如下:
- 如果此被释放的内存块的next指针指向的内存块为free,则将它和后一个内存块合并,并将后一个内存块从free链表中去除;
- 如果此被释放的内存块的pre指针指向的内存块为free,则将它和前一个内存块亦进行合并;
- 将合并后(或不满足合并条件则不合并)的内存块,插入到free链表的头部。

rte_malloc_dump_stats()

代码如下所示:

void
rte_malloc_dump_stats(__rte_unused const char *type)
{
    return;
}

并未做任何处理,所以和API手册中的说明似乎不符?至少在INTELDPDK.L.1.2.3_3版本未做实现,源码中还标识着TODO。

rte_malloc_set_limit()

代码如下所示:

int
rte_malloc_set_limit(__rte_unused const char *type,
        __rte_unused size_t max)
{
    return 0;
}

并未做任何处理,所以和API手册中的说明似乎不符?至少在INTELDPDK.L.1.2.3_3版本未做实现,源码中还标识着TODO。

Logo

更多推荐