日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

《LINUX3.0内核源代码分析》第一章:内存寻址

發布時間:2023/11/27 生活经验 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《LINUX3.0内核源代码分析》第一章:内存寻址 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

https://blog.csdn.net/ekenlinbing/article/details/7613334
摘要:本章主要介紹了LINUX3.0內存尋址方面的內容,重點對follow_page函數進行注釋,以幫助讀者大致了解ARM A9的頁表組織。 讀者需要理解一些基本概念:虛擬地址、物理地址、MPU、MMU、ARM中的二級頁表、cache、TLB。

法律聲明:《LINUX3.0內核源代碼分析》系列文章由謝寶友(scxby@163.com)發表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代碼遵循GPL協議。除此以外,文檔中的其他內容由作者保留所有版權。謝絕轉載。

本連載文章并不是為了形成一本適合出版的書籍,而是為了向有一定內核基本的讀者提供一些linux3.0源碼分析。因此,請讀者結合《深入理解LINUX內核》第三版閱讀本連載。

本系列文章分析ARM A9的linux3.0代碼實現。因此,需要讀者有一定的ARM體系硬件知識。推薦閱讀《ARM嵌入式系統開發-軟件設計與優化》。另外,讀者最好對內核有所了解,推薦閱讀《深入理解LINUX內核》第三版。

讀者需要理解一些基本概念:虛擬地址、物理地址、MPU、MMU、ARM中的二級頁表、cache、TLB。

1.1 基本函數
Linux3.0將分頁抽象為四級:

名稱數據結構備注
頁全局目錄Pgd_t
頁上級目錄Pud_tA9未用
頁中間目錄Pmd_tA9未用
頁表Pte_t
/*** 對A9來說,只支持4K大小的頁,因此PAGE_SHIFT定義為12.它表示一個虛擬地址的頁內偏移量的位數。* 根據它計算出來的頁大小PAGE_SIZE為4K,PAGE_MASK為0xffff000。*/#define PAGE_SHIFT           12#define PAGE_SIZE              (_AC(1,UL) << PAGE_SHIFT)#define PAGE_MASK           (~(PAGE_SIZE-1))/*** 對A9來說,沒有PMD和PUD,因此,PMD_SHIFT和PUD_SHIFT的值與PGDIR_SHIFT是一樣的,都是21.* 21表示一個頁全局目錄項代表了2^20即1M的地址空間。*/#define PMD_SHIFT            21#define PGDIR_SHIFT                  21/*** 分別代表一個頁表、頁中間目錄、頁全局目錄表中表項的個數。*/#define PTRS_PER_PTE               512#define PTRS_PER_PMD             1#define PTRS_PER_PGD              2048/*** 將pte\pmd\pud\pgd\pgprot轉換為整型值*/#define pte_val(x)      (x)#define pmd_val(x)      (x)#define pgd_val(x)      ((x)[0])#define pgprot_val(x)   (x)/*** 將整型值轉換為pte\pmd\pud\pgd\pgprot*/#define __pte(x)        (x)#define __pmd(x)        (x)#define __pgprot(x)     (x)

1.1.1 判斷頁表項標志的函數

/*** 頁表項是否為0*/#define pte_none(pte)                 (!pte_val(pte))/*** 頁表項是否可用。當頁在內存中但是不可讀寫時置此標志。典型的用途是寫時復制。*/#define pte_present(pte)  (pte_val(pte) & L_PTE_PRESENT)/*** 頁表項是否有可寫標志*/#define pte_write(pte)                (!(pte_val(pte) & L_PTE_RDONLY))/*** 頁表項是否為臟*/#define pte_dirty(pte)                 (pte_val(pte) & L_PTE_DIRTY)/*** 頁表項是否表示最近沒有被訪問過*/#define pte_young(pte)               (pte_val(pte) & L_PTE_YOUNG)/*** 頁表項是否有可執行標志*/#define pte_exec(pte)                 (!(pte_val(pte) & L_PTE_XN))#define pte_special(pte)    (0)/*** 清除頁表項的值。*/#define pte_clear(mm,addr,ptep)     set_pte_ext(ptep, __pte(0), 0)/*** 向一個頁表項中寫入指定的值。*/#define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)/*** 判斷兩個頁表項是否指向相同的頁并且有相同的訪問權限*/static inline int pte_same(pte_t pte_a, pte_t pte_b){return pte_val(pte_a) == pte_val(pte_b);}/*** 檢查頁中間目錄項是否指向不可用的頁表。*/#define pmd_bad(pmd)               (pmd_val(pmd) & 2)/*** 頁表項是否可用。當頁在內存中但是不可讀寫時置此標志。典型的用途是寫時復制。*/#define pte_present(pte)  (pte_val(pte) & L_PTE_PRESENT)

1.1.2 頁表項操作函數

/*** 虛擬地址在頁全局目錄中索引*/#define pgd_index(addr)             ((addr) >> PGDIR_SHIFT)/*** 計算一個進程用戶態地址對應的頁全局目錄項地址。* 計算內核態地址的頁全局目錄項地址應當使用pgd_offset_k*/#define pgd_offset(mm, addr)  ((mm)->pgd + pgd_index(addr))/* to find an entry in a kernel page-table-directory *//*** 計算一個內核態地址的頁全局目錄項地址。*/#define pgd_offset_k(addr)        pgd_offset(&init_mm, addr)/*** 獲得頁全局目錄項所指向的頁面。對A9來說,就是pmd_page*/#define pgd_page(pgd)                                  (pud_page((pud_t){ pgd }))/*** 獲得頁全局目錄項的虛擬地址。*/#define pgd_page_vaddr(pgd)                     (pud_page_vaddr((pud_t){ pgd }))/*** 在頁全局目錄表中,查找一個虛擬地址對應的頁上級目錄位置。* 對二級頁表來說,頁上級目錄就是頁全局目錄,因此直接返回頁全局目錄。*/#define pud_offset(pgd, start)           (pgd)/*** 獲得頁上級目錄頁面。*/#define pud_page(pud)                         pgd_page(pud)/*** 獲得頁上級目錄頁面的虛擬地址。*/#define pud_page_vaddr(pud)            pgd_page_vaddr(pud)/*** 獲得一個虛擬地址的頁中間目錄中的地址。對二級頁表來說,沒有pmd,直接返回頁全局目錄地址即可。*/#define pmd_offset(dir, addr)    ((pmd_t *)(dir))/*** 獲得頁中間目錄指向的頁表頁面。*/#define pmd_page(pmd)             pfn_to_page(__phys_to_pfn(pmd_val(pmd)))/*** 獲得一個線性地址對應的頁表項在頁表中的索引*/#define pte_index(addr)              (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))/*** 在主內核頁表中定位內核地址對應的頁表項的虛擬地址。*/#define pte_offset_kernel(pmd,addr)        (pmd_page_vaddr(*(pmd)) + pte_index(addr))/*** 在進程頁表中定位線性地址對應的頁表項的地址。如果頁表保存在高端內存中,那么還為頁表建立一個臨時內核映射。*/#define pte_offset_map(pmd,addr)  (__pte_map(pmd) + pte_index(addr))/*** 如果頁表在高端內存中,不解除由pte_offset_map建立的臨時內核映射。*/#define pte_unmap(pte)                      __pte_unmap(pte)/*** 獲取頁表項中的頁幀號。*/#define pte_pfn(pte)           (pte_val(pte) >> PAGE_SHIFT)/*** 根據頁幀號和頁面屬性,合成頁表項。*/#define pfn_pte(pfn,prot)  __pte(__pfn_to_phys(pfn) | pgprot_val(prot))/*** 從頁表項中提取頁幀號,并定位該頁幀號對應的頁框。*/#define pte_page(pte)                 pfn_to_page(pte_pfn(pte))/*** 根據頁框和頁面屬性,合成頁表項。*/#define mk_pte(page,prot)        pfn_pte(page_to_pfn(page), prot)/*** 當頁表項映射到文件,并且沒有裝載進內存時,從頁表項中提取文件頁號。*/#define pte_to_pgoff(x)              (pte_val(x) >> 3)/*** 將頁面映射的頁號存放到頁表項中*/#define pgoff_to_pte(x)              __pte(((x) << 3) | L_PTE_FILE)

1.1.3 頁表分配相關的函數

/*** 為頁全局目錄分配內存*/pgd_t *pgd_alloc(struct mm_struct *mm)/*** 釋放頁全局目錄項*/void pgd_free(struct mm_struct *mm, pgd_t *pgd_base)/*** 分配頁上級目錄,在二級頁表中,此函數什么也不做。*/#define pud_alloc(mm, pgd, address)        (pgd)/*** 釋放頁上級目錄,在二級頁表中,這個函數什么也不做*/#define pud_free(mm, x)                               do { } while (0)Pmd_alloc、pmd_free、pte_alloc_map、pte_free等宏或函數與此類似。

1.2 刷新cache和TLB
Cache是CPU與內存之間的緩存,而TLB是CPU與MMU之間緩存。

當外部硬件通過DMA修改了內存中的數據時,需要使cache中的數據失效,強制CPU從內存中裝載數據。當CPU向緩存中寫入數據后,為了通過DMA將數據傳送到外部硬件,則需要將緩存中的數據強制寫入內存。

當頁表項映射的頁面發生變化后,也需要將頁面緩存的內容寫入內存。

同理,當修改了頁表項后,為了避免TLB中緩存的項進行錯誤的MMU轉換,也需要使TLB中緩存的項失效。

1.3 follow_page函數
follow_page函數是從進程的頁表中搜索特定地址對應的頁面對象。這個函數對于理解LINUX內核頁表管理有幫助。

struct page *follow_page(struct vm_area_struct *vma, unsigned long address,unsigned int flags){pgd_t *pgd;pud_t *pud;pmd_t *pmd;pte_t *ptep, pte;spinlock_t *ptl;struct page *page;struct mm_struct *mm = vma->vm_mm;/*** 對ARM A9來說,沒有配置巨頁功能,follow_huge_addr實際上是空處理。*/page = follow_huge_addr(mm, address, flags & FOLL_WRITE);if (!IS_ERR(page)) {BUG_ON(flags & FOLL_GET);goto out;}page = NULL;/*** 在一級目錄項中,查找地址對應的一級目錄索引項。*/pgd = pgd_offset(mm, address);/*** 該地址對應的一級目錄項無效。對ARM來說,pgd_none總返回0,真正的判斷是在pmd_none。*/if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))goto no_page_table;/*** 查找地址對應的頁上級目錄項。這對4級目錄的分組體系來說才有效。ARM不存在頁上級目錄和頁中間目錄。* pud總是返回pgd。*/pud = pud_offset(pgd, address);/*** pud_none總是返回0,因此下面的判斷是無用。真正有用的判斷在后面的pmd_none*/if (pud_none(*pud))goto no_page_table;if (pud_huge(*pud) && vma->vm_flags & VM_HUGETLB) {BUG_ON(flags & FOLL_GET);page = follow_huge_pud(mm, address, pud, flags & FOLL_WRITE);goto out;}if (unlikely(pud_bad(*pud)))goto no_page_table;/*** 取頁中間目錄,對ARM來說,pmd直接返回pud,即pgd。*/pmd = pmd_offset(pud, address);/*** 判斷pmd是否為0,即ARM一級目錄是否有效。對pgd,pud的判斷都是無用的,真正的判斷在這里。*/if (pmd_none(*pmd))goto no_page_table;/*** 判斷pmd是否是一個巨頁,以及用戶虛擬地址空間段是否是一個巨頁段,略過。*/if (pmd_huge(*pmd) && vma->vm_flags & VM_HUGETLB) {BUG_ON(flags & FOLL_GET);/*** 查找巨頁地址映射的物理頁面。*/page = follow_huge_pmd(mm, address, pmd, flags & FOLL_WRITE);goto out;}/*** 透明巨頁處理,對某些體系結構,如mips來說,這個功能是有效的。但是雖然ARM硬件支持巨頁(1M頁)* 目前的內核還不支持ARM巨頁,略過。*/if (pmd_trans_huge(*pmd)) {if (flags & FOLL_SPLIT) {split_huge_page_pmd(mm, pmd);goto split_fallthrough;}spin_lock(&mm->page_table_lock);if (likely(pmd_trans_huge(*pmd))) {if (unlikely(pmd_trans_splitting(*pmd))) {spin_unlock(&mm->page_table_lock);wait_split_huge_page(vma->anon_vma, pmd);} else {page = follow_trans_huge_pmd(mm, address,pmd, flags);spin_unlock(&mm->page_table_lock);goto out;}} elsespin_unlock(&mm->page_table_lock);/* fall through */}split_fallthrough:/*** 判斷pmd是否有效。*/if (unlikely(pmd_bad(*pmd)))goto no_page_table;/*** 在二級頁表中找到地址對應的pte。并將pte指針返回。* 注意,這里獲取了進程的內存頁表鎖。以防止內核其他路徑修改進程頁表,使得ptep指向的pte產生變化。* ptl是內存頁表鎖。* 如果內核支持將pte表放到高端內存,那么還需要調用kmap_atomic將頁表到內核地址空間中。*/ptep = pte_offset_map_lock(mm, pmd, address, &ptl);pte = *ptep;/*** 這里判斷頁表項是否有效。* 有時,頁面在內存中,但是不允許訪問。比如寫時復制。* 當頁完全不在內存中時,頁表項也沒有效。*/if (!pte_present(pte))goto no_page;/*** 希望搜索一個可寫的頁面,但是頁表項沒有寫權限。*/if ((flags & FOLL_WRITE) && !pte_write(pte))goto unlock;/*** 根據pte中保存的頁幀號,找到該頁幀號對應的page結構。*/page = vm_normal_page(vma, address, pte);if (unlikely(!page)) {/* 根據頁幀號無法找到page結構,可能是一些特殊情況。如驅動自行管理的pte出了問題。 */if ((flags & FOLL_DUMP) || /* 不允許返回0頁 */!is_zero_pfn(pte_pfn(pte))) /* 不是0頁 */goto bad_page;page = pte_page(pte);/* 向上層返回0頁 */}/*** 調用者要求獲取頁面引用,則增加頁面引用計數。*/if (flags & FOLL_GET)get_page(page);if (flags & FOLL_TOUCH) {/* 調用者希望設置訪問標志,可能是隨后會寫頁面 */if ((flags & FOLL_WRITE) &&/* 獲取寫引用 */!pte_dirty(pte) && !PageDirty(page))/* 頁面和pte的臟標志都還沒有設置,則強制設置臟標志 */set_page_dirty(page);/** pte_mkyoung() would be more correct here, but atomic care* is needed to avoid losing the dirty bit: it is easier to use* mark_page_accessed().*//*** 標記頁面訪問標志。*/mark_page_accessed(page);}/*** 調用者想將頁面鎖在內存中。*/if ((flags & FOLL_MLOCK) && (vma->vm_flags & VM_LOCKED)) {/** The preliminary mapping check is mainly to avoid the* pointless overhead of lock_page on the ZERO_PAGE* which might bounce very badly if there is contention.** If the page is already locked, we don't need to* handle it now - vmscan will handle it later if and* when it attempts to reclaim the page.*/if (page->mapping && trylock_page(page)) {/* 鎖住頁面,不交換到外部存儲器中 */lru_add_drain();  /* push cached pages to LRU *//** Because we lock page here and migration is* blocked by the pte's page reference, we need* only check for file-cache page truncation.*/if (page->mapping)mlock_vma_page(page);unlock_page(page);}}unlock:/*** 釋放進程頁面鎖,同時,如果支持將頁表放到高端內存,就解除對頁表的映射。*/pte_unmap_unlock(ptep, ptl);out:return page;bad_page:pte_unmap_unlock(ptep, ptl);return ERR_PTR(-EFAULT);no_page:pte_unmap_unlock(ptep, ptl);if (!pte_none(pte))return page;no_page_table:/** When core dumping an enormous anonymous area that nobody* has touched so far, we don't want to allocate unnecessary pages or* page tables.  Return error instead of NULL to skip handle_mm_fault,* then get_dump_page() will return NULL to leave a hole in the dump.* But we can only make this optimization where a hole would surely* be zero-filled if handle_mm_fault() actually did handle it.*/if ((flags & FOLL_DUMP) &&(!vma->vm_ops || !vma->vm_ops->fault))return ERR_PTR(-EFAULT);return page;

總結

以上是生活随笔為你收集整理的《LINUX3.0内核源代码分析》第一章:内存寻址的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。