Linux內存管理--基本概念【轉】


 
 

1. Linux物理內存三級架構

     對於內存管理,Linux采用了與具體體系架構不相關的設計模型,實現了良好的可伸縮性。它主要由內存節點node、內存區域zone和物理頁框page三級架構組成。

    • 內存節點node

       內存節點node是計算機系統中對物理內存的一種描述方法,一個總線主設備訪問位於同一個節點中的任意內存單元所花的代價相同,而訪問任意兩個不同節點中的內存單元所花的代價不同。在一致存儲結構(Uniform Memory Architecture,簡稱UMA)計算機系統中只有一個節點,而在非一致性存儲結構(NUMA)計算機系統中有多個節點。Linux內核中使用數據結構pg_data_t來表示內存節點node。如常用的ARM架構為UMA架構。

    •  內存區域zone

       內存區域位於同一個內存節點之內,由於各種原因它們的用途和使用方法並不一樣。如基於IA32體系結構的個人計算機系統中,由於歷史原因使得ISA設備只能使用最低16MB來進行DMA傳輸。又如,由於Linux內核采用

 

    •  物理頁框page

 

2. Linux虛擬內存三級頁表

      Linux虛擬內存三級管理由以下三級組成:

     • PGD: Page Global Directory (頁目錄)

     • PMD: Page Middle Directory (頁目錄)

     • PTE:  Page Table Entry  (頁表項)

    

     每一級有以下三個關鍵描述宏:

     • SHIFT

     • SIZE

     • MASK

        如頁的對應描述為:

[cpp]  view plain  copy
 
  1. /* PAGE_SHIFT determines the page size  asm/page.h */  
  2. #define PAGE_SHIFT      12  
  3. #define PAGE_SIZE       (_AC(1,UL) << PAGE_SHIFT)  
  4. #define PAGE_MASK       (~(PAGE_SIZE-1))  

    數據結構定義如下:

[cpp]  view plain  copy
 
  1. /* asm/page.h */  
  2. typedef unsigned long pteval_t;  
  3.   
  4. typedef pteval_t pte_t;  
  5. typedef unsigned long pmd_t;  
  6. typedef unsigned long pgd_t[2];  
  7. typedef unsigned long pgprot_t;  
  8.   
  9. #define pte_val(x)      (x)  
  10. #define pmd_val(x)      (x)  
  11. #define pgd_val(x)  ((x)[0])  
  12. #define pgprot_val(x)   (x)  
  13.   
  14. #define __pte(x)        (x)  
  15. #define __pmd(x)        (x)  
  16. #define __pgprot(x)     (x)  

 

2.1 Page Directory (PGD and PMD)

     每個進程有它自己的PGD( Page Global Directory),它是一個物理頁,並包含一個pgd_t數組。其定義見<asm/page.h>。 進程的pgd_t數據見 task_struct -> mm_struct -> pgd_t * pgd;    

     ARM架構的PGD和PMD的定義如下<arch/arm/include/asm/pgtable.h>:

[cpp]  view plain  copy
 
  1. <p>#define PTRS_PER_PTE  512    // PTE中可包含的指針<u32>數 (21-12=9bit)  
  2. #define PTRS_PER_PMD  1  
  3. #define PTRS_PER_PGD  2048   // PGD中可包含的指針<u32>數 (32-21=11bit)</p><p>#define PTE_HWTABLE_PTRS (PTRS_PER_PTE)  
  4. #define PTE_HWTABLE_OFF  (PTE_HWTABLE_PTRS * sizeof(pte_t))  
  5. #define PTE_HWTABLE_SIZE (PTRS_PER_PTE * sizeof(u32))</p><p>/*  
  6.  * PMD_SHIFT determines the size of the area a second-level page table can map  
  7.  * PGDIR_SHIFT determines what a third-level page table entry can map  
  8.  */  
  9. #define PMD_SHIFT  21  
  10. #define PGDIR_SHIFT  21</p>  
  11.  <span style="font-size:18px;">     虛擬地址SHIFT宏圖:</span>  

     虛擬地址MASK和SIZE宏圖:

 

 

 2.2 Page Table Entry

      PTEs, PMDs和PGDs分別由pte_t, pmd_t 和pgd_t來描述。為了存儲保護位,pgprot_t被定義,它擁有相關的flags並經常被存儲在page table entry低位(lower bits),其具體的存儲方式依賴於CPU架構。

     每個pte_t指向一個物理頁的地址,並且所有的地址都是頁對齊的。因此在32位地址中有PAGE_SHIFT(12)位是空閑的,它可以為PTE的狀態位。

     PTE的保護和狀態位如下圖所示:

2.3 如何通過3級頁表訪問物理內存

       為了通過PGD、PMD和PTE訪問物理內存,其相關宏在asm/pgtable.h中定義。

       • pgd_offset 

       根據當前虛擬地址和當前進程的mm_struct獲取pgd項的宏定義如下: 

[cpp]  view plain  copy
 
  1. /* to find an entry in a page-table-directory */  
  2. #define pgd_index(addr)     ((addr) >> PGDIR_SHIFT)  //獲得在pgd表中的索引  
  3.   
  4. #define pgd_offset(mm, addr)    ((mm)->pgd + pgd_index(addr)) //獲得pmd表的起始地址  
  5.   
  6. /* to find an entry in a kernel page-table-directory */  
  7. #define pgd_offset_k(addr)  pgd_offset(&init_mm, addr)  

         • pmd_offset
             根據通過pgd_offset獲取的pgd 項和虛擬地址,獲取相關的pmd項(即pte表的起始地址) 

[cpp]  view plain  copy
 
  1. /* Find an entry in the second-level page table.. */  
  2. #define pmd_offset(dir, addr)   ((pmd_t *)(dir))   //即為pgd項的值  

        • pte_offset

      根據通過pmd_offset獲取的pmd項和虛擬地址,獲取相關的pte項(即物理頁的起始地址)

[cpp]  view plain  copy
 
  1. #ifndef CONFIG_HIGHPTE  
  2. #define __pte_map(pmd)      pmd_page_vaddr(*(pmd))  
  3. #define __pte_unmap(pte)    do { } while (0)  
  4. #else  
  5. #define __pte_map(pmd)      (pte_t *)kmap_atomic(pmd_page(*(pmd)))  
  6. #define __pte_unmap(pte)    kunmap_atomic(pte)  
  7. #endif  
  8.   
  9. #define pte_index(addr)     (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))  
  10.   
  11. #define pte_offset_kernel(pmd,addr) (pmd_page_vaddr(*(pmd)) + pte_index(addr))  
  12.   
  13. #define pte_offset_map(pmd,addr)    (__pte_map(pmd) + pte_index(addr))  
  14. #define pte_unmap(pte)          __pte_unmap(pte)  
  15.   
  16. #define pte_pfn(pte)        (pte_val(pte) >> PAGE_SHIFT)  
  17. #define pfn_pte(pfn,prot)   __pte(__pfn_to_phys(pfn) | pgprot_val(prot))  
  18.   
  19. #define pte_page(pte)       pfn_to_page(pte_pfn(pte))  
  20. #define mk_pte(page,prot)   pfn_pte(page_to_pfn(page), prot)  
  21.   
  22. #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)  
  23. #define pte_clear(mm,addr,ptep) set_pte_ext(ptep, __pte(0), 0)  

        其示意圖如下圖所示:

 
  2.4 根據虛擬地址獲取物理頁的示例代碼

        根據虛擬地址獲取物理頁的示例代碼詳見<mm/memory.c中的函數follow_page>。

 

[cpp]  view plain  copy
 
  1. /** 
  2.  * follow_page - look up a page descriptor from a user-virtual address 
  3.  * @vma: vm_area_struct mapping @address 
  4.  * @address: virtual address to look up 
  5.  * @flags: flags modifying lookup behaviour 
  6.  * 
  7.  * @flags can have FOLL_ flags set, defined in <linux/mm.h> 
  8.  * 
  9.  * Returns the mapped (struct page *), %NULL if no mapping exists, or 
  10.  * an error pointer if there is a mapping to something not represented 
  11.  * by a page descriptor (see also vm_normal_page()). 
  12.  */  
  13. struct page *follow_page(struct vm_area_struct *vma, unsigned long address,  
  14.             unsigned int flags)  
  15. {  
  16.     pgd_t *pgd;  
  17.     pud_t *pud;  
  18.     pmd_t *pmd;  
  19.     pte_t *ptep, pte;  
  20.     spinlock_t *ptl;  
  21.     struct page *page;  
  22.     struct mm_struct *mm = vma->vm_mm;  
  23.   
  24.     page = follow_huge_addr(mm, address, flags & FOLL_WRITE);  
  25.     if (!IS_ERR(page)) {  
  26.         BUG_ON(flags & FOLL_GET);  
  27.         goto out;  
  28.     }  
  29.   
  30.     page = NULL;  
  31.     pgd = pgd_offset(mm, address);  
  32.     if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))  
  33.         goto no_page_table;  
  34.   
  35.     pud = pud_offset(pgd, address);  
  36.     if (pud_none(*pud))  
  37.         goto no_page_table;  
  38.     if (pud_huge(*pud) && vma->vm_flags & VM_HUGETLB) {  
  39.         BUG_ON(flags & FOLL_GET);  
  40.         page = follow_huge_pud(mm, address, pud, flags & FOLL_WRITE);  
  41.         goto out;  
  42.     }  
  43.     if (unlikely(pud_bad(*pud)))  
  44.         goto no_page_table;  
  45.   
  46.     pmd = pmd_offset(pud, address);  
  47.     if (pmd_none(*pmd))  
  48.         goto no_page_table;  
  49.     if (pmd_huge(*pmd) && vma->vm_flags & VM_HUGETLB) {  
  50.         BUG_ON(flags & FOLL_GET);  
  51.         page = follow_huge_pmd(mm, address, pmd, flags & FOLL_WRITE);  
  52.         goto out;  
  53.     }  
  54.     if (pmd_trans_huge(*pmd)) {  
  55.         if (flags & FOLL_SPLIT) {  
  56.             split_huge_page_pmd(mm, pmd);  
  57.             goto split_fallthrough;  
  58.         }  
  59.         spin_lock(&mm->page_table_lock);  
  60.         if (likely(pmd_trans_huge(*pmd))) {  
  61.             if (unlikely(pmd_trans_splitting(*pmd))) {  
  62.                 spin_unlock(&mm->page_table_lock);  
  63.                 wait_split_huge_page(vma->anon_vma, pmd);  
  64.             } else {  
  65.                 page = follow_trans_huge_pmd(mm, address,  
  66.                                  pmd, flags);  
  67.                 spin_unlock(&mm->page_table_lock);  
  68.                 goto out;  
  69.             }  
  70.         } else  
  71.             spin_unlock(&mm->page_table_lock);  
  72.         /* fall through */  
  73.     }  
  74. split_fallthrough:  
  75.     if (unlikely(pmd_bad(*pmd)))  
  76.         goto no_page_table;  
  77.   
  78.     ptep = pte_offset_map_lock(mm, pmd, address, &ptl);  
  79.   
  80.     pte = *ptep;  
  81.     if (!pte_present(pte))  
  82.         goto no_page;  
  83.     if ((flags & FOLL_WRITE) && !pte_write(pte))  
  84.         goto unlock;  
  85.   
  86.     page = vm_normal_page(vma, address, pte);  
  87.     if (unlikely(!page)) {  
  88.         if ((flags & FOLL_DUMP) ||  
  89.             !is_zero_pfn(pte_pfn(pte)))  
  90.             goto bad_page;  
  91.         page = pte_page(pte);  
  92.     }  
  93.   
  94.     if (flags & FOLL_GET)  
  95.         get_page(page);  
  96.     if (flags & FOLL_TOUCH) {  
  97.         if ((flags & FOLL_WRITE) &&  
  98.             !pte_dirty(pte) && !PageDirty(page))  
  99.             set_page_dirty(page);  
  100.         /* 
  101.          * pte_mkyoung() would be more correct here, but atomic care 
  102.          * is needed to avoid losing the dirty bit: it is easier to use 
  103.          * mark_page_accessed(). 
  104.          */  
  105.         mark_page_accessed(page);  
  106.     }  
  107.     if ((flags & FOLL_MLOCK) && (vma->vm_flags & VM_LOCKED)) {  
  108.         /* 
  109.          * The preliminary mapping check is mainly to avoid the 
  110.          * pointless overhead of lock_page on the ZERO_PAGE 
  111.          * which might bounce very badly if there is contention. 
  112.          * 
  113.          * If the page is already locked, we don't need to 
  114.          * handle it now - vmscan will handle it later if and 
  115.          * when it attempts to reclaim the page. 
  116.          */  
  117.         if (page->mapping && trylock_page(page)) {  
  118.             lru_add_drain();  /* push cached pages to LRU */  
  119.             /* 
  120.              * Because we lock page here and migration is 
  121.              * blocked by the pte's page reference, we need 
  122.              * only check for file-cache page truncation. 
  123.              */  
  124.             if (page->mapping)  
  125.                 mlock_vma_page(page);  
  126.             unlock_page(page);  
  127.         }  
  128.     }  
  129. unlock:  
  130.     pte_unmap_unlock(ptep, ptl);  
  131. out:  
  132.     return page;  
  133.   
  134. bad_page:  
  135.     pte_unmap_unlock(ptep, ptl);  
  136.     return ERR_PTR(-EFAULT);  
  137.   
  138. no_page:  
  139.     pte_unmap_unlock(ptep, ptl);  
  140.     if (!pte_none(pte))  
  141.         return page;  
  142.   
  143. no_page_table:  
  144.     /* 
  145.      * When core dumping an enormous anonymous area that nobody 
  146.      * has touched so far, we don't want to allocate unnecessary pages or 
  147.      * page tables.  Return error instead of NULL to skip handle_mm_fault, 
  148.      * then get_dump_page() will return NULL to leave a hole in the dump. 
  149.      * But we can only make this optimization where a hole would surely 
  150.      * be zero-filled if handle_mm_fault() actually did handle it. 
  151.      */  
  152.     if ((flags & FOLL_DUMP) &&  
  153.         (!vma->vm_ops || !vma->vm_ops->fault))  
  154.         return ERR_PTR(-EFAULT);  
  155.     return page;  
  156. }  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM