DPDK內存管理(1)


1 前言

 

DPDK將利用hugepage預留的物理內存統一的組織管理起來,然后以庫的方式對外提供使用的接口。下圖展示了DPDK中內存有關的模塊的相互關系。

rte_eal            是統一的組織管理者(當然rte_eal不只是做內存的工作)

rte_malloc       對外提供分配釋放內存的API,分配的內存都是rte_eal中管理的內存

rte_ring          提供無鎖隊列,他之間使用了rte_eal管理的內存

rte_mempool  利用rte_eal中的內存和rte_ring提供內存池的功能

ret_mbuf       

 

下面我們一個一個分析各個模塊是怎樣處理及使用內存的(重點訴說流程,不會詳細的介紹代碼),DPDK管理的內存有個很有用的特點--同一塊物理內存在不同的進程地址空間中,虛擬地址是一樣的。下面會詳細的說明DPDK是怎樣實現這個功能的。

首先hugepage這里就不描述了,要理解DPDK的內存管理必須知道hugepage,不了解的自行補齊吧,linux內核中有個文檔介紹的很詳細。可以點擊這里查看。

2 rte_eal 對內存的組織與管理

這塊是在rte_eal中實現的。有關的函數有:

rte_eal_init -> eal_hugepage_info_init

rte_eal_init -> rte_eal_memory_init

rte_eal_init -> rte_eal_memzone_init

rte_eal_init -> eal_check_mem_on_local_socket

 

2.1 eal_hugepage_info_init

首先DPDK管理的內存都是通過hugepage預留的,這個函數主要獲取預留的內存的信息,獲取方法是讀取 /sys/kernel/mm/hugepages 下的文件,具體還是參考hugepage的文檔吧,這里不詳細描述。

預留的內存按頁大小分類( i386 architecture supports 4K and 4M (2M in PAE mode) page sizes, ia64 architecture supports multiple page sizes 4K, 8K, 64K, 256K, 1M, 4M, 16M, 256M and ppc64 supports 4K and 16M. ),可能會有多類,DPDK會把這些信息全部讀取,使用struct hugepage_info進行保存,每一類對應一個struct hugepage_info類型的對象,所有的這些對象保存在struct internal_config的數組中。DPDK有個internal_config的全局變量,記錄這些信息。

 

struct hugepage_info {
    size_t hugepage_sz;   /**< size of a huge page */
    const char *hugedir;    /**< dir where hugetlbfs is mounted */
    uint32_t num_pages[RTE_MAX_NUMA_NODES];
                /**< number of hugepages of that size on each socket */
    int lock_descriptor;    /**< file descriptor for hugepage dir */
};

 

struct internal_config {
   ...
unsigned num_hugepage_sizes; /**< how many sizes on this system */ struct hugepage_info hugepage_info[MAX_HUGEPAGE_SIZES]; };

 

 

struct internal_config internal_config;

 

總結 eal_hugepage_info_init,就是獲取了系統中預留的內存信息,有幾類(相同頁大小的是一類),將每一類的信息(頁大小、頁數、掛載點)存儲在internal_config.hugepage_info數組中。hugepage_info數組也進行了排序,大頁的信息放前面,小頁的信息放后面。

每一類所有內存頁,也分處在哪個 socket上(不明白的查看NUMA相關知識補齊)的,hugepage_info中統計內存頁數會按屬於處在哪個socket上進行統計,但在這一步(eal_hugepage_info_init)中,還區分不了每個頁處在哪個socket上,因此這里還沒有按socket統計頁數,將內存頁數直接記錄到num_pages[0]里面了。

 

2.2 rte_eal_memory_init

int
rte_eal_memory_init(void)
{
    RTE_LOG(INFO, EAL, "Setting up memory...\n");
    const int retval = rte_eal_process_type() == RTE_PROC_PRIMARY ?
            rte_eal_hugepage_init() :
            rte_eal_hugepage_attach();
    if (retval < 0)
        return -1;

    if (internal_config.no_shconf == 0 && rte_eal_memdevice_init() < 0)
        return -1;

    return 0;
}

 

DPDK多進程狀態下,分為RTE_PROC_PRIMARY進程及RTE_PROC_SECONDARY進程,RTE_PROC_PRIMARY負責初始化內存,RTE_PROC_SECONDARY獲取 RTE_PROC_PRIMARY 內存映射的信息,創建與RTE_PROC_PRIMARY一樣的內存映射。這是DPDK多進程共享內存的方式。此處先不展開描述。隨着流程的展開,自然會明白。

2.2.1 rte_eal_mem_init -> rte_eal_hugepage_init 

DPDK有個全局變量

static struct rte_config rte_config

該變量的成員 struct rte_mem_config *mem_config;  保存了DPDK管理的內存的所有信息。DPDK將內存統一的管理,就是將所有內存保存到mem_config:memseg[]中。

struct rte_mem_config {
    volatile uint32_t magic;   /**< Magic number - Sanity check. */

    /* memory topology */
    uint32_t nchannel;    /**< Number of channels (0 if unknown). */
    uint32_t nrank;       /**< Number of ranks (0 if unknown). */

    /**
     * current lock nest order
     *  - qlock->mlock (ring/hash/lpm)
     *  - mplock->qlock->mlock (mempool)
     * Notice:
     *  *ALWAYS* obtain qlock first if having to obtain both qlock and mlock
     */
    rte_rwlock_t mlock;   /**< only used by memzone LIB for thread-safe. */
    rte_rwlock_t qlock;   /**< used for tailq operation for thread safe. */
    rte_rwlock_t mplock;  /**< only used by mempool LIB for thread-safe. */

    uint32_t memzone_idx; /**< Index of memzone */

    /* memory segments and zones */
    struct rte_memseg memseg[RTE_MAX_MEMSEG];    /**< Physmem descriptors. */
    struct rte_memzone memzone[RTE_MAX_MEMZONE]; /**< Memzone descriptors. */

    /* Runtime Physmem descriptors. */
    struct rte_memseg free_memseg[RTE_MAX_MEMSEG];

    struct rte_tailq_head tailq_head[RTE_MAX_TAILQ]; /**< Tailqs for objects */

    /* Heaps of Malloc per socket */
    struct malloc_heap malloc_heaps[RTE_MAX_NUMA_NODES];
} __attribute__((__packed__));

詳細流程:

函數先根據 struct hugepage_info hugepage_info[MAX_HUGEPAGE_SIZES]; (eal_hugepage_info_init中獲取的信息)得到了內存頁的總數。然后為struct hugepage_file *tmp_hp申請了內存,有多少個內存頁,就分配了多少個hugepage_file。

 

struct hugepage_file {
    void *orig_va;      /**< virtual addr of first mmap() */
    void *final_va;     /**< virtual addr of 2nd mmap() */
    uint64_t physaddr;  /**< physical addr */
    size_t size;        /**< the page size */
    int socket_id;      /**< NUMA socket ID */
    int file_id;        /**< the '%d' in HUGEFILE_FMT */
    int memseg_id;      /**< the memory segment to which page belongs */
#ifdef RTE_EAL_SINGLE_FILE_SEGMENTS
    int repeated;        /**< number of times the page size is repeated */
#endif
    char filepath[MAX_HUGEPAGE_PATH]; /**< path to backing file on filesystem */
};

接着針對每一類內存(相同頁大小的內存算一類)進行處理,

for(每一類) {

  for(這一類的每一頁) {         

    每個頁對應一個hugepage_file,file_id,size,filepath初始化。其中file_id是頁的序號,這個序號是給filepath用的,是為了給每個頁對應的文件起一個獨立的文件名(與其他頁不會重復)。

    建立文件<filepath>,這個文件是因為使用hugepage的內存必須按這樣的方式(先建立文件,再mmap,得到內存虛擬地址使用)使用。

    執行mmap獲得頁的虛擬地址,賦給hugepage_file:orig_va。

  } (map_all_hugepages())

 

  所有hugepage預留的物理內存都映射完成,可以根據hugepage_file:orig_va進行訪問了。

  for(這一類的每一頁) {

    獲取物理地址,賦給hugepage_file:physaddr。

    獲取物理地址的方式,是linux內核提供的功能,不了解的可以點擊這里補齊。

  }(find_physaddrs())

  

  for(這一類的每一頁) {

     獲取每個頁所在的socket,賦給hugepage_file:socket_id。 

     獲取的方法也是linux內核提供的功能,具體不詳述。

  }(find_numasocket())

 

  對這一類的頁的hugepage_file[]按頁物理地址進行排序。

 

  for(這一類的每一頁) {

    重新執行mmap,並將獲取的虛擬地址賦給hugepage_file:final_va。    

  }(map_all_hugepages())

  本次重新mmap,與第一次的不同是,會主動先申請一塊足夠映射本類所有內存頁的虛擬地址空間,然后將本類的所有頁連續的映射的該虛擬地址空間中。

  如果找不到這么大的虛擬地址空間,還是會一頁一頁的映射,讓內核去決定虛擬地址空間。

  第二次映射用代碼的注釋解釋 The second mapping tries to  map continguous physical blocks in contiguous virtual blocks.

 

  將第一次的映射unmap掉

 

繼續

更新 internal_config.hugepage_info[].num_pages[],我們知道bugepage_info在eal_hugepage_info_init中初始化時,不知道每個頁是屬於哪個socket的,因此直接使用num_pages[0]記錄了所有的頁數,但在上一步中,已經知道了每個頁屬於哪個socket,因此這里更新了num_pages,准確的記錄了哪個socket上有多少個頁。

 

這塊我還沒具體看 unmap pages that we won't need (looks at used_hp),接下來再研究。

接下來,建立了共享內存,將hugepage_file拷貝到共享內存中,這樣RTE_PROC_SECONDARY啟動后可以在共享內存中讀取這些信息,然后創建與RTE_PROC_PRIMARY一致的映射。

 

最后,使用mem_config->memseg[]將所有內存管理起來。

一個struct rte_memsg對象,保存一塊相同socket的、相同頁大小的、物理地址連續的、虛擬地址連續的內存。因此整個內存由多個memseg組成。

 

總結rte_eal_hugepage_init,它獲取hugepge的方式預留的所有物理內存,統一的映射到虛擬地址空間中,保存到memseg中。至此,理論上可以通過memseg獲取虛擬地址使用這些內存了。但DPDK提供了統一的方式來使用,不是誰用就可以來拿的。

 

 2.2.2 rte_eal_mem_init -> rte_eal_hugepage_attach

 /*
 * This creates the memory mappings in the secondary process to match that of
 * the server process. It goes through each memory segment in the DPDK runtime
 * configuration and finds the hugepages which form that segment, mapping them
 * in order to form a contiguous block in the virtual memory space
 */

 

3 rte_malloc

 

4 rte_ring

 

5 rte_mempool

 

原文鏈接 http://www.cnblogs.com/jintianfree/p/4018043.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM