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

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

創(chuàng)新互聯(lián)專注于撫順網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供撫順營(yíng)銷型網(wǎng)站建設(shè),撫順網(wǎng)站制作、撫順網(wǎng)頁(yè)設(shè)計(jì)、撫順網(wǎng)站官網(wǎng)定制、小程序開發(fā)服務(wù),打造撫順網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供撫順網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。

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

HugePages分配器初始化

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

static int __init hugetlb_init(void) {     unsigned long i;      // 1. 初始化空閑大內(nèi)存頁(yè)鏈表 hugepage_freelists,      //    內(nèi)核使用 hugepage_freelists 鏈表把空閑的大內(nèi)存頁(yè)連接起來(lái),     //    為了分析簡(jiǎn)單,我們可以把 MAX_NUMNODES 當(dāng)成 1     for (i = 0; i < MAX_NUMNODES; ++i)                   INIT_LIST_HEAD(&hugepage_freelists[i]);       // 2. max_huge_pages 為系統(tǒng)能夠使用的大頁(yè)內(nèi)存的數(shù)量,     //    由系統(tǒng)啟動(dòng)項(xiàng) hugepages 指定,     //    這里主要申請(qǐng)大內(nèi)存頁(yè), 并且保存到 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ù)主要完成兩個(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)如下:

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

所以,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)如下:

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

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

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

從上面的實(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)如下圖所示:

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

所以,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)存頁(yè)后,現(xiàn)在來(lái)了解下怎樣分配大內(nèi)存頁(yè)。在《一文讀懂 HugePages的原理》一文中介紹過,要申請(qǐng)大內(nèi)存頁(yè),必須使用 mmap  系統(tǒng)調(diào)用把虛擬內(nèi)存映射到 hugetlbfs 文件系統(tǒng)中的文件中。

免去繁瑣的文件系統(tǒng)掛載過程,我們主要來(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ù),如下:

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 文件時(shí),將會(huì)調(diào)用 hugetlbfs_file_mmap 函數(shù)來(lái)處理。

而 hugetlbfs_file_mmap 函數(shù)最主要的工作就是把虛擬內(nèi)存分區(qū)對(duì)象的 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ū)對(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)行訪問(讀寫)時(shí),由于此虛擬內(nè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è)流程,如下圖所示:

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

所以,最終會(huì)調(diào)用 do_page_fault 函數(shù)對(duì) 缺頁(yè)異常 進(jìn)行修復(fù)操作,我們來(lái)看看 do_page_fault 做了什么工作,實(shí)現(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->mm;         // 1. 獲取當(dāng)前進(jìn)程對(duì)應(yīng)的內(nèi)存管理對(duì)象     address = read_cr2(); // 2. 獲取觸發(fā)缺頁(yè)異常的虛擬內(nèi)存地址      ...     vma = find_vma(mm, address); // 3. 通過虛擬內(nèi)存地址獲取對(duì)應(yīng)的虛擬內(nèi)存分區(qū)對(duì)象     ...      // 4. 調(diào)用 handle_mm_fault 函數(shù)對(duì)異常進(jìn)行修復(fù)     fault = handle_mm_fault(mm, vma, address, write);     ...      return; }

上面代碼對(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)存地址。

  • 通過觸發(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),代碼如下:

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 進(jì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),如下圖:

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

從上圖可以看出,使用 HugePages 后,頁(yè)中間目錄 直接指向物理內(nèi)存頁(yè)。所以,hugetlb_fault 函數(shù)主要就是對(duì) 頁(yè)中間目錄項(xiàng)  進(jìn)行填充。實(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)存地址對(duì)應(yīng)的頁(yè)中間目錄項(xiàng)     ...     entry = *ptep;      if (pte_none(entry)) { // 如果頁(yè)中間目錄項(xiàng)還沒進(jìn)行映射         // 2. 那么調(diào)用 hugetlb_no_page 函數(shù)進(jìn)行映射操作         ret = hugetlb_no_page(mm, vma, address, ptep, write_access);         ...         return ret;     }     ... }

對(duì) hugetlb_fault 函數(shù)進(jìn)行精簡(jiǎn)后,主要完成兩個(gè)工作:

  • 通過觸發(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)行填充:

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)存頁(yè)鏈表 hugepage_freelists 中申請(qǐng)一個(gè)大內(nèi)存頁(yè)         page = alloc_huge_page(vma, address);         ...     }     ...     // 2. 通過大內(nèi)存頁(yè)的物理地址生成頁(yè)中間目錄項(xiàng)的值     new_pte = make_huge_pte(vma, page, ((vma->vm_flags & VM_WRITE)                                             && (vma->vm_flags & VM_SHARED)));      // 3. 設(shè)置頁(yè)中間目錄項(xiàng)的值為上面生成的值     set_huge_pte_at(mm, address, ptep, new_pte);     ...     return ret; }

通過對(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è)。

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

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

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

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

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

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

本文題目:如何實(shí)現(xiàn)完全解析HugePages
瀏覽路徑:http://muchs.cn/article16/gheddg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、App設(shè)計(jì)、建站公司、網(wǎng)頁(yè)設(shè)計(jì)公司、小程序開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)