一文帶你實(shí)現(xiàn)完全解析HugePages(大內(nèi)存頁(yè))(趕快學(xué)習(xí)起來(lái)~)
我們來(lái)分析一下 Linux 內(nèi)核是怎么實(shí)現(xiàn) HugePages 分配的。
本文使用 Linux 內(nèi)核 2.6.23 版本
HugePages分配器初始化
在內(nèi)核初始化時(shí),會(huì)調(diào)用 hugetlb_init 函數(shù)對(duì) HugePages 分配器進(jìn)行初始化,其實(shí)現(xiàn)如下:
hugetlb_init 函數(shù)主要完成兩個(gè)工作:
初始化空閑大內(nèi)存頁(yè)鏈表 hugepage_freelists,這個(gè)鏈表保存了系統(tǒng)中能夠使用的大內(nèi)存。
為系統(tǒng)申請(qǐng)空閑的大內(nèi)存頁(yè),并且保存到 hugepage_freelists 鏈表中。
我們?cè)賮?lái)分析下 alloc_fresh_huge_page 函數(shù)是怎么申請(qǐng)大內(nèi)存頁(yè)的,其實(shí)現(xiàn)如下:
所以,alloc_fresh_huge_page 函數(shù)主要完成三個(gè)工作:
調(diào)用 alloc_pages_node 函數(shù)申請(qǐng)一個(gè)大內(nèi)存頁(yè)(2MB)。
設(shè)置大內(nèi)存頁(yè)的釋放回調(diào)函數(shù)為 free_huge_page,當(dāng)釋放大內(nèi)存頁(yè)時(shí),將會(huì)調(diào)用這個(gè)函數(shù)進(jìn)行釋放操作。
調(diào)用 put_page 函數(shù)釋放大內(nèi)存頁(yè),其將會(huì)調(diào)用 free_huge_page 函數(shù)進(jìn)行相關(guān)操作。
那么,我們來(lái)看看 free_huge_page 函數(shù)是怎么釋放大內(nèi)存頁(yè)的,其實(shí)現(xiàn)如下:
free_huge_page 函數(shù)主要調(diào)用 enqueue_huge_page 函數(shù)把大內(nèi)存頁(yè)添加到空閑大內(nèi)存頁(yè)鏈表中,其實(shí)現(xiàn)如下:
從上面的實(shí)現(xiàn)可知,enqueue_huge_page 函數(shù)只是簡(jiǎn)單的把大內(nèi)存頁(yè)添加到空閑鏈表 hugepage_freelists 中,并且增加計(jì)數(shù)器。
假如我們?cè)O(shè)置了系統(tǒng)能夠使用的大內(nèi)存頁(yè)為 100 個(gè),那么空閑大內(nèi)存頁(yè)鏈表 hugepage_freelists 的結(jié)構(gòu)如下圖所示:

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。∏?00名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


?
所以,HugePages 分配器初始化的調(diào)用鏈為:
hugetlbfs 文件系統(tǒng)
為系統(tǒng)準(zhǔn)備好空閑的大內(nèi)存頁(yè)后,現(xiàn)在來(lái)了解下怎樣分配大內(nèi)存頁(yè)。在《一文讀懂 HugePages的原理》一文中介紹過(guò),要申請(qǐng)大內(nèi)存頁(yè),必須使用 mmap 系統(tǒng)調(diào)用把虛擬內(nèi)存映射到 hugetlbfs 文件系統(tǒng)中的文件中。
免去繁瑣的文件系統(tǒng)掛載過(guò)程,我們主要來(lái)看看當(dāng)使用 mmap 系統(tǒng)調(diào)用把虛擬內(nèi)存映射到 hugetlbfs 文件系統(tǒng)的文件時(shí)會(huì)發(fā)生什么事情。
每個(gè)文件描述符對(duì)象都有個(gè) mmap 的方法,此方法會(huì)在調(diào)用 mmap 函數(shù)映射到文件時(shí)被觸發(fā),我們來(lái)看看 hugetlbfs 文件的 mmap 方法所對(duì)應(yīng)的真實(shí)函數(shù),如下:
從上面的代碼可以發(fā)現(xiàn),hugetlbfs 文件的 mmap 方法被設(shè)置為 hugetlbfs_file_mmap 函數(shù)。所以當(dāng)調(diào)用 mmap 函數(shù)映射 hugetlbfs 文件時(shí),將會(huì)調(diào)用 hugetlbfs_file_mmap 函數(shù)來(lái)處理。
而 hugetlbfs_file_mmap 函數(shù)最主要的工作就是把虛擬內(nèi)存分區(qū)對(duì)象的 vm_flags 字段添加 VM_HUGETLB 標(biāo)志位,如下代碼:
為虛擬內(nèi)存分區(qū)對(duì)象設(shè)置 VM_HUGETLB 標(biāo)志位的作用是:當(dāng)對(duì)虛擬內(nèi)存分區(qū)進(jìn)行物理內(nèi)存映射時(shí),會(huì)進(jìn)行特殊的處理,下面將會(huì)介紹。
虛擬內(nèi)存與物理內(nèi)存映射
使用 mmap 函數(shù)映射到 hugetlbfs 文件后,會(huì)返回一個(gè)虛擬內(nèi)存地址。當(dāng)對(duì)這個(gè)虛擬內(nèi)存地址進(jìn)行訪問(wèn)(讀寫(xiě))時(shí),由于此虛擬內(nèi)存地址還沒(méi)有與物理內(nèi)存地址進(jìn)行映射,將會(huì)觸發(fā) 缺頁(yè)異常,內(nèi)核會(huì)調(diào)用 do_page_fault 函數(shù)對(duì) 缺頁(yè)異常 進(jìn)行修復(fù)。
我們來(lái)看看整個(gè)流程,如下圖所示:

所以,最終會(huì)調(diào)用 do_page_fault 函數(shù)對(duì) 缺頁(yè)異常 進(jìn)行修復(fù)操作,我們來(lái)看看 do_page_fault 做了什么工作,實(shí)現(xiàn)如下:
上面代碼對(duì) do_page_fault 進(jìn)行了精簡(jiǎn),精簡(jiǎn)后主要完成4個(gè)工作:
獲取當(dāng)前進(jìn)程對(duì)應(yīng)的內(nèi)存管理對(duì)象。
調(diào)用 read_cr2 獲取觸發(fā)缺頁(yè)異常的虛擬內(nèi)存地址。
通過(guò)觸發(fā) 缺頁(yè)異常 的虛擬內(nèi)存地址獲取對(duì)應(yīng)的虛擬內(nèi)存分區(qū)對(duì)象。
調(diào)用 handle_mm_fault 函數(shù)對(duì) 缺頁(yè)異常 進(jìn)行修復(fù)。
我們繼續(xù)來(lái)看看 handle_mm_fault 函數(shù)的實(shí)現(xiàn),代碼如下:
對(duì) handle_mm_fault 函數(shù)進(jìn)行精簡(jiǎn)后,邏輯就非常清晰。如果虛擬內(nèi)存分區(qū)使用 HugePages,那么就調(diào)用 hugetlb_fault 函數(shù)進(jìn)行處理(由于我們分析使用 HugePages 的情況,所以剛好進(jìn)入這個(gè)分支)。
hugetlb_fault 函數(shù)主要對(duì)進(jìn)程的頁(yè)表進(jìn)行填充,所以我們先來(lái)回顧一下 HugePages 對(duì)應(yīng)的頁(yè)表結(jié)構(gòu),如下圖:

從上圖可以看出,使用 HugePages 后,頁(yè)中間目錄 直接指向物理內(nèi)存頁(yè)。所以,hugetlb_fault 函數(shù)主要就是對(duì) 頁(yè)中間目錄項(xiàng) 進(jìn)行填充。實(shí)現(xiàn)如下:
對(duì) hugetlb_fault 函數(shù)進(jìn)行精簡(jiǎn)后,主要完成兩個(gè)工作:
通過(guò)觸發(fā) 缺頁(yè)異常 的虛擬內(nèi)存地址找到其對(duì)應(yīng)的 頁(yè)中間目錄項(xiàng)。
調(diào)用 hugetlb_no_page 函數(shù)對(duì) 頁(yè)中間目錄項(xiàng) 進(jìn)行映射操作。
我們?cè)賮?lái)看看 hugetlb_no_page 函數(shù)怎么對(duì) 頁(yè)中間目錄項(xiàng) 進(jìn)行填充:
通過(guò)對(duì) hugetlb_no_page 函數(shù)進(jìn)行精簡(jiǎn)后,主要完成3個(gè)工作:
調(diào)用 alloc_huge_page 函數(shù)從空閑大內(nèi)存頁(yè)鏈表 hugepage_freelists 中申請(qǐng)一個(gè)大內(nèi)存頁(yè)。
通過(guò)大內(nèi)存頁(yè)的物理地址生成頁(yè)中間目錄項(xiàng)的值。
設(shè)置頁(yè)中間目錄項(xiàng)的值為上面生成的值。
至此,HugePages 的映射過(guò)程已經(jīng)完成。
還有個(gè)問(wèn)題,就是 CPU 怎么知道 頁(yè)中間表項(xiàng) 指向的是 頁(yè)表 還是 大內(nèi)存頁(yè) 呢? 這是因?yàn)??頁(yè)中間表項(xiàng) 有個(gè) PSE 的標(biāo)志位,如果將其設(shè)置為1,那么就表明其指向 大內(nèi)存頁(yè) ,否則就指向 頁(yè)表。
總結(jié)
本文介紹了 HugePages 實(shí)現(xiàn)的整個(gè)流程,當(dāng)然本文也只是介紹了申請(qǐng)內(nèi)存的流程,釋放內(nèi)存的流程并沒(méi)有分析,如果有興趣的話(huà)可以自己查閱源碼。
