久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

如何實現(xiàn)完全解析HugePages

165次閱讀
沒有評論

共計 5700 個字符,預(yù)計需要花費 15 分鐘才能閱讀完成。

這期內(nèi)容當(dāng)中丸趣 TV 小編將會給大家?guī)碛嘘P(guān)如何實現(xiàn)完全解析 HugePages,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

本文使用 Linux 內(nèi)核 2.6.23 版本

HugePages 分配器初始化

在內(nèi)核初始化時,會調(diào)用 hugetlb_init 函數(shù)對 HugePages 分配器進行初始化,其實現(xiàn)如下:

static int __init hugetlb_init(void) { unsigned long i; // 1.  初始化空閑大內(nèi)存頁鏈表  hugepage_freelists, //  內(nèi)核使用  hugepage_freelists  鏈表把空閑的大內(nèi)存頁連接起來, //  為了分析簡單,我們可以把  MAX_NUMNODES  當(dāng)成  1 for (i = 0; i   MAX_NUMNODES; ++i) INIT_LIST_HEAD(hugepage_freelists[i]); // 2. max_huge_pages  為系統(tǒng)能夠使用的大頁內(nèi)存的數(shù)量, //  由系統(tǒng)啟動項  hugepages  指定, //  這里主要申請大內(nèi)存頁,  并且保存到  hugepage_freelists  鏈表中. for (i = 0; i   max_huge_pages; ++i) { if (!alloc_fresh_huge_page()) break; } max_huge_pages = free_huge_pages = nr_huge_pages = i; return 0; }

hugetlb_init 函數(shù)主要完成兩個工作:

初始化空閑大內(nèi)存頁鏈表 hugepage_freelists,這個鏈表保存了系統(tǒng)中能夠使用的大內(nèi)存。

為系統(tǒng)申請空閑的大內(nèi)存頁,并且保存到 hugepage_freelists 鏈表中。

我們再來分析下 alloc_fresh_huge_page 函數(shù)是怎么申請大內(nèi)存頁的,其實現(xiàn)如下:

 static int alloc_fresh_huge_page(void) { static int prev_nid; struct page *page; int nid; ... // 1.  申請一個大的物理內(nèi)存頁... page = alloc_pages_node(nid, htlb_alloc_mask|__GFP_COMP|__GFP_NOWARN, HUGETLB_PAGE_ORDER); if (page) { // 2.  設(shè)置釋放大內(nèi)存頁的回調(diào)函數(shù)為  free_huge_page set_compound_page_dtor(page, free_huge_page); ... // 3. put_page  函數(shù)將會調(diào)用上面設(shè)置的  free_huge_page  函數(shù)把內(nèi)存頁放入到緩存隊列中  put_page(page); return 1; } return 0; }

所以,alloc_fresh_huge_page 函數(shù)主要完成三個工作:

調(diào)用 alloc_pages_node 函數(shù)申請一個大內(nèi)存頁(2MB)。

設(shè)置大內(nèi)存頁的釋放回調(diào)函數(shù)為 free_huge_page,當(dāng)釋放大內(nèi)存頁時,將會調(diào)用這個函數(shù)進行釋放操作。

調(diào)用 put_page 函數(shù)釋放大內(nèi)存頁,其將會調(diào)用 free_huge_page 函數(shù)進行相關(guān)操作。

那么,我們來看看 free_huge_page 函數(shù)是怎么釋放大內(nèi)存頁的,其實現(xiàn)如下:

static void free_huge_page(struct page *page) { ... enqueue_huge_page(page); //  把大內(nèi)存頁放置到空閑大內(nèi)存頁鏈表中  ... }

free_huge_page 函數(shù)主要調(diào)用 enqueue_huge_page 函數(shù)把大內(nèi)存頁添加到空閑大內(nèi)存頁鏈表中,其實現(xiàn)如下:

 static void enqueue_huge_page(struct page *page) { int nid = page_to_nid(page); //  我們假設(shè)這里一定返回  0 //  把大內(nèi)存頁添加到空閑鏈表  hugepage_freelists  中  list_add(page- lru,  hugepage_freelists[nid]); //  增加計數(shù)器  free_huge_pages++; free_huge_pages_node[nid]++; }

從上面的實現(xiàn)可知,enqueue_huge_page 函數(shù)只是簡單的把大內(nèi)存頁添加到空閑鏈表 hugepage_freelists   中,并且增加計數(shù)器。

假如我們設(shè)置了系統(tǒng)能夠使用的大內(nèi)存頁為 100 個,那么空閑大內(nèi)存頁鏈表 hugepage_freelists 的結(jié)構(gòu)如下圖所示:

所以,HugePages 分配器初始化的調(diào)用鏈為:

hugetlb_init() | + mdash; mdash;  alloc_fresh_huge_page() | | mdash; mdash;  alloc_pages_node() | mdash; mdash;  set_compound_page_dtor() + mdash; mdash;  put_page() | + mdash; mdash;  free_huge_page() | + mdash; mdash;  enqueue_huge_page()

hugetlbfs 文件系統(tǒng)

為系統(tǒng)準(zhǔn)備好空閑的大內(nèi)存頁后,現(xiàn)在來了解下怎樣分配大內(nèi)存頁。在《一文讀懂 HugePages 的原理》一文中介紹過,要申請大內(nèi)存頁,必須使用 mmap   系統(tǒng)調(diào)用把虛擬內(nèi)存映射到 hugetlbfs 文件系統(tǒng)中的文件中。

免去繁瑣的文件系統(tǒng)掛載過程,我們主要來看看當(dāng)使用 mmap 系統(tǒng)調(diào)用把虛擬內(nèi)存映射到 hugetlbfs 文件系統(tǒng)的文件時會發(fā)生什么事情。

每個文件描述符對象都有個 mmap 的方法,此方法會在調(diào)用 mmap 函數(shù)映射到文件時被觸發(fā),我們來看看 hugetlbfs 文件的 mmap   方法所對應(yīng)的真實函數(shù),如下:

const struct file_operations hugetlbfs_file_operations = { .mmap = hugetlbfs_file_mmap, .fsync = simple_sync_file, .get_unmapped_area = hugetlb_get_unmapped_area, };

從上面的代碼可以發(fā)現(xiàn),hugetlbfs 文件的 mmap 方法被設(shè)置為 hugetlbfs_file_mmap 函數(shù)。所以當(dāng)調(diào)用 mmap 函數(shù)映射  hugetlbfs 文件時,將會調(diào)用 hugetlbfs_file_mmap 函數(shù)來處理。

而 hugetlbfs_file_mmap 函數(shù)最主要的工作就是把虛擬內(nèi)存分區(qū)對象的 vm_flags 字段添加 VM_HUGETLB   標(biāo)志位,如下代碼:

static int hugetlbfs_file_mmap(struct file *file, struct vm_area_struct *vma) { ... vma- vm_flags |= VM_HUGETLB | VM_RESERVED; //  為虛擬內(nèi)存分區(qū)添加  VM_HUGETLB  標(biāo)志位  ... return ret; }

為虛擬內(nèi)存分區(qū)對象設(shè)置 VM_HUGETLB 標(biāo)志位的作用是:當(dāng)對虛擬內(nèi)存分區(qū)進行物理內(nèi)存映射時,會進行特殊的處理,下面將會介紹。

虛擬內(nèi)存與物理內(nèi)存映射

使用 mmap 函數(shù)映射到 hugetlbfs   文件后,會返回一個虛擬內(nèi)存地址。當(dāng)對這個虛擬內(nèi)存地址進行訪問 (讀寫) 時,由于此虛擬內(nèi)存地址還沒有與物理內(nèi)存地址進行映射,將會觸發(fā) 缺頁異常,內(nèi)核會調(diào)用  do_page_fault 函數(shù)對 缺頁異常 進行修復(fù)。

我們來看看整個流程,如下圖所示:

所以,最終會調(diào)用 do_page_fault 函數(shù)對 缺頁異常 進行修復(fù)操作,我們來看看 do_page_fault 做了什么工作,實現(xiàn)如下:

asmlinkage void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) { ... struct mm_struct *mm; struct vm_area_struct *vma; unsigned long address; ... mm = tsk-  // 1.  獲取當(dāng)前進程對應(yīng)的內(nèi)存管理對象  address = read_cr2(); // 2.  獲取觸發(fā)缺頁異常的虛擬內(nèi)存地址  ... vma = find_vma(mm, address); // 3.  通過虛擬內(nèi)存地址獲取對應(yīng)的虛擬內(nèi)存分區(qū)對象  ... // 4.  調(diào)用  handle_mm_fault  函數(shù)對異常進行修復(fù)  fault = handle_mm_fault(mm, vma, address, write); ... return; }

上面代碼對 do_page_fault 進行了精簡,精簡后主要完成 4 個工作:

獲取當(dāng)前進程對應(yīng)的內(nèi)存管理對象。

調(diào)用 read_cr2 獲取觸發(fā)缺頁異常的虛擬內(nèi)存地址。

通過觸發(fā) 缺頁異常 的虛擬內(nèi)存地址獲取對應(yīng)的虛擬內(nèi)存分區(qū)對象。

調(diào)用 handle_mm_fault 函數(shù)對 缺頁異常 進行修復(fù)。

我們繼續(xù)來看看 handle_mm_fault 函數(shù)的實現(xiàn),代碼如下:

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, int write_access) { ... if (unlikely(is_vm_hugetlb_page(vma))) //  虛擬內(nèi)存分區(qū)是否需要使用  HugePages return hugetlb_fault(mm, vma, address, write_access); //  如果使用  HugePages,  就調(diào)用  hugetlb_fault  進行處理  ... }

對 handle_mm_fault 函數(shù)進行精簡后,邏輯就非常清晰。如果虛擬內(nèi)存分區(qū)使用 HugePages,那么就調(diào)用 hugetlb_fault   函數(shù)進行處理(由于我們分析使用 HugePages 的情況,所以剛好進入這個分支)。

hugetlb_fault 函數(shù)主要對進程的頁表進行填充,所以我們先來回顧一下 HugePages 對應(yīng)的頁表結(jié)構(gòu),如下圖:

從上圖可以看出,使用 HugePages 后,頁中間目錄 直接指向物理內(nèi)存頁。所以,hugetlb_fault 函數(shù)主要就是對 頁中間目錄項   進行填充。實現(xiàn)如下:

int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, int write_access) { pte_t *ptep; pte_t entry; int ret; ptep = huge_pte_alloc(mm, address); // 1.  找到虛擬內(nèi)存地址對應(yīng)的頁中間目錄項  ... entry = *ptep; if (pte_none(entry)) { //  如果頁中間目錄項還沒進行映射  // 2.  那么調(diào)用  hugetlb_no_page  函數(shù)進行映射操作  ret = hugetlb_no_page(mm, vma, address, ptep, write_access); ... return ret; } ... }

對 hugetlb_fault 函數(shù)進行精簡后,主要完成兩個工作:

通過觸發(fā) 缺頁異常 的虛擬內(nèi)存地址找到其對應(yīng)的 頁中間目錄項。

調(diào)用 hugetlb_no_page 函數(shù)對 頁中間目錄項 進行映射操作。

我們再來看看 hugetlb_no_page 函數(shù)怎么對 頁中間目錄項 進行填充:

static int hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pte_t *ptep, int write_access) { ... page = find_lock_page(mapping, idx); if (!page) { ... // 1.  從空閑大內(nèi)存頁鏈表  hugepage_freelists  中申請一個大內(nèi)存頁  page = alloc_huge_page(vma, address); ... } ... // 2.  通過大內(nèi)存頁的物理地址生成頁中間目錄項的值  new_pte = make_huge_pte(vma, page, ((vma- vm_flags   VM_WRITE)   (vma- vm_flags   VM_SHARED))); // 3.  設(shè)置頁中間目錄項的值為上面生成的值  set_huge_pte_at(mm, address, ptep, new_pte); ... return ret; }

通過對 hugetlb_no_page 函數(shù)進行精簡后,主要完成 3 個工作:

調(diào)用 alloc_huge_page 函數(shù)從空閑大內(nèi)存頁鏈表 hugepage_freelists 中申請一個大內(nèi)存頁。

通過大內(nèi)存頁的物理地址生成頁中間目錄項的值。

設(shè)置頁中間目錄項的值為上面生成的值。

至此,HugePages 的映射過程已經(jīng)完成。

還有個問題,就是 CPU 怎么知道 頁中間表項 指向的是 頁表 還是 大內(nèi)存頁 呢?

這是因為 頁中間表項 有個 PSE 的標(biāo)志位,如果將其設(shè)置為 1,那么就表明其指向 大內(nèi)存頁,否則就指向 頁表。

上述就是丸趣 TV 小編為大家分享的如何實現(xiàn)完全解析 HugePages 了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注丸趣 TV 行業(yè)資訊頻道。

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-08-25發(fā)表,共計5700字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 高碑店市| 翁源县| 祥云县| 利辛县| 上蔡县| 凤翔县| 略阳县| 乐都县| 武定县| 安远县| 邯郸县| 龙山县| 清徐县| 永福县| 大化| 固镇县| 西乌| 白城市| 青铜峡市| 桐乡市| 万州区| 乌鲁木齐市| 绥宁县| 从江县| 关岭| 农安县| 厦门市| 徐州市| 安吉县| 平陆县| 虎林市| 延庆县| 龙海市| 西乌珠穆沁旗| 甘孜县| 平江县| 普兰店市| 天气| 金川县| 保定市| 隆德县|