DPDK_API_rte_malloc源碼分析

概念:

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章