huge形式_Linux hugepage使用与实现
Linux
hugepage使用與實(shí)現(xiàn)
——lvyilong316
1.1引言
隨著計(jì)算需求規(guī)模的不斷增大,應(yīng)用程序?qū)?nèi)存的需求也越來(lái)越大。為了實(shí)現(xiàn)虛擬內(nèi)存管理機(jī)制,操作系統(tǒng)對(duì)內(nèi)存實(shí)行分頁(yè)管理。自內(nèi)存“分頁(yè)機(jī)制”提出之始,內(nèi)存頁(yè)面的默認(rèn)大小便被設(shè)置為4096字節(jié)(4KB),雖然原則上內(nèi)存頁(yè)面大小是可配置的,但絕大多數(shù)的操作系統(tǒng)實(shí)現(xiàn)中仍然采用默認(rèn)的4KB頁(yè)面。4KB大小的頁(yè)面在“分頁(yè)機(jī)制”提出的時(shí)候是合理的,因?yàn)楫?dāng)時(shí)的內(nèi)存大小不過(guò)幾十兆字節(jié),然而當(dāng)物理內(nèi)存容量增長(zhǎng)到幾G甚至幾十G的時(shí)候,操作系統(tǒng)仍然以4KB大小為頁(yè)面的基本單位,是否依然合理呢?
在Linux操作系統(tǒng)上運(yùn)行內(nèi)存需求量較大的應(yīng)用程序時(shí),由于其采用的默認(rèn)頁(yè)面大小為4KB,因而將會(huì)產(chǎn)生較多TLB Miss和缺頁(yè)中斷,從而大大影響應(yīng)用程序的性能。當(dāng)操作系統(tǒng)以2MB甚至更大作為分頁(yè)的單位時(shí),將會(huì)大大減少TLB
Miss和缺頁(yè)中斷的數(shù)量,顯著提高應(yīng)用程序的性能。這也正是Linux內(nèi)核引入大頁(yè)面支持的直接原因。好處是很明顯的,假設(shè)應(yīng)用程序需要2MB的內(nèi)存,如果操作系統(tǒng)以4KB作為分頁(yè)的單位,則需要512個(gè)頁(yè)面,進(jìn)而在TLB中需要512個(gè)表項(xiàng),同時(shí)也需要512個(gè)頁(yè)表項(xiàng),操作系統(tǒng)需要經(jīng)歷至少512次TLB Miss和512次缺頁(yè)中斷才能將2MB應(yīng)用程序空間全部映射到物理內(nèi)存;然而,當(dāng)操作系統(tǒng)采用2MB作為分頁(yè)的基本單位時(shí),只需要一次TLB
Miss和一次缺頁(yè)中斷,就可以為2MB的應(yīng)用程序空間建立虛實(shí)映射,并在運(yùn)行過(guò)程中無(wú)需再經(jīng)歷TLB Miss和缺頁(yè)中斷(假設(shè)未發(fā)生TLB項(xiàng)替換和Swap)。
為了能以最小的代價(jià)實(shí)現(xiàn)大頁(yè)面支持,Linux操作系統(tǒng)采用了基于hugetlbfs特殊文件系統(tǒng)2M字節(jié)大頁(yè)面支持。這種采用特殊文件系統(tǒng)形式支持大頁(yè)面的方式,使得應(yīng)用程序可以根據(jù)需要靈活地選擇虛存頁(yè)面大小,而不會(huì)被強(qiáng)制使用2MB大頁(yè)面。本文將針對(duì)hugetlb大頁(yè)面的應(yīng)用和內(nèi)核實(shí)現(xiàn)兩個(gè)方面進(jìn)行簡(jiǎn)單的介紹,以期起到拋磚引玉的作用。
1.2 Hugetlb FileSystem的應(yīng)用
本文的例子摘自Linux內(nèi)核源碼中提供的有關(guān)說(shuō)明文檔(Documentation/vm/hugetlbpage.txt)。使用hugetlbfs之前,首先需要在編譯內(nèi)核(make menuconfig)時(shí)配置CONFIG_HUGETLB_PAGE和CONFIG_HUGETLBFS選項(xiàng),這兩個(gè)選項(xiàng)均可在File systems內(nèi)核配置菜單中找到。
內(nèi)核編譯完成并成功啟動(dòng)內(nèi)核之后,將hugetlbfs特殊文件系統(tǒng)掛載到根文件系統(tǒng)的某個(gè)目錄上去,以使得hugetlbfs可以訪問(wèn)。命令如下:
mount none /mnt/huge -t hugetlbfs
此后,只要是在/mnt/huge/目錄下創(chuàng)建的文件,將其映射到內(nèi)存中時(shí)都會(huì)使用2MB作為分頁(yè)的基本單位。值得一提的是,hugetlbfs中的文件是不支持讀/寫系統(tǒng)調(diào)用(如read()或write()等)的,一般對(duì)它的訪問(wèn)都是以內(nèi)存映射的形式進(jìn)行的。為了更好地介紹大頁(yè)面的應(yīng)用,接下來(lái)將給出一個(gè)大頁(yè)面應(yīng)用的例子,該例子同樣也是摘自于上述提到的內(nèi)核文檔,只是略有簡(jiǎn)化。
點(diǎn)擊(此處)折疊或打開(kāi)
#include
#include
#include
#define MAP_LENGTH (10*1024*1024)
int main()
{
int fd;
void * addr;
/* create a file in hugetlb fs */
fd = open("/mnt/huge/test", O_CREAT | O_RDWR);
if(fd < 0){
perror("Err: ");
return -1;
}
/* map the file into address space of current application process */
addr = mmap(0, MAP_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED){
perror("Err: ");
close(fd);
unlink("/mnt/huge/test");
return -1;
}
/* from now on, you can store application data on huage pages via addr */
munmap(addr, MAP_LENGTH);
close(fd);
unlink("/mnt/huge/test");
return 0;
}
對(duì)于系統(tǒng)中大頁(yè)面的統(tǒng)計(jì)信息可以在Proc特殊文件系統(tǒng)(/proc)中查到,如/proc/sys/vm/nr_hugepages給出了當(dāng)前內(nèi)核中配置的大頁(yè)面的數(shù)目,也可以通過(guò)該文件配置大頁(yè)面的數(shù)目,如:
echo 20 > /proc/sys/vm/nr_hugepages
調(diào)整系統(tǒng)中的大頁(yè)面的數(shù)目為20。 例子中給出的大頁(yè)面應(yīng)用是簡(jiǎn)單的,而且如果僅僅是這樣的應(yīng)用,對(duì)應(yīng)用程序來(lái)說(shuō)也是沒(méi)有任何用處的。在實(shí)際應(yīng)用中,為了使用大頁(yè)面,還需要將應(yīng)用程序與庫(kù)libhugetlb鏈接在一起。libhugetlb庫(kù)對(duì)malloc()/free()等常用的內(nèi)存相關(guān)的庫(kù)函數(shù)進(jìn)行了重載,以使得應(yīng)用程序的數(shù)據(jù)可以放置在采用大頁(yè)面的內(nèi)存區(qū)域中,以提高內(nèi)存性能。
1.3 Hugetlb特殊文件系統(tǒng)的內(nèi)核實(shí)現(xiàn)
在簡(jiǎn)要介紹了大頁(yè)面的使用之后,本文接下來(lái)將重點(diǎn)介紹hugetlbfs在內(nèi)核中的實(shí)現(xiàn)。本文的源代碼分析是基于2.6.18.8版本的Linux內(nèi)核進(jìn)行的。涉及到的文件主要包括mm/hugetlb.c和include/linux/hugetlb.h以及fs/hugetlbfs/inode.c三個(gè)文件。為了能夠更好地理解hugetlbfs的工作原理,將按照上述程序示例中給出的流程,介紹hugetlb特殊文件系統(tǒng)的初始化過(guò)程、在hugetlbfs偽文件系統(tǒng)中創(chuàng)建文件的內(nèi)核處理流程,以及將hugetlb文件映射到用戶地址空間的內(nèi)核實(shí)現(xiàn)過(guò)程。
1.3.1 hugetlbfs初始化
Hugetlbfs的初始化是通過(guò)函數(shù)hugetlb_init()完成的,該函數(shù)在系統(tǒng)初始化時(shí)執(zhí)行。用于hugetlbfs的大頁(yè)面是在這一初始化過(guò)程就分配好了的,并且在系統(tǒng)運(yùn)行過(guò)程中不會(huì)被回收。初始化的過(guò)程大致如下:
1.在NUMA機(jī)器上,每個(gè)NUMA節(jié)點(diǎn)(node)都有一個(gè)空閑huge page的鏈表,用數(shù)組hugepage_freelists[]記錄這些鏈表的頭結(jié)構(gòu)(list_head),每個(gè)NUMA節(jié)點(diǎn)對(duì)應(yīng)數(shù)組的一個(gè)元素,在hugetlb偽文件系統(tǒng)初始化時(shí),首先要初始化這些鏈表頭結(jié)構(gòu)。
2.接下來(lái),調(diào)用alloc_fresh_huge_page()在每個(gè)NUMA node中預(yù)分配大頁(yè)面,并加入到上述的空閑頁(yè)面鏈表中去,全局變量max_huge_pages記錄了系統(tǒng)中支持的大頁(yè)面的最大數(shù)目。在alloc_fresh_huge_page()中以__GFP_COMP為標(biāo)志調(diào)用通用底層的物理頁(yè)面分配函數(shù)alloc_page_node()以分配一個(gè)2MB的大頁(yè)面,由于聲明了__GFP_COMP標(biāo)志,使得連續(xù)的512個(gè) 4KB的頁(yè)面作為一個(gè)“混合”頁(yè)面,它們的page結(jié)構(gòu)一起被分配給hugetlbfs。分配成功后,將這512個(gè)連續(xù)的page結(jié)構(gòu)中的第二個(gè)page結(jié)構(gòu)的lru.next指針指向一個(gè)特3.定的“析構(gòu)函數(shù)”——free_huge_page()。
最后調(diào)用put_page()將大頁(yè)面放到相應(yīng)的空閑鏈表中去,由于這個(gè)大頁(yè)面被設(shè)置了PG_compound標(biāo)志,進(jìn)而會(huì)調(diào)用該析構(gòu)函數(shù),將頁(yè)面放到該大頁(yè)面所在NUMA node的hugepage_freelists[]鏈表中去。最后將max_huge_pages、free_huge_pages(系統(tǒng)中空閑的大頁(yè)面數(shù)目)以及nr_huge_pages(系統(tǒng)中實(shí)際的大頁(yè)面數(shù)目)等作為統(tǒng)計(jì)信息的全局變量初始化為成功預(yù)分配的大頁(yè)面數(shù)目。
1.3.2在hugetlbfs中創(chuàng)建文件
在示例代碼中,首先調(diào)用open()系統(tǒng)調(diào)用在hugetlbfs(/mnt/huge)創(chuàng)建了一個(gè)文件,相應(yīng)地,在內(nèi)核中由sys_open()函數(shù)最終調(diào)用hugetlbfs_create()為該文件分配內(nèi)存索引節(jié)點(diǎn)(inode)結(jié)構(gòu),并進(jìn)行基本的初始化工作。其中值得一提的是,在inode初始化時(shí)將文件操作表指針inode->i_fop指向hugetlbfs特有的函數(shù)跳轉(zhuǎn)表hugetlbfs_file_operations,而此表中就包含了實(shí)現(xiàn)文件映射的mmap方法hugetlbfs_file_mmap(),這個(gè)函數(shù)在系統(tǒng)調(diào)用sys_mmap()的實(shí)現(xiàn)中極為關(guān)鍵。
由于hugetlbfs是一個(gè)偽文件系統(tǒng),在磁盤上沒(méi)有相應(yīng)的副本,因此在該文件系統(tǒng)中創(chuàng)建一個(gè)文件的過(guò)程也僅僅是分配虛擬文件系統(tǒng)(VFS)層的inode、dentry等結(jié)構(gòu)。甚至連物理內(nèi)存頁(yè)面都不會(huì)分配,而是在對(duì)該文件映射后并訪問(wèn)時(shí),才通過(guò)缺頁(yè)中斷進(jìn)入內(nèi)核分配大頁(yè)面并建立虛實(shí)映射,這一過(guò)程將在后面詳細(xì)介紹。
1.3.3為Hugetlb文件建立映射
在成功創(chuàng)建了hugetlbfs文件之后,就可以將其映射到應(yīng)用進(jìn)程的地址空間了,這是通過(guò)系統(tǒng)調(diào)用mmap()實(shí)現(xiàn)的。在內(nèi)核中,sys_mmap()調(diào)用2.2節(jié)中提到的函數(shù)跳轉(zhuǎn)表中的hugetlbfs_file_mmap()方法為應(yīng)用進(jìn)程建立映射。
函數(shù)hugetlbfs_file_mmap()對(duì)虛存區(qū)域(vma)的偏移、邊界等進(jìn)行檢查,并設(shè)置該vma的VM_HUGETLB和VM_RESERVED等標(biāo)志,以區(qū)別于4KB映射的虛存區(qū)域,并且在進(jìn)程運(yùn)行過(guò)程中,該虛存區(qū)域映射的物理頁(yè)面不會(huì)被回收。在這個(gè)過(guò)程中,虛擬地址并沒(méi)有真正映射到物理地址空間,而這一工作則推遲到應(yīng)用程序訪問(wèn)該內(nèi)存區(qū)域并引發(fā)缺頁(yè)中斷(即Page Fault)的時(shí)候進(jìn)行,見(jiàn)下一節(jié)。
1.3.4分配大頁(yè)面、建立虛實(shí)映射
以4KB為基本分頁(yè)單位的64位Linux操作系統(tǒng)來(lái)采用四級(jí)頁(yè)表管理虛實(shí)映射。如圖1所示。每個(gè)頁(yè)表項(xiàng)占據(jù)64位(8Bytes),因此每個(gè)作為頁(yè)表的物理頁(yè)面可以存放512個(gè)頁(yè)表項(xiàng),從而最末級(jí)頁(yè)表所映射的物理內(nèi)存大小為512*4KB = 2MB,依此類推,在上一級(jí)頁(yè)表(PMD)中,每一個(gè)PMD表項(xiàng)可映射2MB的物理內(nèi)存。當(dāng)采用2MB作為分頁(yè)的基本單位時(shí),內(nèi)核中則設(shè)置了三級(jí)頁(yè)表,如圖2所示。在三級(jí)頁(yè)表中,最末一級(jí)頁(yè)表為PMD表,同樣地,每一個(gè)PMD表項(xiàng)指出了一個(gè)2MB的大頁(yè)面,也即虛擬地址的低21位作為大頁(yè)面的頁(yè)內(nèi)偏移,而高位則作為大頁(yè)面的頁(yè)面編號(hào)(pfn)。為了能讓MMU正確地進(jìn)行虛實(shí)地址轉(zhuǎn)換,必須告知MMU哪個(gè)頁(yè)表項(xiàng)映射的是4KB的物理頁(yè)面,哪個(gè)頁(yè)表項(xiàng)映射的是2MB的大頁(yè)面,這是通過(guò)頁(yè)表項(xiàng)中的標(biāo)志位_PAGE_PSE來(lái)區(qū)分的,這一般是通過(guò)內(nèi)聯(lián)函數(shù)pte_mkhuge()設(shè)置的。
圖1. 64位Linux操作系統(tǒng)四級(jí)頁(yè)表示意圖
圖2. 64位Linux操作系統(tǒng)三級(jí)頁(yè)表示意圖
簡(jiǎn)單介紹了采用大頁(yè)面映射的頁(yè)表組織后,下面將描述進(jìn)程在設(shè)置為大頁(yè)面的虛存區(qū)域產(chǎn)生Page Fault時(shí)的缺頁(yè)中斷處理流程,如圖3所示:
圖3.大頁(yè)面缺頁(yè)中斷處理函數(shù)調(diào)用流程
在進(jìn)程訪問(wèn)到尚未建立虛實(shí)映射的大頁(yè)面內(nèi)存區(qū)域時(shí),就會(huì)產(chǎn)生缺頁(yè)中斷,缺頁(yè)中斷的處理函數(shù)是大名鼎鼎的do_page_fault()函數(shù)。從do_page_fault()到函數(shù)__handle_mm_fault()是缺頁(yè)中斷處理的公共流程,不是我們關(guān)注的重點(diǎn),在此不作介紹。在函數(shù)__handle_mm_fault()中首先會(huì)檢查產(chǎn)生缺頁(yè)中斷的內(nèi)存區(qū)域是否是大頁(yè)面區(qū)域,即VM_HUGETLB標(biāo)志是否設(shè)置,如果是,則調(diào)用函數(shù)hugetlb_fault()進(jìn)行大頁(yè)面的映射,這是大頁(yè)面缺頁(yè)中斷處理的入口函數(shù),其處理過(guò)程大致如下:
lhugetlb_fault()
1)根據(jù)產(chǎn)生Page Fault的虛擬地址查找或分配相應(yīng)的PMD表項(xiàng);
2)調(diào)用以分配物理內(nèi)存、建立虛實(shí)映射;
3)如果引發(fā)缺頁(yè)中斷的內(nèi)存操作是寫操作,且該大頁(yè)面被設(shè)置為只讀,則預(yù)先做一次Copyon Write操作,以避免因“違規(guī)操作”再次產(chǎn)生Page Fault而影響性能。
lhugetlb_no_page()
1)在產(chǎn)生Page Fault的虛存區(qū)域所映射的hugetlb特殊文件的頁(yè)面緩存(PageCache)中查找引發(fā)中斷的虛擬地址所在的文件頁(yè)面,如果找到則跳轉(zhuǎn)到3);
2)分配大頁(yè)面,這是通過(guò)函數(shù)完成的。分配成功后,將該頁(yè)面加入到該hugetlb文件對(duì)應(yīng)的Page Cache中,以便可以與其它進(jìn)程共享該大頁(yè)面。
3)設(shè)置相應(yīng)的PMD表項(xiàng),需要強(qiáng)調(diào)的是,為了區(qū)分大頁(yè)面與4KB頁(yè)面需要設(shè)置頁(yè)表項(xiàng)的_PAGE_PSE標(biāo)志位,使得MMU在進(jìn)行虛實(shí)地址轉(zhuǎn)換時(shí)能將此PMD表項(xiàng)作為最后一級(jí)映射,得到大頁(yè)面的物理地址。
lalloc_huge_page()
在前面提到,系統(tǒng)初始化時(shí)為每個(gè)NUMA node都初始化了相應(yīng)的空閑大頁(yè)面鏈表——hugepage_freelists[],并分配了全部的大頁(yè)面,因此,在系統(tǒng)運(yùn)行過(guò)程中分配大頁(yè)面的操作即為從該鏈表中獲取空閑大頁(yè)面的過(guò)程。至于大頁(yè)面的解除映射以及釋放,與分配與建立映射的過(guò)程相反,在此不再贅述。
1.3.5小結(jié)
Linux基于hugetlb特殊文件系統(tǒng)的大頁(yè)面支持為應(yīng)用程序的靈活性和性能優(yōu)化提供了方便。為了測(cè)試大頁(yè)面對(duì)應(yīng)用程序性能的影響,我們使用Linpack進(jìn)行了一個(gè)簡(jiǎn)單的實(shí)驗(yàn),實(shí)驗(yàn)結(jié)果表明,采用hugetlb大頁(yè)面的情況下,Linpack的性能相對(duì)于采用4KB頁(yè)面時(shí)提升了1到2個(gè)百分點(diǎn),這對(duì)于大規(guī)模的科學(xué)計(jì)算應(yīng)用來(lái)說(shuō)性能的提升是較為顯著的。除了性能的顯著提高外,簡(jiǎn)單的文件操作接口如open()、mmap()等也使得大頁(yè)面機(jī)制簡(jiǎn)單易用。從總體上講,通過(guò)hugetlbfs實(shí)現(xiàn)對(duì)大頁(yè)面的支持是成功的。
但是,從本質(zhì)上講hugetlbfs的實(shí)現(xiàn)方式僅僅是一個(gè)通過(guò)“打補(bǔ)丁”的手段來(lái)支持靈活的內(nèi)存頁(yè)面大小,這主要是受限于Linux內(nèi)核“模塊化”的特征,為了盡可能少地影響到其它內(nèi)核模塊,hugetlbfs無(wú)疑是一個(gè)很明智的選擇,同時(shí)也注定了其無(wú)法實(shí)現(xiàn)對(duì)應(yīng)用程序的透明性。
隨著芯片制造工藝的不斷進(jìn)步,物理內(nèi)存的容量會(huì)越來(lái)越大,因此Linux內(nèi)核的內(nèi)存分頁(yè)基本單位的增大是一個(gè)必然的趨勢(shì)。但如何做到對(duì)傳統(tǒng)應(yīng)用程序的完全透明性和與其它內(nèi)核模塊的兼容性,是實(shí)現(xiàn)上的難點(diǎn)。筆者在寫作本文之前曾試圖通過(guò)修改Linux內(nèi)核中定義頁(yè)面大小的宏(PAGE_SIZE)來(lái)實(shí)現(xiàn)透明的大頁(yè)面支持,但內(nèi)核中某些部分的代碼僅僅支持4KB的頁(yè)面大小,使得內(nèi)核編譯都無(wú)法通過(guò),即使經(jīng)過(guò)適當(dāng)?shù)男薷拿銖?qiáng)編譯通過(guò),內(nèi)核也無(wú)法正常啟動(dòng)。因此可以預(yù)見(jiàn)的是,實(shí)現(xiàn)Linux內(nèi)核透明的大頁(yè)面支持將是一項(xiàng)繁雜的工作。
總結(jié)
以上是生活随笔為你收集整理的huge形式_Linux hugepage使用与实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 6s测试信号软件,手机信号强度测试:苹果
- 下一篇: linux logo程序设计,教你在线设