電子產(chǎn)業(yè)一站式賦能平臺(tái)

PCB聯(lián)盟網(wǎng)

搜索
查看: 162|回復(fù): 0
收起左側(cè)

深入理解Linux內(nèi)核頁(yè)表映射分頁(yè)機(jī)制

[復(fù)制鏈接]

502

主題

502

帖子

3383

積分

四級(jí)會(huì)員

Rank: 4

積分
3383
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-10-11 11:38:00 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
前言操作系統(tǒng)的核心任務(wù)是對(duì)系統(tǒng)資源的管理,而重中之重就是對(duì)CPU和內(nèi)存的管理。為了使進(jìn)程擺脫系統(tǒng)內(nèi)存的制約,用戶進(jìn)程運(yùn)行在虛擬內(nèi)存之上,每個(gè)用戶進(jìn)程都擁有完整的虛擬地址空間,互不干涉。而實(shí)現(xiàn)虛擬內(nèi)存的關(guān)鍵就在于建立虛擬地址與物理地址之間的映射,因?yàn)闊o(wú)論如何數(shù)據(jù)終究要存儲(chǔ)到物理內(nèi)存中才能被記錄下來(lái)。

如上圖所示,進(jìn)程1和進(jìn)程2都擁有完整的虛擬地址空間,虛擬地址空間又分為了用戶空間內(nèi)核空間,對(duì)于不同的進(jìn)程面對(duì)的都是同一個(gè)內(nèi)核,其內(nèi)核空間的地址對(duì)于的物理地址都是一樣的,因而進(jìn)程1和進(jìn)程2中內(nèi)核空間的VA K地址都映射到了物理內(nèi)存的PA K地址。
一、分頁(yè)機(jī)制1.分頁(yè)機(jī)制的核心思想就是解除線性地址和物理地址的一一對(duì)應(yīng)關(guān)系,使線性地址連續(xù),而物理地址不連續(xù)。使連續(xù)的線性地址可以與任意物理內(nèi)存地址相關(guān)聯(lián),即從虛擬頁(yè)面到物理頁(yè)面的映射。而這個(gè)翻譯過(guò)程由內(nèi)存管理單元(MMU)完成,MMU接收CPU發(fā)出的虛擬地址,將其翻譯為物理地址后發(fā)送給內(nèi)存。內(nèi)存管理單元按照該物理地址進(jìn)行相應(yīng)訪問(wèn)后讀出或?qū)懭胂嚓P(guān)數(shù)據(jù),如下圖所示:

2.分頁(yè)機(jī)制尋址原理通過(guò)查頁(yè)表,對(duì)于每個(gè)程序,內(nèi)存管理單元MMU都為其保存一個(gè)頁(yè)表,該頁(yè)表中存放的是虛擬頁(yè)面到物理頁(yè)面的映射。每當(dāng)為一個(gè)虛擬頁(yè)面尋找到一個(gè)物理頁(yè)面之后,就在頁(yè)表里增加一條記錄來(lái)保留該映射關(guān)系。當(dāng)然,隨著虛擬頁(yè)面進(jìn)出物理內(nèi)存,頁(yè)表的內(nèi)容也會(huì)不斷更新變化。

二級(jí)頁(yè)表模式:
(1)取虛擬地址高10位*4+頁(yè)目錄表(PDE)得到頁(yè)表的物理地址
(2)取虛擬地址中間10位*4+頁(yè)表(PTE)得到頁(yè)地址
(3)取虛擬地址最后12位+頁(yè)地址得到最終的物理地址頁(yè)目錄項(xiàng)、頁(yè)表項(xiàng)結(jié)構(gòu):



3.啟動(dòng)分頁(yè)機(jī)制,按順序做好三件事(1)準(zhǔn)備好頁(yè)目錄表及頁(yè)表
(2)將頁(yè)表地址寫入控制寄存器cr3
(3)寄存器cr0的PG位置1,開啟頁(yè)表機(jī)制
二、x86架構(gòu)下頁(yè)表分頁(yè)機(jī)制源碼分析1.頁(yè)表結(jié)構(gòu)源碼:arch/x86/include/asm/pgtable_type.h
這段代碼是 Linux 內(nèi)核中用于定義 x86 架構(gòu)下頁(yè)表相關(guān)的宏和數(shù)據(jù)結(jié)構(gòu)的頭文件,它定義了頁(yè)表項(xiàng)的各個(gè)標(biāo)志位、頁(yè)表項(xiàng)的格式以及一些輔助函數(shù)和宏。
頁(yè)表項(xiàng)標(biāo)志位定義
#define _PAGE_BIT_PRESENT       /* 表示頁(yè)面是否存在 */
#define _PAGE_BIT_RW            /* 表示頁(yè)面是否可寫 */
#define _PAGE_BIT_USER          /* 示頁(yè)面是否用戶可訪問(wèn) */頁(yè)表項(xiàng)的組合
#define _PAGE_SHARED
#define _PAGE_KERNEL
#define _PAGE_KERNEL_EXEC頁(yè)表項(xiàng)的類型定義
typedef struct{unsigned long pgd;}pgd_t;/* 頂級(jí)頁(yè)表目錄 */
typedef struct{unsigned long pud;}pud_t;/* 高層頁(yè)表目錄 */
typedef struct{unsigned long pmd;}pmd_t;/* 中層頁(yè)表目錄 */
typedef struct{unsigned long pte;}pte_t;/* 最底層頁(yè)表?xiàng)l目 */2.對(duì)頁(yè)表的基本操作源碼:arch/x86/mm/pgtable/pgtable.c
這段代碼包含了頁(yè)表分配、釋放、設(shè)置訪問(wèn)標(biāo)志、清除訪問(wèn)標(biāo)志等功能。這些操作是內(nèi)存管理的基本操作,通過(guò)這些操作,內(nèi)核可以高效地管理虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。
頁(yè)表分配和釋放
定義了頁(yè)表分配和釋放的函數(shù)。頁(yè)表分配是指為進(jìn)程或內(nèi)核分配新的頁(yè)表項(xiàng)。這通常在進(jìn)程創(chuàng)建或需要新的虛擬內(nèi)存區(qū)域時(shí)進(jìn)行。分配頁(yè)表項(xiàng)時(shí),內(nèi)核會(huì)從物理內(nèi)存中分配一塊內(nèi)存,并將其初始化為頁(yè)表項(xiàng)。
   pgd_t *pgd_alloc(struct mm_struct *mm)
   {
       pgd_t *pgd;
       pmd_t *u_pmds[MAX_PREALLOCATED_USER_PMDS];
       pmd_t *pmds[MAX_PREALLOCATED_PMDS];
//PGD 分配:  
       pgd = _pgd_alloc();//調(diào)用 _pgd_alloc() 函數(shù)分配一個(gè)新的 PGD。如果分配失敗,跳轉(zhuǎn)到 out 標(biāo)簽.
       if (pgd == NULL)
           goto out;
//設(shè)置 PGD:
       mm->pgd = pgd;//將分配的 PGD 賦值給當(dāng)前進(jìn)程的內(nèi)存管理結(jié)構(gòu)。
//預(yù)分配 PMD:
       if (sizeof(pmds) != 0 &&
               preallocate_pmds(mm, pmds, PREALLOCATED_PMDS) != 0)
           goto out_free_pgd;//如果 pmds 數(shù)組的大小不為 0,調(diào)用 preallocate_pmds() 函數(shù)為當(dāng)前進(jìn)程預(yù)分配 PMD(頁(yè)中間目錄)。如果失敗,跳轉(zhuǎn)到 out_free_pgd 標(biāo)簽。
//預(yù)分配用戶 PMD:
       if (sizeof(u_pmds) != 0 &&
               preallocate_pmds(mm, u_pmds, PREALLOCATED_USER_PMDS) != 0)
           goto out_free_pmds;
//并行虛擬化 PGD 分配:
       if (paravirt_pgd_alloc(mm) != 0)
           goto out_free_user_pmds;//調(diào)用 paravirt_pgd_alloc() 進(jìn)行并行虛擬化的 PGD 分配。
// 加鎖:
       spin_lock(&pgd_lock);//獲取 PGD 鎖以保護(hù)后續(xù)操作。
//PGD 構(gòu)造:
       pgd_ctor(mm, pgd);//調(diào)用 pgd_ctor() 初始化 PGD。
//預(yù)填充 PMD:
       if (sizeof(pmds) != 0)
           pgd_prepopulate_pmd(mm, pgd, pmds);//如果 pmds 不為空,調(diào)用 pgd_prepopulate_pmd() 預(yù)填充 PMD。
//預(yù)填充用戶 PMD:
       if (sizeof(u_pmds) != 0)
           pgd_prepopulate_user_pmd(mm, pgd, u_pmds);
//解鎖:
       spin_unlock(&pgd_lock);//釋放 PGD 鎖。
//返回 PGD:
       return pgd;
   out_free_user_pmds:
       if (sizeof(u_pmds) != 0)
           free_pmds(mm, u_pmds, PREALLOCATED_USER_PMDS);
   out_free_pmds:
       if (sizeof(pmds) != 0)
           free_pmds(mm, pmds, PREALLOCATED_PMDS);
   out_free_pgd:
       _pgd_free(pgd);
   out:
       return NULL;
   }
   void pgd_free(struct mm_struct *mm, pgd_t *pgd)
   {
       pgd_mop_up_pmds(mm, pgd);
       pgd_dtor(pgd);//調(diào)用 pgd_dtor() 進(jìn)行 PGD 的析構(gòu)。
       paravirt_pgd_free(mm, pgd);//調(diào)用 paravirt_pgd_free() 釋放并行虛擬化的 PGD。
       _pgd_free(pgd);//調(diào)用 _pgd_free() 釋放 PGD。
   }頁(yè)表級(jí)別的釋放
頁(yè)表級(jí)別的釋放函數(shù)用于釋放不同級(jí)別的頁(yè)表項(xiàng),定義了頁(yè)表級(jí)別的釋放函數(shù),如 pmd_free_tlb、pud_free_tlb 等。這些函數(shù)確保了內(nèi)存的高效利用和釋放。
   #if CONFIG_PGTABLE_LEVELS > 2
   void ___pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd)
   {
       struct ptdesc *ptdesc = virt_to_ptdesc(pmd);//獲取 PMD 描述符,使用 virt_to_ptdesc 函數(shù)將 PMD 轉(zhuǎn)換為相應(yīng)的頁(yè)表描述符.
       paravirt_release_pmd(__pa(pmd) >> PAGE_SHIFT);// 釋放 PMD,用 paravirt_release_pmd 函數(shù)釋放 PMD。__pa(pmd) 將虛擬地址轉(zhuǎn)換為物理地址,>> PAGE_SHIFT 用于獲取頁(yè)框號(hào)。
       #ifdef CONFIG_X86_PAE
       tlb->need_flush_all = 1;
       #endif//如果啟用了 PAE,設(shè)置 tlb->need_flush_all 為 1,表示需要刷新所有 TLB 條目。
       pagetable_pmd_dtor(ptdesc);//調(diào)用 pagetable_pmd_dtor 函數(shù)對(duì) PMD 進(jìn)行析構(gòu),釋放與 PMD 相關(guān)的資源。
       paravirt_tlb_remove_table(tlb, ptdesc_page(ptdesc));//調(diào)用 paravirt_tlb_remove_table 函數(shù)從 TLB 中移除與 PMD 相關(guān)的頁(yè)表?xiàng)l目。
   }設(shè)置和清除訪問(wèn)標(biāo)志
定義了設(shè)置和清除頁(yè)表項(xiàng)訪問(wèn)標(biāo)志的函數(shù),設(shè)置訪問(wèn)標(biāo)志是指修改頁(yè)表項(xiàng)中的標(biāo)志位,以控制頁(yè)面的訪問(wèn)權(quán)限。例如,可以設(shè)置頁(yè)面為可讀、可寫或可執(zhí)行。設(shè)置訪問(wèn)標(biāo)志時(shí),內(nèi)核會(huì)更新頁(yè)表項(xiàng)中的相應(yīng)位。設(shè)置和清除頁(yè)表項(xiàng)訪問(wèn)標(biāo)志的函數(shù)用于管理頁(yè)表項(xiàng)的訪問(wèn)權(quán)限,這些函數(shù)確保了內(nèi)存的安全性和訪問(wèn)控制。



   int ptep_set_access_flags(struct vm_area_struct *vma,
                             unsigned long address, pte_t *ptep,
                             pte_t entry, int dirty)
   {
       int changed = !pte_same(*ptep, entry);//使用 pte_same 函數(shù)檢查當(dāng)前頁(yè)表項(xiàng)與新條目是否相同。如果不同,設(shè)置 changed 為 1。
       if (changed && dirty)
           set_pte(ptep, entry);//更新頁(yè)表項(xiàng),如果頁(yè)表項(xiàng)已更改且 dirty 為真,調(diào)用 set_pte 函數(shù)更新頁(yè)表項(xiàng)。
       return changed;//返回 changed,指示頁(yè)表項(xiàng)是否已更改。
   }
   int ptep_test_and_clear_young(struct vm_area_struct *vma,
                                 unsigned long addr, pte_t *ptep)
   {
       int ret = 0;
       if (pte_young(*ptep))
           ret = test_and_clear_bit(_PAGE_BIT_ACCESSED,
                                    (unsigned long *) &ptep->pte);//檢查頁(yè)表項(xiàng)的年輕標(biāo)志,使用 pte_young 函數(shù)檢查頁(yè)表項(xiàng)是否被標(biāo)記為“年輕”。如果是,調(diào)用 test_and_clear_bit 函數(shù)清除訪問(wèn)標(biāo)志,并將結(jié)果存儲(chǔ)在 ret 中。
       return ret;//返回 ret,指示是否成功清除年輕標(biāo)志。
   }3.x86 架構(gòu)相關(guān)的兩級(jí)頁(yè)表操作源碼:arch/x86/include/asm/pgtable-2level.h
這段代碼主要定義了與 x86 架構(gòu)相關(guān)的兩級(jí)頁(yè)表操作,包括設(shè)置和清除 PTE、PMD 和 PUD 的函數(shù),以及交換條目和 PTE 的編碼/解碼宏和函數(shù)。通過(guò)這些定義,內(nèi)核可以正確地管理和操作兩級(jí)頁(yè)表。
頭文件保護(hù)
使用頭文件保護(hù)機(jī)制,防止重復(fù)包含。
   #ifndef _ASM_X86_PGTABLE_2LEVEL_H
   #define _ASM_X86_PGTABLE_2LEVEL_H錯(cuò)誤打印宏
定義了用于打印 PTE 和 PGD 錯(cuò)誤的宏。
   #define pte_ERROR(e) \
    pr_err("%s:%d: bad pte %08lx
", __FILE__, __LINE__, (e).pte_low)
   #define pgd_ERROR(e) \
    pr_err("%s:%d: bad pgd %08lx
", __FILE__, __LINE__, pgd_val(e))設(shè)置 PTE、PMD 和 PUD 的函數(shù)
   static inline void native_set_pte(pte_t *ptep , pte_t pte)
   {
           *ptep = pte;
   }//將傳入的 pte 值賦給指向的頁(yè)表項(xiàng) *ptep。
   static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
   {
           *pmdp = pmd;
   }//將傳入的 pmd 值賦給指向的頁(yè)中間目錄 *pmdp。
   static inline void native_set_pud(pud_t *pudp, pud_t pud)
   {
   }//該函數(shù)的實(shí)現(xiàn)為空,表示當(dāng)前沒(méi)有對(duì) PUD 的設(shè)置操作。原子設(shè)置 PTE 的函數(shù)
   static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)
   {
           native_set_pte(ptep, pte);//調(diào)用 native_set_pte 來(lái)設(shè)置頁(yè)表項(xiàng)。
   }清除 PMD 和 PUD 的函數(shù)
   static inline void native_pmd_clear(pmd_t *pmdp)
   {
           native_set_pmd(pmdp, __pmd(0));//調(diào)用 native_set_pmd,將 PMD 設(shè)置為 __pmd(0),將 PMD 清空或設(shè)置為無(wú)效狀態(tài)。__pmd(0) 通常表示一個(gè)無(wú)效的 PMD 條目。
   }
   static inline void native_pud_clear(pud_t *pudp)
   {
   }清除 PTE 的函數(shù)
   static inline void native_pte_clear(struct mm_struct *mm,
                                       unsigned long addr, pte_t *xp)
   {
           *xp = native_make_pte(0);//調(diào)用 native_make_pte(0),將頁(yè)表項(xiàng) *xp 設(shè)置為無(wú)效狀態(tài)。native_make_pte(0) 通常會(huì)返回一個(gè)表示無(wú)效頁(yè)表項(xiàng)的 PTE 值。
   }獲取并清除 PTE、PMD 和 PUD 的函數(shù)
   #ifdef CONFIG_SMP
   static inline pte_t native_ptep_get_and_clear(pte_t *xp)
   {
           return __pte(xchg(&xp->pte_low, 0));
   }//如果啟用了 SMP,函數(shù)使用 xchg 原子操作將 xp->pte_low 的值交換為 0,并返回原來(lái)的 PTE 值。__pte 用于將原始值轉(zhuǎn)換為 PTE 類型。
   #else
   #define native_ptep_get_and_clear(xp) native_local_ptep_get_and_clear(xp)
   #endif//如果未啟用 SMP,使用宏定義將調(diào)用重定向到 native_local_ptep_get_and_clear
   #ifdef CONFIG_SMP
   static inline pmd_t native_pmdp_get_and_clear(pmd_t *xp)
   {
           return __pmd(xchg((pmdval_t *)xp, 0));
   }//如果啟用了 SMP,函數(shù)使用 xchg 原子操作將 xp 的值交換為 0,并返回原來(lái)的 PMD 值。__pmd 用于將原始值轉(zhuǎn)換為 PMD 類型。
   #else
   #define native_pmdp_get_and_clear(xp) native_local_pmdp_get_and_clear(xp)
   #endif//如果未啟用 SMP,使用宏定義將調(diào)用重定向到 native_local_pmdp_get_and_clear。
   #ifdef CONFIG_SMP
   static inline pud_t native_pudp_get_and_clear(pud_t *xp)
   {
           return __pud(xchg((pudval_t *)xp, 0));
   }//如果啟用了 SMP,函數(shù)使用 xchg 原子操作將 xp 的值交換為 0,并返回原來(lái)的 PUD 值。__pud 用于將原始值轉(zhuǎn)換為 PUD 類型。
   #else
   #define native_pudp_get_and_clear(xp) native_local_pudp_get_and_clear(xp)
   #endif//如果未啟用 SMP,使用宏定義將調(diào)用重定向到 native_local_pudp_get_and_clear。交換條目和 PTE 的編碼/解碼
定義了交換條目和 PTE 的編碼/解碼宏和函數(shù)。
   #define SWP_TYPE_BITS 5//義交換類型所需的位數(shù),這里是 5 位。
   #define _SWP_TYPE_MASK ((1U  5)//使用 BUILD_BUG_ON 宏在編譯時(shí)檢查 MAX_SWAPFILES_SHIFT 是否大于 5。如果是,則編譯失敗。這是為了確保交換文件的數(shù)量不會(huì)超過(guò)可以用位表示的最大值。
   #define __swp_type(x)(((x).val >> _SWP_TYPE_SHIFT)& _SWP_TYPE_MASK)//從交換條目 x 中提取交換類型。通過(guò)右移和掩碼操作獲取類型值。
   #define __swp_offset(x)((x).val >> SWP_OFFSET_SHIFT)//從交換條目 x 中提取交換偏移量。通過(guò)右移操作獲取偏移值。
   #define __swp_entry(type, offset)((swp_entry_t) { \
                                            (((type) & _SWP_TYPE_MASK) 交換 PTE 的獨(dú)占標(biāo)記
定義了用于存儲(chǔ)獨(dú)占標(biāo)記的交換 PTE 位。
    #define _PAGE_SWP_EXCLUSIVE        _PAGE_PSE無(wú)反轉(zhuǎn) PFN
定義了無(wú)反轉(zhuǎn) PFN 的函數(shù)。
    static inline u64 protnone_mask(u64 val)
    {
            return 0;
    }
    static inline u64 flip_protnone_guard(u64 oldval, u64 val, u64 mask)
    {
            return val;
    }
    static inline bool __pte_needs_invert(u64 val)
    {
            return false;
    }4.x86 架構(gòu)相關(guān)的三級(jí)頁(yè)表操作源碼:arch/x86/include/asm/pgtable-3level.h
這段代碼主要定義了與 x86 架構(gòu)相關(guān)的三級(jí)頁(yè)表操作,包括設(shè)置和清除 PTE、PMD 和 PUD 的函數(shù),以及交換條目和 PTE 的編碼/解碼宏和函數(shù)。通過(guò)這些定義,內(nèi)核可以正確地管理和操作三級(jí)頁(yè)表。
頭文件保護(hù)
使用頭文件保護(hù)機(jī)制,防止重復(fù)包含。
   #ifndef _ASM_X86_PGTABLE_3LEVEL_H
   #define _ASM_X86_PGTABLE_3LEVEL_H錯(cuò)誤打印宏
定義了用于打印 PTE、PMD 和 PGD 錯(cuò)誤的宏。
   #define pte_ERROR(e)                                                       
           pr_err("%s:%d: bad pte %p(%08lx%08lx)
",                       
                  __FILE__, __LINE__, &(e), (e).pte_high, (e).pte_low)//當(dāng)檢測(cè)到無(wú)效的 PTE 時(shí),調(diào)用此宏以輸出詳細(xì)的錯(cuò)誤信息。
   #define pmd_ERROR(e)                                                       
           pr_err("%s:%d: bad pmd %p(%016Lx)
",                               
                  __FILE__, __LINE__, &(e), pmd_val(e))//當(dāng)檢測(cè)到無(wú)效的 PMD 時(shí),調(diào)用此宏以輸出詳細(xì)的錯(cuò)誤信息。
   #define pgd_ERROR(e)                                                       
           pr_err("%s:%d: bad pgd %p(%016Lx)
",                               
                  __FILE__, __LINE__, &(e), pgd_val(e))//當(dāng)檢測(cè)到無(wú)效的 PGD 時(shí),調(diào)用此宏以輸出詳細(xì)的錯(cuò)誤信息。交換宏
定義了用于交換 PTE、PMD 和 PUD 的宏。
   #define pxx_xchg64(_pxx, _ptr, _val) ({                                       
           _pxx##val_t *_p = (_pxx##val_t *)_ptr;//將 _ptr 轉(zhuǎn)換為指向特定類型的指針。                       
           _pxx##val_t _o = *_p;//讀取指針 _p 指向的當(dāng)前值,并存儲(chǔ)在 _o 中。                                               
           do { } while (!try_cmpxchg64(_p, &_o, (_val)));//使用 try_cmpxchg64 函數(shù)嘗試將 _val 設(shè)置為 _p 指向的值。如果當(dāng)前值與 _o 不同,交換操作將失敗,循環(huán)將繼續(xù)嘗試,直到成功為止。                       
           native_make_##_pxx(_o);//調(diào)用 native_make_##_pxx 函數(shù),將 _o 轉(zhuǎn)換為適當(dāng)?shù)念愋筒⒎祷亍?nbsp;                              
   })設(shè)置 PTE、PMD 和 PUD 的函數(shù)
定義了設(shè)置 PTE、PMD 和 PUD 的函數(shù)。
   static inline void native_set_pte(pte_t *ptep, pte_t pte)//native_set_pte 用于設(shè)置給定的頁(yè)表項(xiàng),pte_t *ptep: 指向要設(shè)置的頁(yè)表項(xiàng)的指針,pte_t pte: 要設(shè)置的頁(yè)表項(xiàng)值。
   {
           WRITE_ONCE(ptep->pte_high, pte.pte_high);//使用 WRITE_ONCE 宏將 pte_high 寫入 ptep 指向的頁(yè)表項(xiàng)的高位部分。
           smp_wmb();//調(diào)用 smp_wmb(),確保在多處理器環(huán)境中寫入的順序。
           WRITE_ONCE(ptep->pte_low, pte.pte_low);//使用 WRITE_ONCE 宏將 pte_low 寫入 ptep 指向的頁(yè)表項(xiàng)的低位部分。
   }
   static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)//以原子方式設(shè)置給定的頁(yè)表項(xiàng)
   {
           pxx_xchg64(pte, ptep, native_pte_val(pte));//調(diào)用 pxx_xchg64 宏,執(zhí)行原子交換操作,將 pte 的值設(shè)置到 ptep 指向的頁(yè)表項(xiàng)中。
   }
   static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)//用于設(shè)置給定的頁(yè)中間目錄
   {
           pxx_xchg64(pmd, pmdp, native_pmd_val(pmd));//將 pmd 的值設(shè)置到 pmdp 指向的頁(yè)中間目錄中。
   }
   static inline void native_set_pud(pud_t *pudp, pud_t pud)//設(shè)置給定的頁(yè)上級(jí)目錄
   {
   #ifdef CONFIG_PAGE_TABLE_ISOLATION
           pud.p4d.pgd = pti_set_user_pgtbl(&pudp->p4d.pgd, pud.p4d.pgd);//設(shè)置用戶頁(yè)表
   #endif
           pxx_xchg64(pud, pudp, native_pud_val(pud));//將 pud 的值設(shè)置到 pudp 指向的頁(yè)上級(jí)目錄中。
   }清除 PTE、PMD 和 PUD 的函數(shù)
定義了清除 PTE、PMD 和 PUD 的函數(shù)。
   static inline void native_pte_clear(struct mm_struct *mm, unsigned long addr,pte_t *ptep)//清除給定的頁(yè)表項(xiàng)(PTE),將其設(shè)置為無(wú)效狀態(tài)。
   {
           WRITE_ONCE(ptep->pte_low, 0);//將 pte_low 設(shè)置為 0,清除頁(yè)表項(xiàng)的低位部分。
           smp_wmb();//確保在多處理器環(huán)境中寫入的順序
           WRITE_ONCE(ptep->pte_high, 0);//將 pte_high 設(shè)置為 0,清除頁(yè)表項(xiàng)的高位部分。
   }
   static inline void native_pmd_clear(pmd_t *pmdp)//清除給定的頁(yè)中間目錄(PMD),將其設(shè)置為無(wú)效狀態(tài)。
   {
           WRITE_ONCE(pmdp->pmd_low, 0);//將 pmd_low 設(shè)置為 0,清除頁(yè)中間目錄的低位部分。
           smp_wmb();//確保在多處理器環(huán)境中寫入的順序。
           WRITE_ONCE(pmdp->pmd_high, 0);//將 pmd_high 設(shè)置為 0,清除頁(yè)中間目錄的高位部分。
   }
   static inline void native_pud_clear(pud_t *pudp)//清除給定的頁(yè)上級(jí)目錄(PUD)
   {
   }
   static inline void pud_clear(pud_t *pudp)//清除給定的頁(yè)上級(jí)目錄(PUD),將其設(shè)置為無(wú)效狀態(tài)。
   {
           set_pud(pudp, __pud(0));//將 pud 設(shè)置為無(wú)效狀態(tài)。
   }獲取并清除 PTE、PMD 和 PUD 的函數(shù)
根據(jù)是否啟用 SMP,定義了獲取并清除 PTE、PMD 和 PUD 的函數(shù)。
   #ifdef CONFIG_SMP
   static inline pte_t native_ptep_get_and_clear(pte_t *ptep)//獲取并清除給定的頁(yè)表項(xiàng)(PTE),pte_t *ptep: 指向要獲取并清除的頁(yè)表項(xiàng)的指針。
   {
           return pxx_xchg64(pte, ptep, 0ULL);將 ptep 指向的頁(yè)表項(xiàng)的值替換為 0ULL,并返回原來(lái)的 PTE 值。
   }
   static inline pmd_t native_pmdp_get_and_clear(pmd_t *pmdp)//獲取并清除給定的頁(yè)中間目錄(PMD)
   {
           return pxx_xchg64(pmd, pmdp, 0ULL);//將 pmdp 指向的頁(yè)中間目錄的值替換為 0ULL,并返回原來(lái)的 PMD 值。
   }
   static inline pud_t native_pudp_get_and_clear(pud_t *pudp)//獲取并清除給定的頁(yè)上級(jí)目錄(PUD)
   {
           return pxx_xchg64(pud, pudp);//將 pudp 指向的頁(yè)上級(jí)目錄的值替換為無(wú)效值,并返回原來(lái)的 PUD 值。
   }三、arm架構(gòu)下頁(yè)表分頁(yè)機(jī)制源碼分析1.頁(yè)表分配這段代碼定義了與 ARM 架構(gòu)相關(guān)的頁(yè)表分配操作。
源碼:arc/arm/include/asm/pgtable.h
定義PGD分配和釋放函數(shù)
   extern pgd_t *pgd_alloc(struct mm_struct *mm);//為給定的內(nèi)存管理結(jié)構(gòu)(mm_struct)分配一個(gè)新的頁(yè)全局目錄(PGD)。
   extern void pgd_free(struct mm_struct *mm, pgd_t *pgd);//釋放之前分配的頁(yè)全局目錄(PGD)定義清理 PTE 表的函數(shù)
   static inline void clean_pte_table(pte_t *pte)//清理與頁(yè)表項(xiàng)相關(guān)的緩存
   {
           clean_dcache_area(pte + PTE_HWTABLE_PTRS, PTE_HWTABLE_SIZE);//pte + PTE_HWTABLE_PTRS:計(jì)算要清理的緩存區(qū)域的起始地址,調(diào)用clean_dcache_area以清理指定區(qū)域的緩存。
   }定義 PTE 分配函數(shù)
這段代碼展示了內(nèi)核如何為內(nèi)核空間和用戶空間分配頁(yè)表。
pte_alloc_one_kernel 函數(shù):為內(nèi)核分配一個(gè)新的頁(yè)表項(xiàng)(PTE)頁(yè),使用 __pte_alloc_one_kernel 分配PTE,如果分配成功,調(diào)用 clean_pte_table 清理新分配的頁(yè)表,返回分配的PTE指針。
pte_alloc_one 函數(shù):為用戶空間分配一個(gè)新的頁(yè)表項(xiàng)頁(yè),使用 __pte_alloc_one 分配一個(gè)頁(yè),使用 GFP_PGTABLE_USER | PGTABLE_HIGHMEM 標(biāo)志,如果分配失敗,返回 NULL,如果分配的頁(yè)不是高端內(nèi)存頁(yè),調(diào)用 clean_pte_table 清理它,返回分配的頁(yè)。
clean_pte_table 函數(shù):用于初始化新分配的頁(yè)表,可能涉及清零或設(shè)置特定的初始值。
使用CONFIG_HIGHPTE 允許在某些架構(gòu)上將頁(yè)表存儲(chǔ)在高端內(nèi)存中,這可以節(jié)省低端內(nèi)存。
GFP_PGTABLE_USER 是一個(gè)特殊的分配標(biāo)志,用于用戶空間頁(yè)表分配。
PageHighMem 檢查是否為高端內(nèi)存頁(yè)。
page_address 函數(shù):用于獲取頁(yè)的虛擬地址。
   #define __HAVE_ARCH_PTE_ALLOC_ONE_KERNEL
   #define __HAVE_ARCH_PTE_ALLOC_ONE
   #define __HAVE_ARCH_PGD_FREE
   #include
   static inline pte_t *
   pte_alloc_one_kernel(struct mm_struct *mm)//為內(nèi)核分配一個(gè)頁(yè)表項(xiàng)(PTE)
   {
           pte_t *pte = __pte_alloc_one_kernel(mm);//調(diào)用 __pte_alloc_one_kernel(mm) 分配一個(gè)頁(yè)表項(xiàng)。
           if (pte)
                   clean_pte_table(pte);//如果分配成功,調(diào)用 clean_pte_table(pte) 清理頁(yè)表項(xiàng)相關(guān)的緩存。
           return pte;//返回分配的頁(yè)表項(xiàng)指針。
   }
   #ifdef CONFIG_HIGHPTE
   #define PGTABLE_HIGHMEM __GFP_HIGHMEM
   #else
   #define PGTABLE_HIGHMEM 0
   #endif
   static inline pgtable_t
   pte_alloc_one(struct mm_struct *mm)//為用戶空間分配一個(gè)頁(yè)表項(xiàng)(PTE),struct mm_struct *mm是指向當(dāng)前進(jìn)程的內(nèi)存管理結(jié)構(gòu)的指針。
   {
           struct page *pte;
           pte = __pte_alloc_one(mm, GFP_PGTABLE_USER | PGTABLE_HIGHMEM);//分配一個(gè)頁(yè)表項(xiàng),使用用戶空間分配標(biāo)志和高內(nèi)存標(biāo)志。
           if (!pte)
                   return NULL;//如果分配失敗,返回 NULL。
           if (!PageHighMem(pte))
                   clean_pte_table(page_address(pte));//如果分配的頁(yè)表項(xiàng)不在高內(nèi)存中,調(diào)用 clean_pte_table(page_address(pte)) 清理頁(yè)表項(xiàng)相關(guān)的緩存。
           return pte;//返回分配的頁(yè)表項(xiàng)指針。
   }定義 PMD 填充函數(shù)
__pmd_populate 函數(shù):內(nèi)部輔助函數(shù),用于填充PMD條目。
pmd_populate_kernel 函數(shù):用于內(nèi)核空間的PMD填充。
pmd_populate 函數(shù):用于用戶空間的PMD填充。
這段代碼展示了ARM架構(gòu)下內(nèi)核如何管理多級(jí)頁(yè)表結(jié)構(gòu),主要定義了與 ARM 架構(gòu)相關(guān)的頁(yè)表分配操作,包括頁(yè)表的分配和釋放、頁(yè)表的填充等。通過(guò)這些定義,內(nèi)核可以正確地管理和操作 ARM 架構(gòu)的頁(yè)表。它是內(nèi)存管理子系統(tǒng)中關(guān)鍵的一部分,負(fù)責(zé)維護(hù)虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系。
    static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,pmdval_t prot)//填充給定的頁(yè)中間目錄(PMD),將其設(shè)置為指向特定的頁(yè)表項(xiàng),pmd_t *pmdp指向要填充的 PMD 的指針,phys_addr_t pte是頁(yè)表項(xiàng)的物理地址,pmdval_t prot是PMD 的保護(hù)標(biāo)志。
    {
            pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;//計(jì)算 pmdval,將頁(yè)表項(xiàng)地址加上 PTE_HWTABLE_OFF(頁(yè)表項(xiàng)的偏移量),并與保護(hù)標(biāo)志進(jìn)行按位或操作。
            pmdp[0] = __pmd(pmdval);//將計(jì)算得到的 pmdval 轉(zhuǎn)換為 PMD 類型并賦值給 pmdp[0]。
    #ifndef CONFIG_ARM_LPAE
            pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));//如果未啟用 ARM LPAE(大頁(yè)表擴(kuò)展),則填充 pmdp[1],將 pmdval 加上 256 個(gè)頁(yè)表項(xiàng)的大小。
    #endif
            flush_pmd_entry(pmdp);//刷新 PMD 條目,以確保更新生效。
    }
    static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmdp, pte_t *ptep)//在內(nèi)核空間填充 PMD。
    {
            __pmd_populate(pmdp, __pa(ptep), _PAGE_KERNEL_TABLE);//調(diào)用 __pmd_populate,將頁(yè)表項(xiàng)的物理地址和內(nèi)核頁(yè)表的保護(hù)標(biāo)志 _PAGE_KERNEL_TABLE 傳遞給它。
    }
    static inline void
    pmd_populate(struct mm_struct *mm, pmd_t *pmdp, pgtable_t ptep)//填充用戶空間的 PMD。
    {
            extern pmdval_t user_pmd_table;//存儲(chǔ)用戶 PMD 表的保護(hù)標(biāo)志。
            pmdval_t prot;
            if (__LINUX_ARM_ARCH__ >= 6 && !IS_ENABLED(CONFIG_ARM_LPAE))
                    prot = user_pmd_table;
            else
                    prot = _PAGE_USER_TABLE;
            __pmd_populate(pmdp, page_to_phys(ptep), prot);//調(diào)用 __pmd_populate,將頁(yè)表的物理地址(通過(guò) page_to_phys(ptep) 獲。┖捅Wo(hù)標(biāo)志傳遞給它。
    }2.arm架構(gòu)相關(guān)的兩級(jí)頁(yè)表操作源碼:arc/arm/include/asm/pgtable-2level.h
定義兩級(jí)頁(yè)表結(jié)構(gòu)
定義了兩級(jí)頁(yè)表結(jié)構(gòu)的相關(guān)常量和宏。
   #define __PAGETABLE_PMD_FOLDED 1
   #define PTRS_PER_PTE        512//每個(gè)頁(yè)表項(xiàng)(PTE)指向的條目數(shù)量,通常為 512。
   #define PTRS_PER_PMD        1//每個(gè)頁(yè)中間目錄(PMD)指向的條目數(shù)量,通常為 1。
   #define PTRS_PER_PGD        2048//每個(gè)頁(yè)全局目錄(PGD)指向的條目數(shù)量,通常為 2048。
   #define PTE_HWTABLE_PTRS    (PTRS_PER_PTE)//硬件表中每個(gè)頁(yè)表項(xiàng)的指針數(shù)量。
   #define PTE_HWTABLE_OFF     (PTE_HWTABLE_PTRS * sizeof(pte_t))//頁(yè)表項(xiàng)的偏移量,計(jì)算為頁(yè)表項(xiàng)數(shù)量乘以每個(gè)頁(yè)表項(xiàng)的大小。
   #define PTE_HWTABLE_SIZE    (PTRS_PER_PTE * sizeof(u32))//頁(yè)表項(xiàng)的總大小,計(jì)算為頁(yè)表項(xiàng)數(shù)量乘以每個(gè)頁(yè)表項(xiàng)的大小。
   #define MAX_POSSIBLE_PHYSMEM_BITS    32//定義最大可能的物理內(nèi)存位數(shù),通常為 32 位,表示支持的最大物理內(nèi)存為 4GB。
   #define PMD_SHIFT        21//定義PMD的位移量,通常為 21 位
   #define PGDIR_SHIFT      21定義PGD 的位移量,通常為 21 位
   #define PMD_SIZE         (1UL 定義 Linux PTE
定義了 Linux PTE 的相關(guān)常量和宏。
   #define L_PTE_VALID        (_AT(pteval_t, 1) 定義 PUD 操作函數(shù)
這段代碼定義了一系列與頁(yè)表管理相關(guān)的內(nèi)聯(lián)函數(shù)和宏,特別是針對(duì)PUD(Page Upper Directory)和PMD(Page Middle Directory)級(jí)別的操作。這些定義通常用于ARM架構(gòu),特別是在不使用三級(jí)或四級(jí)頁(yè)表的情況下。
PUD(Page Upper Directory)相關(guān)函數(shù):

PMD(Page Middle Directory)相關(guān)函數(shù):

這段代碼針對(duì)的是一個(gè)簡(jiǎn)化的頁(yè)表結(jié)構(gòu),可能是兩級(jí)頁(yè)表系統(tǒng)(PGD -> PMD -> PTE),PUD級(jí)別在這個(gè)架構(gòu)中被忽略或不存在,所有PUD相關(guān)操作都是空操作或返回固定值,PMD操作被定義為直接操作頁(yè)表?xiàng)l目,包括設(shè)置、清除、復(fù)制等,使用位操作來(lái)判斷PMD的狀態(tài)(如large、leaf、bad等),這是一種常見(jiàn)的優(yōu)化技術(shù),包含了一些架構(gòu)特定的操作,如flush_pmd_entry和clean_pmd_entry,用于確保頁(yè)表更改對(duì)硬件可見(jiàn)。
   #ifndef __ASSEMBLY__
   static inline int pud_none(pud_t pud)
   {
       return 0;//檢查給定的 PUD 是否為空。當(dāng)前實(shí)現(xiàn)總是返回 0,表示 PUD 不為空。
   }
   static inline int pud_bad(pud_t pud)
   {
       return 0;//檢查給定的 PUD 是否無(wú)效。當(dāng)前實(shí)現(xiàn)總是返回 0,表示 PUD 是有效的。
   }
   static inline int pud_present(pud_t pud)
   {
       return 1;//檢查給定的 PUD 是否存在。當(dāng)前實(shí)現(xiàn)總是返回 1,表示 PUD 存在。
   }
   static inline void pud_clear(pud_t *pudp)
   {
   }//清除給定的 PUD。當(dāng)前實(shí)現(xiàn)為空,表示沒(méi)有具體的清除邏輯。
   static inline void set_pud(pud_t *pudp, pud_t pud)
   {
   }//設(shè)置給定的 PUD。當(dāng)前實(shí)現(xiàn)為空,表示沒(méi)有具體的設(shè)置邏輯。
   static inline pmd_t *pmd_offset(pud_t *pud, unsigned long addr)
   {
       return (pmd_t *)pud;//返回指向 PMD 的指針。
   }
   #define pmd_offset pmd_offset
   #define pmd_pfn(pmd)(__phys_to_pfn(pmd_val(pmd) & PHYS_MASK))//從 PMD 中提取物理頁(yè)框號(hào),使用 pmd_val 獲取 PMD 的值,并與物理掩碼進(jìn)行按位與操作。
   #define pmd_large(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否為大頁(yè)
   #define pmd_leaf(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否為葉節(jié)點(diǎn)。
   #define pmd_bad(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否無(wú)效。
   #define pmd_present(pmd)(pmd_val(pmd))//檢查 PMD 是否存在。
   #define copy_pmd(pmdpd,pmdps) //復(fù)制 PMD 條目并刷新目標(biāo) PMD 條目。
       do {               
           pmdpd[0] = pmdps[0];   
           pmdpd[1] = pmdps[1];   
           flush_pmd_entry(pmdpd);   
       } while (0)
   #define pmd_clear(pmdp)//清除 PMD 條目,將其設(shè)置為無(wú)效,并調(diào)用 clean_pmd_entry 清理相關(guān)緩存。
       do {               
           pmdp[0] = __pmd(0);   
           pmdp[1] = __pmd(0);   
           clean_pmd_entry(pmdp);   
       } while (0)
   #define pmd_addr_end(addr,end) (end)//返回 PMD 地址范圍的結(jié)束地址
   #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)//設(shè)置頁(yè)表項(xiàng)(PTE)并擴(kuò)展其屬性
   #define pmd_hugewillfault(pmd)(0)
   #define pmd_thp_or_huge(pmd)(0)//檢查 PMD 是否會(huì)導(dǎo)致故障或是否為大頁(yè)。當(dāng)前實(shí)現(xiàn)總是返回 0,表示不會(huì)導(dǎo)致故障或不是大頁(yè)。
   #endif /* __ASSEMBLY__ */3.arm架構(gòu)相關(guān)的三級(jí)頁(yè)表操作源碼:arc/arm/include/asm/pgtable-3level.h
定義三級(jí)頁(yè)表結(jié)構(gòu)
定義了三級(jí)頁(yè)表結(jié)構(gòu)的相關(guān)常量和宏。
   #define PTRS_PER_PTE        512//每個(gè)頁(yè)表項(xiàng)(PTE)指向的條目數(shù)量,通常為 512。
   #define PTRS_PER_PMD        512//每個(gè)頁(yè)中間目錄(PMD)指向的條目數(shù)量,通常為 512。
   #define PTRS_PER_PGD        4//每個(gè)頁(yè)全局目錄(PGD)指向的條目數(shù)量,通常為 4。
   #define PTE_HWTABLE_PTRS    (0)//每個(gè)頁(yè)表項(xiàng)的指針數(shù)量,這里設(shè)置為 0,表示沒(méi)有硬件表。
   #define PTE_HWTABLE_OFF     (0)//頁(yè)表項(xiàng)的偏移量,這里設(shè)置為 0。
   #define PTE_HWTABLE_SIZE    (PTRS_PER_PTE * sizeof(u64))//頁(yè)表項(xiàng)的總大小,計(jì)算為頁(yè)表項(xiàng)數(shù)量乘以每個(gè)頁(yè)表項(xiàng)的大小
   #define MAX_POSSIBLE_PHYSMEM_BITS 40//定義最大可能的物理內(nèi)存位數(shù),通常為 40 位,表示支持的最大物理內(nèi)存為 1TB。
   #define PGDIR_SHIFT         30//定義 PGD 的位移量,為 30 位。
   #define PMD_SHIFT           21//定義 PMD 的位移量,為 21 位。
   #define PMD_SIZE            (1UL 定義大頁(yè)相關(guān)常量
   #define HPAGE_SHIFT         PMD_SHIFT//將大頁(yè)的位移量定義為 PMD 的位移量。
   #define HPAGE_SIZE          (_AC(1, UL) 定義 Linux PTE
   #define L_PTE_VALID         (_AT(pteval_t, 1) 定義 PUD 操作函數(shù)
這段代碼主要定義了與 ARM 架構(gòu)相關(guān)的三級(jí)頁(yè)表操作,包括頁(yè)表的結(jié)構(gòu)、頁(yè)表項(xiàng)的定義、PUD 和 PMD 操作函數(shù)等。通過(guò)這些定義,內(nèi)核可以正確地管理和操作 ARM 架構(gòu)的三級(jí)頁(yè)表。





   #ifndef __ASSEMBLY__
   #define pud_none(pud)       (!pud_val(pud))//檢查 PUD 是否為空
   #define pud_bad(pud)        (!(pud_val(pud) & 2))//檢查 PUD 是否無(wú)效
   #define pud_present(pud)    (pud_val(pud))//檢查 PUD 是否存在
   #define pmd_table(pmd)    ((pmd_val(pmd) & PMD_TYPE_MASK) ==  PMD_TYPE_TABLE)//檢查 PMD 是否指向頁(yè)表
   #define pmd_sect(pmd)       ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_SECT)//檢查 PMD 是否指向段
   #define pmd_large(pmd)      pmd_sect(pmd)//檢查 PMD 是否為大頁(yè)
   #define pmd_leaf(pmd)       pmd_sect(pmd)//檢查 PMD 是否為葉節(jié)點(diǎn)
   #define pud_clear(pudp)  //清除 PUD,將其設(shè)置為無(wú)效,并清理相關(guān)緩存      
       do {               
           *pudp = __pud(0);   
           clean_pmd_entry(pudp);   
       } while (0)
   #define set_pud(pudp, pud)  //設(shè)置 PUD,并刷新相關(guān)條目     
       do {               
           *pudp = pud;        
           flush_pmd_entry(pudp);   
       } while (0)
   static inline pmd_t *pud_pgtable(pud_t pud)
   {
       return __va(pud_val(pud) & PHYS_MASK & (s32)PAGE_MASK);
   }
   #define pmd_bad(pmd)        (!(pmd_val(pmd) & 2))//檢查 PMD 是否無(wú)效
   #define copy_pmd(pmdpd,pmdps)       //復(fù)制 PMD 條目并刷新目標(biāo) PMD
       do {               
           *pmdpd = *pmdps;   
           flush_pmd_entry(pmdpd);   
       } while (0)
   #define pmd_clear(pmdp)         //清除 PMD,將其設(shè)置為無(wú)效,并清理相關(guān)緩存
       do {               
           *pmdp = __pmd(0);   
           clean_pmd_entry(pmdp);   
       } while (0)
   #define __HAVE_ARCH_PTE_SAME
   #define pte_same(pte_a,pte_b)   ((pte_present(pte_a) ? pte_val(pte_a) & ~PTE_EXT_NG    \
                       : pte_val(pte_a))                \
                   == (pte_present(pte_b) ? pte_val(pte_b) & ~PTE_EXT_NG    \
                       : pte_val(pte_b)))
   #define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,__pte(pte_val(pte)|(ext)))
   #define pte_huge(pte)       (pte_val(pte) && !(pte_val(pte) & PTE_TABLE_BIT))
   #define pte_mkhuge(pte)     (__pte(pte_val(pte) & ~PTE_TABLE_BIT))
   #define pmd_isset(pmd, val) ((u32)(val) == (val) ? pmd_val(pmd) & (val) \
                           : !!(pmd_val(pmd) & (val)))
   #define pmd_isclear(pmd, val)   (!(pmd_val(pmd) & (val)))
   #define pmd_present(pmd)    (pmd_isset((pmd), L_PMD_SECT_VALID))//檢查 PMD 是否存在
   #define pmd_young(pmd)      (pmd_isset((pmd), PMD_SECT_AF))//檢查 PMD 是否為年輕狀態(tài)
   #define pte_special(pte)    (pte_isset((pte), L_PTE_SPECIAL))
   static inline pte_t pte_mkspecial(pte_t pte)
   {
       pte_val(pte) |= L_PTE_SPECIAL;
       return pte;
   }
   #define pmd_write(pmd)      (pmd_isclear((pmd), L_PMD_SECT_RDONLY))//檢查 PMD 是否可寫
   #define pmd_dirty(pmd)      (pmd_isset((pmd), L_PMD_SECT_DIRTY))//檢查 PMD 是否已被修改
   #define pmd_hugewillfault(pmd)   (!pmd_young(pmd) || !pmd_write(pmd))
   #define pmd_thp_or_huge(pmd) (pmd_huge(pmd) || pmd_trans_huge(pmd))
   #ifdef CONFIG_TRANSPARENT_HUGEPAGE
   #define pmd_trans_huge(pmd)  (pmd_val(pmd) && !pmd_table(pmd))
   #endif
   #define PMD_BIT_FUNC(fn,op) \
   static inline pmd_t pmd_##fn(pmd_t pmd) { pmd_val(pmd) op; return pmd; }
   PMD_BIT_FUNC(wrprotect,    |= L_PMD_SECT_RDONLY);
   PMD_BIT_FUNC(mkold,    &= ~PMD_SECT_AF);
   PMD_BIT_FUNC(mkwrite_novma,   &= ~L_PMD_SECT_RDONLY);
   PMD_BIT_FUNC(mkdirty,   |= L_PMD_SECT_DIRTY);
   PMD_BIT_FUNC(mkclean,   &= ~L_PMD_SECT_DIRTY);
   PMD_BIT_FUNC(mkyoung,   |= PMD_SECT_AF);
   #define pmd_mkhuge(pmd)     (__pmd(pmd_val(pmd) & ~PMD_TABLE_BIT))
   #define pmd_pfn(pmd)        (((pmd_val(pmd) & PMD_MASK) & PHYS_MASK) >> PAGE_SHIFT)//從 PMD 中提取物理頁(yè)框號(hào)PFN
   #define pfn_pmd(pfn,prot)   (__pmd(((phys_addr_t)(pfn) = TASK_SIZE);
       if (pmd_val(pmd) & L_PMD_SECT_NONE)
           pmd_val(pmd) &= ~L_PMD_SECT_VALID;
       if (pmd_write(pmd) && pmd_dirty(pmd))
           pmd_val(pmd) &= ~PMD_SECT_AP2;
       else
           pmd_val(pmd) |= PMD_SECT_AP2;
       *pmdp = __pmd(pmd_val(pmd) | PMD_SECT_nG);
       flush_pmd_entry(pmdp);
   }
   #endif /* __ASSEMBLY__ */四、示例實(shí)踐(1)代碼功能
編寫一個(gè)內(nèi)核模塊示例來(lái)演示如何在內(nèi)核中創(chuàng)建一個(gè)頁(yè)表映射。
(2)實(shí)現(xiàn)步驟
a.編寫內(nèi)核模塊代碼。
b.編譯內(nèi)核模塊。
c.加載內(nèi)核模塊。
d.檢查內(nèi)核日志以分析結(jié)果。
e.卸載內(nèi)核模塊。
(3)代碼實(shí)現(xiàn)
pagetable_example.c
#include
#include
#include
#include
#include
#include
static int __init pagetable_example_init(void) {
    struct page *page;
    void *vaddr;
    unsigned long paddr;
    // 分配一個(gè)物理頁(yè)
    page = alloc_page(GFP_KERNEL);
    if (!page) {
        pr_err("Failed to allocate page
");
        return -ENOMEM;
    }
    // 獲取物理地址
    paddr = page_to_phys(page);
    pr_info("Allocated page at physical address: 0x%lx
", paddr);
    // 映射到內(nèi)核虛擬地址空間
    vaddr = vmap(&page, 1, VM_MAP, PAGE_KERNEL);
    if (!vaddr) {
        pr_err("Failed to map page to virtual address
");
        __free_page(page);
        return -ENOMEM;
    }
    pr_info("Mapped page to virtual address: %p
", vaddr);
    // 寫入數(shù)據(jù)到映射的內(nèi)存
    strcpy(vaddr, "Hello, kernel page!");
    // 讀取數(shù)據(jù)
    pr_info("Data in mapped page: %s
", (char *)vaddr);
    // 取消映射
    vunmap(vaddr);
    // 釋放物理頁(yè)
    __free_page(page);
    return 0;
}
static void __exit pagetable_example_exit(void) {
    pr_info("Page table example module unloaded
");
}
module_init(pagetable_example_init);
module_exit(pagetable_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple example of page table mapping in the Linux kernel");編譯和加載內(nèi)核模塊
    obj-m += pagetable_example.o
    all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean加載內(nèi)核模塊
sudo insmod pagetable_example.ko檢查內(nèi)核日志以分析結(jié)果

卸載內(nèi)核模塊
sudo rmmod pagetable_example分析結(jié)果
物理地址:0x199fe000 是分配的物理頁(yè)的地址。
虛擬地址:000000007e626547 是映射到內(nèi)核虛擬地址空間的地址。
數(shù)據(jù):Hello, kernel page! 是寫入和讀取的測(cè)試數(shù)據(jù)?偨Y(jié)
這個(gè)例子展示了如何在內(nèi)核中分配一個(gè)物理頁(yè),將其映射到內(nèi)核虛擬地址空間,進(jìn)行讀寫操作,然后取消映射并釋放物理頁(yè)。
五、心得體會(huì)通過(guò)對(duì)這一部分的學(xué)習(xí),我真切的感受到了線性地址和物理地址的映射關(guān)系、內(nèi)存空間的分配,以及在內(nèi)存中讀寫信息的過(guò)程,對(duì)內(nèi)核也有了進(jìn)步一的認(rèn)識(shí),Linux內(nèi)核頁(yè)表映射確實(shí)是一個(gè)深入理解操作系統(tǒng)內(nèi)存管理機(jī)制的重要過(guò)程。此次分析的內(nèi)核源碼為L(zhǎng)inux6.8.0。
猜你喜歡:
WiFi6+藍(lán)牙+星閃,三合一開發(fā)板,真香!
Github上熱門 C 語(yǔ)言項(xiàng)目匯總!
嵌入式,可測(cè)試性軟件設(shè)計(jì)!
一些低功耗軟件設(shè)計(jì)的要點(diǎn)!
嵌入式 C 保護(hù)結(jié)構(gòu)體的方式
實(shí)用 | 10分鐘教你通過(guò)網(wǎng)頁(yè)點(diǎn)燈
談?wù)勄度胧杰浖募嫒菪裕?/strong>
分享一個(gè)嵌入式代碼生成器設(shè)計(jì)思路!
點(diǎn)擊閱讀原文,查看更多分享。

發(fā)表回復(fù)

本版積分規(guī)則


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表