<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          vmalloc原理與實現(xiàn)

          共 4805字,需瀏覽 10分鐘

           ·

          2020-10-23 14:25

          在 Linux 系統(tǒng)中的每個進程都有獨立 4GB 內存空間,而 Linux 把這 4GB 內存空間劃分為用戶內存空間(0 ~ 3GB)和內核內存空間(3GB ~ 4GB),而內核內存空間由劃分為直接內存映射區(qū)和動態(tài)內存映射區(qū)(vmalloc區(qū))。

          直接內存映射區(qū)從?3GB?開始到?3GB+896MB?處結束,直接內存映射區(qū)的特點就是物理地址與虛擬地址的關系為:虛擬地址 = 物理地址 + 3GB。而動態(tài)內存映射區(qū)不能通過這種簡單的關系關聯(lián),而是需要訪問動態(tài)內存映射區(qū)時,由內核動態(tài)申請物理內存并且映射到動態(tài)內存映射區(qū)中。下圖是動態(tài)內存映射區(qū)在內存空間的位置:

          為什么需要vmalloc區(qū)

          由于直接內存映射區(qū)(3GB ~ 3GB+896MB)是直接映射到物理地址(0 ~ 896MB)的,所以內核不能通過直接內存映射區(qū)使用到超過 896MB 之外的物理內存。這時候就需要提供一個機制能夠讓內核使用 896MB 之外的物理內存,所以 Linux 就實現(xiàn)了一個 vmalloc 機制。vmalloc 機制的目的是在內核內存空間提供一個內存區(qū),能夠讓這個內存區(qū)映射到 896MB 之外的物理內存。如下圖:

          那么什么時候使用 vmalloc 呢?一般來說,如果要申請大塊的內存就可以用vmalloc。

          vmalloc實現(xiàn)

          可以通過?vmalloc()?函數(shù)向內核申請一塊內存,其原型如下:

          void * vmalloc(unsigned long size);

          參數(shù)?size?表示要申請的內存塊大小。

          我們看看看?vmalloc()?函數(shù)的實現(xiàn),代碼如下:

          static inline void * vmalloc(unsigned long size)
          {
          return __vmalloc(size, GFP_KERNEL|__GFP_HIGHMEM, PAGE_KERNEL);
          }

          從上面代碼可以看出,vmalloc()?函數(shù)直接調用了?__vmalloc()?函數(shù),而?__vmalloc()?函數(shù)的實現(xiàn)如下:

          void * __vmalloc(unsigned long size, int gfp_mask, pgprot_t prot)
          {
          void * addr;
          struct vm_struct *area;

          size = PAGE_ALIGN(size); // 內存對齊
          if (!size || (size >> PAGE_SHIFT) > num_physpages) {
          BUG();
          return NULL;
          }

          area = get_vm_area(size, VM_ALLOC); // 申請一個合法的虛擬地址
          if (!area)
          return NULL;

          addr = area->addr;
          // 映射物理內存地址
          if (vmalloc_area_pages(VMALLOC_VMADDR(addr), size, gfp_mask, prot)) {
          vfree(addr);
          return NULL;
          }

          return addr;
          }

          __vmalloc()?函數(shù)主要工作有兩點:

          • 調用?get_vm_area()?函數(shù)申請一個合法的虛擬內存地址。

          • 調用?vmalloc_area_pages()?函數(shù)把虛擬內存地址映射到物理內存地址。

          接下來,我們看看?get_vm_area()?函數(shù)的實現(xiàn),代碼如下:

          struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
          {
          unsigned long addr;
          struct vm_struct **p, *tmp, *area;

          area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
          if (!area)
          return NULL;
          size += PAGE_SIZE;
          addr = VMALLOC_START;
          write_lock(&vmlist_lock);
          for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
          if ((size + addr) < addr)
          goto out;
          if (size + addr <= (unsigned long) tmp->addr)
          break;
          addr = tmp->size + (unsigned long) tmp->addr;
          if (addr > VMALLOC_END-size)
          goto out;
          }
          area->flags = flags;
          area->addr = (void *)addr;
          area->size = size;
          area->next = *p;
          *p = area;
          write_unlock(&vmlist_lock);
          return area;

          out:
          write_unlock(&vmlist_lock);
          kfree(area);
          return NULL;
          }

          get_vm_area()?函數(shù)比較簡單,首先申請一個類型為?vm_struct?的結構?area?用于保存申請到的虛擬內存地址。然后查找可用的虛擬內存地址,如果找到,就把虛擬內存到虛擬內存地址保存到?area?變量中。最后把?area?連接到?vmalloc?虛擬內存地址管理鏈表?vmlist?中。vmlist?鏈表最終結果如下圖:

          申請到虛擬內存地址后,__vmalloc()?函數(shù)會調用?vmalloc_area_pages()?函數(shù)來對虛擬內存地址與物理內存地址進行映射。

          我們知道,映射過程就是對進程的?頁表?進行映射。但每個進程都有一個獨立?頁表(內核線程除外),并且我們知道內核空間是所有進程共享的,那么就有個問題:如果只映射當前進程?頁表?的內核空間,那么怎么同步到其他進程的內核空間呢?

          為了解決內核空間同步問題,Linux 并不是直接對當前進程的內核空間映射的,而是對?init?進程的內核空間(init_mm)進行映射,我們來看看?vmalloc_area_pages()?函數(shù)的實現(xiàn):

          inline int vmalloc_area_pages (unsigned long address, unsigned long size,
          int gfp_mask, pgprot_t prot)
          {
          pgd_t * dir;
          unsigned long end = address + size;
          int ret;

          dir = pgd_offset_k(address); // 獲取 address 地址在 init 進程對應的頁目錄項
          spin_lock(&init_mm.page_table_lock); // 對 init_mm 上鎖
          do {
          pmd_t *pmd;

          pmd = pmd_alloc(&init_mm, dir, address);
          ret = -ENOMEM;
          if (!pmd)
          break;

          ret = -ENOMEM;
          if (alloc_area_pmd(pmd, address, end - address, gfp_mask, prot)) // 對頁目錄項進行映射
          break;

          address = (address + PGDIR_SIZE) & PGDIR_MASK;
          dir++;

          ret = 0;
          } while (address && (address < end));
          spin_unlock(&init_mm.page_table_lock);
          return ret;
          }

          從上面代碼可以看出,vmalloc_area_pages()?函數(shù)映射的主體是?init?進程的內存空間。因為映射的?init?進程的內存空間,所以當前進程訪問?vmalloc()?函數(shù)申請的內存時,由于沒有對虛擬內存進行映射,所以會發(fā)生?缺頁異常?而觸發(fā)內核調用?do_page_fault()?函數(shù)來修復。我們看看?do_page_fault()?函數(shù)對?vmalloc()?申請的內存異常處理:

          void do_page_fault(struct pt_regs *regs, unsigned long error_code)
          {
          ...
          __asm__("movl %%cr2,%0":"=r" (address)); // 獲取出錯的虛擬地址
          ...

          if (address >= TASK_SIZE && !(error_code & 5))
          goto vmalloc_fault;

          ...

          vmalloc_fault:
          {
          int offset = __pgd_offset(address);
          pgd_t *pgd, *pgd_k;
          pmd_t *pmd, *pmd_k;
          pte_t *pte_k;

          asm("movl %%cr3,%0":"=r" (pgd));
          pgd = offset + (pgd_t *)__va(pgd);
          pgd_k = init_mm.pgd + offset;

          if (!pgd_present(*pgd_k))
          goto no_context;
          set_pgd(pgd, *pgd_k);

          pmd = pmd_offset(pgd, address);
          pmd_k = pmd_offset(pgd_k, address);
          if (!pmd_present(*pmd_k))
          goto no_context;
          set_pmd(pmd, *pmd_k);

          pte_k = pte_offset(pmd_k, address);
          if (!pte_present(*pte_k))
          goto no_context;
          return;
          }
          }

          上面的代碼就是當進程訪問?vmalloc()?函數(shù)申請到的內存時,發(fā)生?缺頁異常?而進行的異常修復,主要的修復過程就是把?init?進程的?頁表項?復制到當前進程的?頁表項?中,這樣就可以實現(xiàn)所有進程的內核內存地址空間同步。


          瀏覽 99
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91丨豆花丨成人 熟女 | 翔田千里黑人一区在线观看 | 黄色一级在线 | 99热超碰在线播放 | 亚洲视频VS在线免费观看 |