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