中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何實現完全解析HugePages

發布時間:2021-11-01 10:18:44 來源:億速云 閱讀:105 作者:柒染 欄目:系統運維

這期內容當中小編將會給大家帶來有關如何實現完全解析HugePages,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

本文使用 Linux 內核 2.6.23 版本

HugePages分配器初始化

在內核初始化時,會調用 hugetlb_init 函數對 HugePages 分配器進行初始化,其實現如下:

static int __init hugetlb_init(void) {     unsigned long i;      // 1. 初始化空閑大內存頁鏈表 hugepage_freelists,      //    內核使用 hugepage_freelists 鏈表把空閑的大內存頁連接起來,     //    為了分析簡單,我們可以把 MAX_NUMNODES 當成 1     for (i = 0; i < MAX_NUMNODES; ++i)                   INIT_LIST_HEAD(&hugepage_freelists[i]);       // 2. max_huge_pages 為系統能夠使用的大頁內存的數量,     //    由系統啟動項 hugepages 指定,     //    這里主要申請大內存頁, 并且保存到 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 函數主要完成兩個工作:

  • 初始化空閑大內存頁鏈表 hugepage_freelists,這個鏈表保存了系統中能夠使用的大內存。

  • 為系統申請空閑的大內存頁,并且保存到 hugepage_freelists 鏈表中。

我們再來分析下 alloc_fresh_huge_page 函數是怎么申請大內存頁的,其實現如下:

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

所以,alloc_fresh_huge_page 函數主要完成三個工作:

  • 調用 alloc_pages_node 函數申請一個大內存頁(2MB)。

  • 設置大內存頁的釋放回調函數為 free_huge_page,當釋放大內存頁時,將會調用這個函數進行釋放操作。

  • 調用 put_page 函數釋放大內存頁,其將會調用 free_huge_page 函數進行相關操作。

那么,我們來看看 free_huge_page 函數是怎么釋放大內存頁的,其實現如下:

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

free_huge_page 函數主要調用 enqueue_huge_page 函數把大內存頁添加到空閑大內存頁鏈表中,其實現如下:

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

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

假如我們設置了系統能夠使用的大內存頁為 100 個,那么空閑大內存頁鏈表 hugepage_freelists 的結構如下圖所示:

如何實現完全解析HugePages

所以,HugePages 分配器初始化的調用鏈為:

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 文件系統

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

免去繁瑣的文件系統掛載過程,我們主要來看看當使用 mmap 系統調用把虛擬內存映射到 hugetlbfs 文件系統的文件時會發生什么事情。

每個文件描述符對象都有個 mmap 的方法,此方法會在調用 mmap 函數映射到文件時被觸發,我們來看看 hugetlbfs 文件的 mmap  方法所對應的真實函數,如下:

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

從上面的代碼可以發現,hugetlbfs 文件的 mmap 方法被設置為 hugetlbfs_file_mmap 函數。所以當調用 mmap 函數映射  hugetlbfs 文件時,將會調用 hugetlbfs_file_mmap 函數來處理。

而 hugetlbfs_file_mmap 函數最主要的工作就是把虛擬內存分區對象的 vm_flags 字段添加 VM_HUGETLB  標志位,如下代碼:

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

為虛擬內存分區對象設置 VM_HUGETLB 標志位的作用是:當對虛擬內存分區進行物理內存映射時,會進行特殊的處理,下面將會介紹。

虛擬內存與物理內存映射

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

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

如何實現完全解析HugePages

所以,最終會調用 do_page_fault 函數對 缺頁異常 進行修復操作,我們來看看 do_page_fault 做了什么工作,實現如下:

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. 獲取當前進程對應的內存管理對象     address = read_cr2(); // 2. 獲取觸發缺頁異常的虛擬內存地址      ...     vma = find_vma(mm, address); // 3. 通過虛擬內存地址獲取對應的虛擬內存分區對象     ...      // 4. 調用 handle_mm_fault 函數對異常進行修復     fault = handle_mm_fault(mm, vma, address, write);     ...      return; }

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

  • 獲取當前進程對應的內存管理對象。

  • 調用 read_cr2 獲取觸發缺頁異常的虛擬內存地址。

  • 通過觸發 缺頁異常 的虛擬內存地址獲取對應的虛擬內存分區對象。

  • 調用 handle_mm_fault 函數對 缺頁異常 進行修復。

我們繼續來看看 handle_mm_fault 函數的實現,代碼如下:

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))) // 虛擬內存分區是否需要使用 HugePages         return hugetlb_fault(mm, vma, address, write_access); // 如果使用 HugePages, 就調用 hugetlb_fault 進行處理     ... }

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

hugetlb_fault 函數主要對進程的頁表進行填充,所以我們先來回顧一下 HugePages 對應的頁表結構,如下圖:

如何實現完全解析HugePages

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

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. 找到虛擬內存地址對應的頁中間目錄項     ...     entry = *ptep;      if (pte_none(entry)) { // 如果頁中間目錄項還沒進行映射         // 2. 那么調用 hugetlb_no_page 函數進行映射操作         ret = hugetlb_no_page(mm, vma, address, ptep, write_access);         ...         return ret;     }     ... }

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

  • 通過觸發 缺頁異常 的虛擬內存地址找到其對應的 頁中間目錄項。

  • 調用 hugetlb_no_page 函數對 頁中間目錄項 進行映射操作。

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

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

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

  • 調用 alloc_huge_page 函數從空閑大內存頁鏈表 hugepage_freelists 中申請一個大內存頁。

  • 通過大內存頁的物理地址生成頁中間目錄項的值。

  • 設置頁中間目錄項的值為上面生成的值。

至此,HugePages 的映射過程已經完成。

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

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

上述就是小編為大家分享的如何實現完全解析HugePages了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

德惠市| 海晏县| 灵山县| 泰州市| 武鸣县| 寿阳县| 青铜峡市| 阿巴嘎旗| 彭泽县| 离岛区| 南召县| 巴林右旗| 漳州市| 剑川县| 巴塘县| 克什克腾旗| 慈溪市| 麻阳| 开平市| 咸阳市| 辽阳县| 库车县| 新营市| 抚宁县| 长丰县| 宝丰县| 云和县| 大连市| 澳门| 西城区| 驻马店市| 民乐县| 古蔺县| 龙川县| 舒城县| 福建省| 建始县| 凌源市| 华宁县| 德化县| 平乐县|