天天看點

dpdk記憶體管理之記憶體初始化(記憶體收集)初始化之前的記憶體layout 初始化之後的記憶體layout 組織記憶體用到的技術 主程序初始化記憶體流程 次程序使用

初始化之前的記憶體layout

dpdk得到的原始記憶體是通過mmap大頁獲得的,而這樣的原始記憶體分布可以用下圖表示(NUMA架構)。關于頁表相關的知識,可以參加我的另外一篇文章(虛拟存儲器)。

dpdk記憶體管理之記憶體初始化(記憶體收集)初始化之前的記憶體layout 初始化之後的記憶體layout 組織記憶體用到的技術 主程式初始化記憶體流程 次程式使用

其中頁a、a+1類似的表示連續的實體頁。

dpdk通過事先申請的大頁,mmap對應的大頁dir獲得記憶體。可以看出,程式mmap每個頁時,雖然實體頁可能連續,但傳回的虛拟位址卻不一定是連續的。而且,傳回的記憶體不會固定在某個socket上。

初始化之後的記憶體layout

為了高效的使用記憶體,需要重新組織記憶體,并對其進行管理。

組織的方式:

  • 把實體位址連續的頁映射到程序中時,對應的虛拟位址也連續。
  • 把實體位址連續,并且在同一個socket node上的連續頁,組織成一個段(segment)統一管理。
  • 對于相同的實體頁,每個程序映射的虛拟位址都相同。

經過重新組織記憶體後的layout,如圖:

dpdk記憶體管理之記憶體初始化(記憶體收集)初始化之前的記憶體layout 初始化之後的記憶體layout 組織記憶體用到的技術 主程式初始化記憶體流程 次程式使用

組織記憶體用到的技術

dpdk使用linux提供的擷取大頁、頁表、numa節點表、mmap功能重新組織記憶體。

大頁:最多可以同時存在3中大頁。一般隻是用2M的大頁。linux中擷取大頁相關資訊是通過通路解析/sys/kernel/mm/hugepages、/proc/meminfo、/proc/mounts等内容得到的。

頁表:linux中每個程序的頁表對應的檔案是/proc/pid/pagemap,本程序的頁表是/proc/self/pagemap。

/proc/pid/pagemap中的内容(每一個頁表項有64位)如下:

* Bits 0-54   page framenumber (PFN) if present

* Bits 0-4    swap type ifswapped

* Bits5-54   swap offset if swapped

*Bit  55    pte is soft-dirty (see Documentation/vm/soft-dirty.txt)

*Bit  56   page exclusively mapped (since 4.2)

* Bits57-60  zero

*Bit  61   page is file-page or shared-anon (since 3.5)

*Bit  62    page swapped

* Bit  63    page present

注意:大頁是不會被swap的,是常駐記憶體的,是以每個大頁對應的頁表項中PFN都對應實體記憶體,不會對應到swap區或者外設記憶體中。

numa節點表:linux中每個實體頁所在的numa socket表對應的檔案是/proc/pid/numa_maps,本程序的numa socket表是/proc/self/numa_maps。

/proc/pid/numa_maps中的内容大緻如下:

address   policy   mapping details

00400000 defaultfile=/usr/local/bin/app mapped=1 active=0 N3=1 kernelpagesize_kB=4

00600000 defaultfile=/usr/local/bin/app anon=1 dirty=1 N3=1 kernelpagesize_kB=4

3206000000 default file=/lib64/ld-2.12.so mapped=26 mapmax=6 N0=24 N3=2kernelpagesize_kB=4

320621f000 default file=/lib64/ld-2.12.so anon=1 dirty=1 N3=1 kernelpagesize_kB=4

3206220000 default file=/lib64/ld-2.12.so anon=1 dirty=1 N3=1 kernelpagesize_kB=4

3206221000 default anon=1 dirty=1 N3=1 kernelpagesize_kB=4

3206800000 default file=/lib64/libc-2.12.so mapped=59 mapmax=21 active=55 N0=41 N3=18kernelpagesize_kB=4

320698b000 default file=/lib64/libc-2.12.so

3206b8a000 default file=/lib64/libc-2.12.so anon=2 dirty=2 N3=2 kernelpagesize_kB=4

3206b8e000 default file=/lib64/libc-2.12.so anon=1 dirty=1 N3=1 kernelpagesize_kB=4

3206b8f000 default anon=3 dirty=3 active=1 N3=3 kernelpagesize_kB=4

7f4dc10a2000 default anon=3 dirty=3 N3=3 kernelpagesize_kB=4

7f4dc10b4000 default anon=2 dirty=2 active=1 N3=2 kernelpagesize_kB=4

7f4dc1200000 default file=/anon_hugepage\040(deleted) huge anon=1 dirty=1 N3=1kernelpagesize_kB=2048

7fff335f0000 default stack anon=3 dirty=3 N3=3 kernelpagesize_kB=4

7fff3369d000 default mapped=1 mapmax=35 active=0 N3=1 kernelpagesize_kB=4

Where:"address" is the starting address for the mapping;

"policy"reports the NUMA memory policy set for the mapping (seevm/numa_memory_policy.txt);

"mapping details" summarizes mappingdata such as mapping type, page usage counters, node locality page counters (N0== node0, N1 == node1, ...) and the kernel page size, in KB, that is backingthe mapping up.

主程序初始化記憶體流程

  1. 擷取大頁資訊并存放在internal_config.hugepage_info[i]結構中
  2. 把所有可用的大頁都映射程序中,并且每個大頁對應一個檔案(格式:mountdir/rtesmap_%d),後續可以通過open(hugedir),再mmap(fd)把大頁映射程序中使用。

    把每頁資訊都放到結構struct hugepage_file tmp_hp[]中。

    struct hugepage_file {

            void*orig_va;     

    void *final_va;    

    uint64_t physaddr; 

    size_t size;       

    int socket_id;     

    int file_id;       

    int memseg_id;     

    char filepath[MAX_HUGEPAGE_PATH];

    };

  3. 通過find_physaddrs函數,根據映射得到的虛拟位址orig_va和/proc/self/pagemap中的資訊,找到每個大頁虛拟位址對應的實體位址,并添到struct hugepage_file結構中的physaddr字段。
  4. 通過find_numasocket函數,根據映射得到的虛拟位址orig_va和/proc/self/numa_maps中的資訊,找到每個大頁虛拟位址對應的numa_maps項,解析對應的numa socket節點,指派到對應的頁資訊結構struct hugepage_file中的socket_id字段。
  5. 現在,得到了2M(假設使用2M大頁)大頁的所有可用頁的資訊,并放到tmp_hp數組中。然後,按實體位址physaddr從小到大排序每個大頁。排序後tmp_hp數組中是按實體位址遞增的順序放置大頁資訊的。
  6. 再一次mmap所有的可用頁。這次mmap,是把實體位址連續的頁,映射成虛拟位址也連續的頁(通過指定void*mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)函數的addr值)。并且,這次映射的虛拟位址放到final_va字段中。
  7. 解除第一次映射的va虛拟位址orig_va。munmap(orig_va)之後使orig_va對應的虛拟位址空間可用。
  8. dpdk預設是把所有的頁平分到每個socket(根據每個socket上core的數量)上。
  9. 建立共享記憶體/var/run/.rte_hugepage_info,把重新組織的頁資訊struct hugepage_file數組放到.rte_hugepage_info中,以便其他程序共享。
  10. 最後,把實體位址連續(現在虛拟位址肯定也連續了)、同一個socket上的連續的頁,組織成一個段memseg,放到rte_mem_config結構的memseg字段中。程序間共享。

次程序使用

次程序主要是把重新組織的記憶體映射到程序中。

  1. 根據共享的mem_config,獲得memseg段資訊。memseg中包括每個段的資訊(段大小、虛拟和實體位址等)。再檢查是否能獲得每個段對應的虛拟位址空間。
  2.  然後,再根據共享的.rte_hugepage_info獲得每個頁資訊。
  3. 根據.rte_hugepage_info中每個頁的資訊(dir、memseg_id),把每個頁映射到次程序中,映射的虛拟位址和主程序中的一樣。

繼續閱讀