天天看點

hugepage

作者:後端開發進階

1,預留大頁記憶體

檢查CPU對大頁的支援:對于2M大頁:/proc/cpu . flags 上需要有pse辨別,對于4M大頁:/proc/cpu . flags 上需要有pdpe1gb辨別。

編譯核心時打開 CONFIG_HUGETLB_PAGE 和 CONFIG_HUGETLBFS 以開啟核心的大頁功能。

boot 時預留大頁,即在/boot/grub2/grub.cfg中添加linux核心的啟動參數:任何類型的大頁都可以以這種方式預留,對于2M大頁可以僅需指定:hugepages=1024,對于其他類型的大頁需要同時指定hugepagesz 和 hugepages:"default_hugepagesz=1G hugepagesz=1G hugepages=4"。

啟動後預留大頁,echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages,亦可以使用echo 1024 > /proc/sys/vm/nr_hugepages。僅2M大頁可以以這種方式預留。

預留的大頁會平均分布在各個NUMA上。

2,挂載大頁記憶體

mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge           

如果不是2M大頁,則需要指定選項:mount -t hugetlbfs -o pagesize=1GB nodev /mnt/huge。

3,使用大頁

3.1,dpdk對大頁的使用

example:

fd = open("/mnt/huge/test", O_CREAT | O_RDWR);
addr = mmap(0, MAP_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
munmap(addr, MAP_LENGTH);
close(fd);
unlink("/mnt/huge/test");           

3.2,libhugetlbfs對大頁的使用

需要将應用程式與庫libhugetlb連結在一起。libhugetlb庫對malloc()/free()等常用的記憶體相關的庫函數進行了重載,以使得應用程式的資料可以放置在采用大頁面的記憶體區域中,以提高記憶體性能。

4,檢視大頁資訊

# cat /proc/meminfo |grep Huge     
AnonHugePages:   1196032 kB     
HugePages_Total:      60            #總共有多少大頁
HugePages_Free:       57            #空閑的大頁數
HugePages_Rsvd:        0            #保留的大頁數
HugePages_Surp:        0     
Hugepagesize:    1048576 kB         #大頁的大小,這裡為1GB           

5,參考文檔

https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt

Linux hugepage使用與實作

6,DPDK 大頁 源碼分析

6.1,eal_hugepage_info_init

每一種類型的大頁的相關資訊存儲在internal_config.hugepage_info[xxx]數組中的一個元素内:

hugepage

hugepage_sz:大頁大小

hugedir:大頁的挂載位置

num_pages[xxx]:在各個NUMA上大頁的頁數

lock_descriptor:大頁挂載位置的檔案描述符

周遊/sys/kernel/mm/hugepages/下每個目錄項,每一項都是一種大頁類型 huge page size:

hugepage

比如上圖表示系統隻有一種類型的大頁即1G大頁。下一步就是确定它挂載在哪裡。這可以通過讀取/proc/mounts 再過濾 hugetlbfs得到。一項mount point裡的options域如果包含pagesize字段則指明了大頁的大小pagesize,如果沒有包含pagesize字段則表明它使用了系統預設的大頁大小 cat /proc/meminfo|grep Hugepagesize。

hugepage

由上我們得到了每一種大頁類型的挂載位置。讀取/sys/kernel/mm/hugepages/hugepages-<page size>/nr_hugepages可以得到某種類型大頁的總頁數讀取/sys/kernel/mm/hugepages/hugepages-<page size>/resv_hugepages可得到某種類型大頁的預留頁數用總頁數減去預留頁數就得到目前可用頁數。

6.2,rte_eal_config_create

hugepage

記憶體配置用結構體struct rte_mem_config描述,它以二進制的形式存放在檔案/var/run/.rte_config中,初始化時再用mmap把這個檔案映射到記憶體中來,映射的記憶體位置記錄在rte_config.mem_config->mem_cfg_addr。

6.3,rte_eal_hugepage_init

本函數的主要目的是在/mnt/huge目錄下建立若幹個rtemap_xx檔案,并為每個rtemap_xx檔案做mmap映射,保證mmap後的虛拟位址與實際的實體位址是一樣的。

用配置的記憶體大小internal_config.memory除以大頁大小就得到需要建立的rtemap_xx檔案個數,每一個rtemap_xx檔案表示一個大頁,用一個struct hugepage_file結構體表示:

hugepage

orig_va : rtemap_xx檔案mmap的虛拟位址

final_va : 第二次mmap映射rtemap_xx檔案的虛拟位址

size : 大頁大小

file_id : 頁序号,就是rtemap_xx中的xx

filepath : 檔案路徑,即/dev/hugepages/rtemap_xx

map_all_hugepages(struct hugepage_file *hugepg_tbl, struct hugepage_info *hpi, int orig)為某種類型的大頁下的每一個page建立rtemap_xx檔案,并将其mmap目前程序空間中來。

fd = open("/dev/hugepages/rtemap_xx", O_CREAT | O_RDWR, 0600);
hugepg_tbl [idx].orig_va = 
mmap(vma_addr, hugepage_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0);
flock(fd, LOCK_SH | LOCK_NB)
vma_addr = (char *)vma_addr + hugepage_sz           

這裡首先建立大頁檔案 "/dev/hugepages/rtemap_xx",然後将大頁檔案 "/dev/hugepages/rtemap_xx" mmap映射到目前程序空間中來,注意這裡使用了MAP_POPULATE辨別,表示mmap時就為其配置設定大頁實體 記憶體,而不是等到page fault時再配置設定。flock在檔案上設定共享的flock。上面的mmap輸入了一個vma_addr,第一次調用mmap時vma_addr為NULL表示讓系統選擇起始虛拟位址,而後續的調用vma_addr為上一頁page末尾位址,進而讓所有的大頁映射在一塊連續的虛拟位址空間内。

find_physaddrs():mmap映射的大頁記憶體的虛拟位址存放在hugepg_tbl [idx].orig_va,實體位址則存放在hugepg_tbl[i].physaddr,這是通過查找目前程序自己的頁表/proc/self/pagemap得到的。

find_numasocket():擷取每一個大頁記憶體所在的NUMA node并存放在hugepg_tbl[i].socket_id。讀取"/proc/self/numa_maps",當上一步獲得的虛拟位址hugepg_tbl[i].orig_va等于"/proc/self/numa_maps"表項的虛拟位址時(第一個字段),字段N0=1 或 N1=1 就表示這個大頁記憶體所在的NUMA node。

linux-EjSopu:/mnt/sdb/f00403445/software # cat /proc/33878/numa_maps | grep rtemap_0     
7f2580000000 bind:0 file=/dev/hugepages/rtemap_0 huge dirty=1 N0=1  kernelpagesize_kB=1048576                

map_all_hugepages (orig = 0):對struct hugepage_file *hugepage數組進行排序,按照實體記憶體位址從小到大的順序排列。如果2個大頁在實體位址空間上是連續的,則我們希望他們在映射的虛拟位址空間上也是連續的。 由此需要再調用一次map_all_hugepages将實體上連續的大頁映射成虛拟位址空間上也連續的大頁。

周遊大頁,看看實體上連續的大頁有多少個,得到一個vma_len即相應的連續虛拟位址空間的大小這些連續實體頁的第一頁的虛拟位址由核心選擇,後續實體頁的虛拟位址通過mmap()的第一個參數來指定這些實體位址空間和虛拟位址空間都連續的一片記憶體區稱之為一個block。重映射之後大頁新的虛拟位址儲存在hugepg_tbl[i].final_va。

由上可知每一個rtemap_xx檔案被映射了2次,這2次映射在不同的虛拟位址空間上,但它們使用的都是同一個實體大頁。這裡需要調用unmap_all_hugepages_orig()解除每個大頁第一次映射的虛拟位址空間而僅保留第二次映射的虛拟位址空間。

計算每個socket上包含多少個hugepage,資訊儲存在internal_config.hugepage_info[xx].num_pages[socket]中前面的步驟中已将所有的大頁映射到了目前程序的虛拟位址空間,但是目前程序隻請internal_config.memory大小的記憶體,是以可以釋放超出請求部分的大頁記憶體。calc_num_pages_per_socket()用于計算每個socket需要多少個大頁。unmap_unneeded_hugepages()則會釋放每個socket上超出請求部分的大頁。

每一種類型的大頁對應一個struct hugepage_info結構每一個大頁對應一個struct hugepage_file結構它們是一對多的關系。為了計算每種類型的大頁在各個NUMA上的分布即internal_config.hugepage_info[ x ].num_pages[socket]隻需周遊所有大頁的struct hugepage_file *hp_file->socket_id。

create_shared_memory() -> copy_hugepages_to_shared_mem():為/var/run/.rte_hugepage_info檔案mmap一段nr_hugepages * sizeof(struct hugepage_file)大小的記憶體塊,并将前面建立的hugepage_file數組中的所有内容,都copy到這一塊記憶體中,這片記憶體區域的位址儲存在全局變量g_hugepage_table.hugepg_tbl中。

hugepage

實體位址空間和虛拟位址空間都連續,同屬一個socket,同屬一個大頁類型的多個大頁組成一個memory segment,對應一個struct rte_memseg結構。

原文連結:https://zhuanlan.zhihu.com/p/540139246