本文總結了hugepage使用過程當中的一些知識點,參考的內核版本是4.1.12
注:本文爲原創,轉載請註明出處html
大頁是相對傳統4K小頁而言的,通常來講常見的體系架構都會提供2種大頁大小,好比常見的2M大頁和1G大頁。其實這兩種大頁size也分別對應PMD和PUD的一個頁表項能夠cover的物理內存大小。固然某些體系架構(如arm64)經過contiguous-tlb特性支持2種以上的大頁。
大頁的主要優勢:使用大頁能夠減小頁表項數量,從而減小TLB Miss的機率,提高系統訪存性能。固然有利必有弊,使用大頁下降了內存管理的粒度和靈活性,若是程序並非對內存的使用量特別大,使用大頁還可能形成內存的浪費。node
通常來講有三種方法:linux
注:上述方法使用的前提是,系統中加載了hugetlbfs,且存在空閒大頁。能夠經過cat /proc/meminfo查看是否有空閒內存。另外本質上匿名大頁也是有對應的大頁文件系統中的匿名文件,其實並非真正的匿名頁。數組
HugePages_Total: 10
HugePages_Free: 10
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 92308 kB
DirectMap2M: 1861632 kB架構
通常來講有兩種方法
1)在bootline中設置大頁參數,這種方式最穩妥,由於大頁本質上是連續的4K小頁組成的,因此在系統啓動時預留大頁成功率最高。有三個參數,看名字就很清楚了
default_hugepagesz=1G hugepagesz=1G hugepages=4
若是沒有定義default_hugepagesz,則大頁size默認是2M函數
#define HPAGE_SHIFT PMD_SHIFT #define HPAGE_SIZE (_AC(1,UL) << HPAGE_SHIFT) #define HPAGE_MASK (~(HPAGE_SIZE - 1)) #define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT) static int __init hugetlb_init(void) { ... if (!hugepages_supported())//不支持hugepage直接返回 return 0; if (!size_to_hstate(default_hstate_size)) {//沒有設置default_hugepagesz則默認2M default_hstate_size = HPAGE_SIZE; if (!size_to_hstate(default_hstate_size)) hugetlb_add_hstate(HUGETLB_PAGE_ORDER); } ... }
2)在系統啓動後,經過echo 10 > /proc/sys/vm/nr_hugepages這種方式預留空閒大頁性能
linux系統中是能夠同時存在2種大頁類型的,至於爲何是2種,我的理解是由於大頁每每對應PMD和PUD兩種級別的頁表項所cover的物理內存大小。再大意義也不大了。能夠經過下面的bootline設置兩種大頁類型
default_hugepagesz=1G hugepagesz=1G hugepages=4 hugepagesz=2M hugepages=4
內核啓動的時候會解析bootline,而後把相應的信息存放在下面的結構體中spa
#define HUGE_MAX_HSTATE 2 struct hstate hstates[HUGE_MAX_HSTATE]; void __init hugetlb_add_hstate(unsigned order) { struct hstate *h; unsigned long i; if (size_to_hstate(PAGE_SIZE << order)) { pr_warning("hugepagesz= specified twice, ignoring\n"); return; } BUG_ON(hugetlb_max_hstate >= HUGE_MAX_HSTATE); BUG_ON(order == 0); h = &hstates[hugetlb_max_hstate++]; ... snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB", huge_page_size(h)/1024); parsed_hstate = h; //解析hugepages參數時要使用這個變量 } static __init int setup_hugepagesz(char *opt) { unsigned long ps = memparse(opt, &opt); if (ps == PMD_SIZE) {//2M大頁 hugetlb_add_hstate(PMD_SHIFT - PAGE_SHIFT); } else if (ps == PUD_SIZE) {//1G大頁 hugetlb_add_hstate(PUD_SHIFT - PAGE_SHIFT); } else { pr_err("hugepagesz: Unsupported page size %lu M\n", ps >> 20); return 0; } return 1; } __setup("hugepagesz=", setup_hugepagesz);
在使用mmap或者shmget獲取大頁時,若是直接使用MAP_HUGETLB或SHM_HUGETLB參數,則獲取的大頁size是2M.net
FILE:mmap.c ... else if (flags & MAP_HUGETLB) { struct user_struct *user = NULL; struct hstate *hs; hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & SHM_HUGE_MASK); if (!hs) return -EINVAL; ... FILE:hugetlb.h static inline struct hstate *hstate_sizelog(int page_size_log) { if (!page_size_log) return &default_hstate; return size_to_hstate(1UL << page_size_log); } #define default_hstate (hstates[default_hstate_idx])
若是須要指定大頁的size,則可使用MAP或SHM flag的bit[31:26],這6個bit的取值範圍是0~31,所以能夠表示的頁大小從0~2G,基本夠用了。下面是SHM flag的代碼,MAP flag與此相似rest
/* Bits [26:31] are reserved */ /* * When SHM_HUGETLB is set bits [26:31] encode the log2 of the huge page size. * This gives us 6 bits, which is enough until someone invents 128 bit address * spaces. * * Assume these are all power of twos. * When 0 use the default page size. */ #define SHM_HUGE_SHIFT 26 #define SHM_HUGE_MASK 0x3f #define SHM_HUGE_2MB (21 << SHM_HUGE_SHIFT) #define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT)
還有一種方法能夠指定大頁的size,那就是在mount時經過pagesize選項指定:
mount -t hugetlbfs -o pagesize=1G none /dev/hugepages1G
對應的代碼以下所示:
FILE:fs/hugetlbfs/inode.c case Opt_pagesize: { unsigned long ps; ps = memparse(args[0].from, &rest); //根據輸入的參數到hstates數組中查找匹配的數組項 pconfig->hstate = size_to_hstate(ps); if (!pconfig->hstate) { //找不到匹配的大小就報錯 pr_err("Unsupported page size %lu MB\n", ps >> 20); return -EINVAL; } break; } //最終hstate信息被保存在hugetlbfs的sb結構中 struct hugetlbfs_sb_info *sbinfo; sb->s_fs_info = sbinfo; sbinfo->hstate = config.hstate; //後續hugetlbfs中的文件均可以經過file結構體關聯到hstate結構體 static inline struct hstate *hstate_inode(struct inode *i) { struct hugetlbfs_sb_info *hsb; hsb = HUGETLBFS_SB(i->i_sb); return hsb->hstate; } static inline struct hstate *hstate_file(struct file *f) { return hstate_inode(file_inode(f)); } //從而mmap就知道使用什麼size的大頁了,具體能夠參見hugetlbfs_file_mmap函數
hugepage在缺頁時與普通小頁的缺頁流程是有區別的,具體流程以下
do_page_fault->__do_page_fault->handle_mm_fault->__handle_mm_fault->hugetlb_fault->hugetlb_no_page->alloc_huge_page
能夠看出在__handle_mm_fault對於大頁缺頁直接調用了hugetlb_fault。另外多說一點,alloc_huge_page函數的參數以下所示
static struct page *alloc_huge_page(struct vm_area_struct *vma, unsigned long addr, int avoid_reserve) //經過下面的代碼能夠經過vma或者hstate struct hstate *h = hstate_vma(vma); //本質上仍是調用了hstate_vma函數 static inline struct hstate *hstate_vma(struct vm_area_struct *vma) { return hstate_file(vma->vm_file); }
若是要同時支持2種以上的大頁類型,好比2M,32M,1G,能夠修改HUGE_MAX_HSTATE宏定義,同時也要修改
hugepage pte的底層操做接口,下面的例子是arm64 contiguous-tlb的patch,可供參考。
http://www.spinics.net/lists/...