|
前言操作系統(tǒng)的核心任務是對系統(tǒng)資源的管理,而重中之重就是對CPU和內(nèi)存的管理。為了使進程擺脫系統(tǒng)內(nèi)存的制約,用戶進程運行在虛擬內(nèi)存之上,每個用戶進程都擁有完整的虛擬地址空間,互不干涉。而實現(xiàn)虛擬內(nèi)存的關鍵就在于建立虛擬地址與物理地址之間的映射,因為無論如何數(shù)據(jù)終究要存儲到物理內(nèi)存中才能被記錄下來。
yftoeixwr1264076326815.png (43.59 KB, 下載次數(shù): 6)
下載附件
保存到相冊
yftoeixwr1264076326815.png
2024-10-12 00:57 上傳
如上圖所示,進程1和進程2都擁有完整的虛擬地址空間,虛擬地址空間又分為了用戶空間和內(nèi)核空間,對于不同的進程面對的都是同一個內(nèi)核,其內(nèi)核空間的地址對于的物理地址都是一樣的,因而進程1和進程2中內(nèi)核空間的VA K地址都映射到了物理內(nèi)存的PA K地址。
一、分頁機制1.分頁機制的核心思想就是解除線性地址和物理地址的一一對應關系,使線性地址連續(xù),而物理地址不連續(xù)。使連續(xù)的線性地址可以與任意物理內(nèi)存地址相關聯(lián),即從虛擬頁面到物理頁面的映射。而這個翻譯過程由內(nèi)存管理單元(MMU)完成,MMU接收CPU發(fā)出的虛擬地址,將其翻譯為物理地址后發(fā)送給內(nèi)存。內(nèi)存管理單元按照該物理地址進行相應訪問后讀出或?qū)懭胂嚓P數(shù)據(jù),如下圖所示:
isojib1esqp64076326915.png (71.28 KB, 下載次數(shù): 4)
下載附件
保存到相冊
isojib1esqp64076326915.png
2024-10-12 00:57 上傳
2.分頁機制尋址原理通過查頁表,對于每個程序,內(nèi)存管理單元MMU都為其保存一個頁表,該頁表中存放的是虛擬頁面到物理頁面的映射。每當為一個虛擬頁面尋找到一個物理頁面之后,就在頁表里增加一條記錄來保留該映射關系。當然,隨著虛擬頁面進出物理內(nèi)存,頁表的內(nèi)容也會不斷更新變化。
vslfj3wth1t64076327015.png (94.21 KB, 下載次數(shù): 6)
下載附件
保存到相冊
vslfj3wth1t64076327015.png
2024-10-12 00:57 上傳
二級頁表模式:
(1)取虛擬地址高10位*4+頁目錄表(PDE)得到頁表的物理地址
(2)取虛擬地址中間10位*4+頁表(PTE)得到頁地址
(3)取虛擬地址最后12位+頁地址得到最終的物理地址頁目錄項、頁表項結(jié)構(gòu):
3vhw0gv1yy464076327115.png (27.74 KB, 下載次數(shù): 7)
下載附件
保存到相冊
3vhw0gv1yy464076327115.png
2024-10-12 00:57 上傳
m0b5icrctod64076327215.png (27.39 KB, 下載次數(shù): 4)
下載附件
保存到相冊
m0b5icrctod64076327215.png
2024-10-12 00:57 上傳
3.啟動分頁機制,按順序做好三件事(1)準備好頁目錄表及頁表
(2)將頁表地址寫入控制寄存器cr3
(3)寄存器cr0的PG位置1,開啟頁表機制
二、x86架構(gòu)下頁表分頁機制源碼分析1.頁表結(jié)構(gòu)源碼:arch/x86/include/asm/pgtable_type.h
這段代碼是 Linux 內(nèi)核中用于定義 x86 架構(gòu)下頁表相關的宏和數(shù)據(jù)結(jié)構(gòu)的頭文件,它定義了頁表項的各個標志位、頁表項的格式以及一些輔助函數(shù)和宏。
頁表項標志位定義
#define _PAGE_BIT_PRESENT /* 表示頁面是否存在 */
#define _PAGE_BIT_RW /* 表示頁面是否可寫 */
#define _PAGE_BIT_USER /* 示頁面是否用戶可訪問 */頁表項的組合
#define _PAGE_SHARED
#define _PAGE_KERNEL
#define _PAGE_KERNEL_EXEC頁表項的類型定義
typedef struct{unsigned long pgd;}pgd_t;/* 頂級頁表目錄 */
typedef struct{unsigned long pud;}pud_t;/* 高層頁表目錄 */
typedef struct{unsigned long pmd;}pmd_t;/* 中層頁表目錄 */
typedef struct{unsigned long pte;}pte_t;/* 最底層頁表條目 */2.對頁表的基本操作源碼:arch/x86/mm/pgtable/pgtable.c
這段代碼包含了頁表分配、釋放、設置訪問標志、清除訪問標志等功能。這些操作是內(nèi)存管理的基本操作,通過這些操作,內(nèi)核可以高效地管理虛擬內(nèi)存和物理內(nèi)存之間的映射關系。
頁表分配和釋放
定義了頁表分配和釋放的函數(shù)。頁表分配是指為進程或內(nèi)核分配新的頁表項。這通常在進程創(chuàng)建或需要新的虛擬內(nèi)存區(qū)域時進行。分配頁表項時,內(nèi)核會從物理內(nèi)存中分配一塊內(nèi)存,并將其初始化為頁表項。
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ù)分配一個新的 PGD。如果分配失敗,跳轉(zhuǎn)到 out 標簽.
if (pgd == NULL)
goto out;
//設置 PGD:
mm->pgd = pgd;//將分配的 PGD 賦值給當前進程的內(nèi)存管理結(jié)構(gòu)。
//預分配 PMD:
if (sizeof(pmds) != 0 &&
preallocate_pmds(mm, pmds, PREALLOCATED_PMDS) != 0)
goto out_free_pgd;//如果 pmds 數(shù)組的大小不為 0,調(diào)用 preallocate_pmds() 函數(shù)為當前進程預分配 PMD(頁中間目錄)。如果失敗,跳轉(zhuǎn)到 out_free_pgd 標簽。
//預分配用戶 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() 進行并行虛擬化的 PGD 分配。
// 加鎖:
spin_lock(&pgd_lock);//獲取 PGD 鎖以保護后續(xù)操作。
//PGD 構(gòu)造:
pgd_ctor(mm, pgd);//調(diào)用 pgd_ctor() 初始化 PGD。
//預填充 PMD:
if (sizeof(pmds) != 0)
pgd_prepopulate_pmd(mm, pgd, pmds);//如果 pmds 不為空,調(diào)用 pgd_prepopulate_pmd() 預填充 PMD。
//預填充用戶 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() 進行 PGD 的析構(gòu)。
paravirt_pgd_free(mm, pgd);//調(diào)用 paravirt_pgd_free() 釋放并行虛擬化的 PGD。
_pgd_free(pgd);//調(diào)用 _pgd_free() 釋放 PGD。
}頁表級別的釋放
頁表級別的釋放函數(shù)用于釋放不同級別的頁表項,定義了頁表級別的釋放函數(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)換為相應的頁表描述符.
paravirt_release_pmd(__pa(pmd) >> PAGE_SHIFT);// 釋放 PMD,用 paravirt_release_pmd 函數(shù)釋放 PMD。__pa(pmd) 將虛擬地址轉(zhuǎn)換為物理地址,>> PAGE_SHIFT 用于獲取頁框號。
#ifdef CONFIG_X86_PAE
tlb->need_flush_all = 1;
#endif//如果啟用了 PAE,設置 tlb->need_flush_all 為 1,表示需要刷新所有 TLB 條目。
pagetable_pmd_dtor(ptdesc);//調(diào)用 pagetable_pmd_dtor 函數(shù)對 PMD 進行析構(gòu),釋放與 PMD 相關的資源。
paravirt_tlb_remove_table(tlb, ptdesc_page(ptdesc));//調(diào)用 paravirt_tlb_remove_table 函數(shù)從 TLB 中移除與 PMD 相關的頁表條目。
}設置和清除訪問標志
定義了設置和清除頁表項訪問標志的函數(shù),設置訪問標志是指修改頁表項中的標志位,以控制頁面的訪問權(quán)限。例如,可以設置頁面為可讀、可寫或可執(zhí)行。設置訪問標志時,內(nèi)核會更新頁表項中的相應位。設置和清除頁表項訪問標志的函數(shù)用于管理頁表項的訪問權(quán)限,這些函數(shù)確保了內(nèi)存的安全性和訪問控制。
p0a4x2a0msr64076327315.png (86.43 KB, 下載次數(shù): 5)
下載附件
保存到相冊
p0a4x2a0msr64076327315.png
2024-10-12 00:57 上傳
xioteey2zqa64076327415.png (96.27 KB, 下載次數(shù): 4)
下載附件
保存到相冊
xioteey2zqa64076327415.png
2024-10-12 00:57 上傳
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ù)檢查當前頁表項與新條目是否相同。如果不同,設置 changed 為 1。
if (changed && dirty)
set_pte(ptep, entry);//更新頁表項,如果頁表項已更改且 dirty 為真,調(diào)用 set_pte 函數(shù)更新頁表項。
return changed;//返回 changed,指示頁表項是否已更改。
}
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);//檢查頁表項的年輕標志,使用 pte_young 函數(shù)檢查頁表項是否被標記為“年輕”。如果是,調(diào)用 test_and_clear_bit 函數(shù)清除訪問標志,并將結(jié)果存儲在 ret 中。
return ret;//返回 ret,指示是否成功清除年輕標志。
}3.x86 架構(gòu)相關的兩級頁表操作源碼:arch/x86/include/asm/pgtable-2level.h
這段代碼主要定義了與 x86 架構(gòu)相關的兩級頁表操作,包括設置和清除 PTE、PMD 和 PUD 的函數(shù),以及交換條目和 PTE 的編碼/解碼宏和函數(shù)。通過這些定義,內(nèi)核可以正確地管理和操作兩級頁表。
頭文件保護
使用頭文件保護機制,防止重復包含。
#ifndef _ASM_X86_PGTABLE_2LEVEL_H
#define _ASM_X86_PGTABLE_2LEVEL_H錯誤打印宏
定義了用于打印 PTE 和 PGD 錯誤的宏。
#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))設置 PTE、PMD 和 PUD 的函數(shù)
static inline void native_set_pte(pte_t *ptep , pte_t pte)
{
*ptep = pte;
}//將傳入的 pte 值賦給指向的頁表項 *ptep。
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)
{
*pmdp = pmd;
}//將傳入的 pmd 值賦給指向的頁中間目錄 *pmdp。
static inline void native_set_pud(pud_t *pudp, pud_t pud)
{
}//該函數(shù)的實現(xiàn)為空,表示當前沒有對 PUD 的設置操作。原子設置 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 來設置頁表項。
}清除 PMD 和 PUD 的函數(shù)
static inline void native_pmd_clear(pmd_t *pmdp)
{
native_set_pmd(pmdp, __pmd(0));//調(diào)用 native_set_pmd,將 PMD 設置為 __pmd(0),將 PMD 清空或設置為無效狀態(tài)。__pmd(0) 通常表示一個無效的 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),將頁表項 *xp 設置為無效狀態(tài)。native_make_pte(0) 通常會返回一個表示無效頁表項的 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,并返回原來的 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,并返回原來的 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,并返回原來的 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 宏在編譯時檢查 MAX_SWAPFILES_SHIFT 是否大于 5。如果是,則編譯失敗。這是為了確保交換文件的數(shù)量不會超過可以用位表示的最大值。
#define __swp_type(x)(((x).val >> _SWP_TYPE_SHIFT)& _SWP_TYPE_MASK)//從交換條目 x 中提取交換類型。通過右移和掩碼操作獲取類型值。
#define __swp_offset(x)((x).val >> SWP_OFFSET_SHIFT)//從交換條目 x 中提取交換偏移量。通過右移操作獲取偏移值。
#define __swp_entry(type, offset)((swp_entry_t) { \
(((type) & _SWP_TYPE_MASK) 交換 PTE 的獨占標記
定義了用于存儲獨占標記的交換 PTE 位。
#define _PAGE_SWP_EXCLUSIVE _PAGE_PSE無反轉(zhuǎn) PFN
定義了無反轉(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)相關的三級頁表操作源碼:arch/x86/include/asm/pgtable-3level.h
這段代碼主要定義了與 x86 架構(gòu)相關的三級頁表操作,包括設置和清除 PTE、PMD 和 PUD 的函數(shù),以及交換條目和 PTE 的編碼/解碼宏和函數(shù)。通過這些定義,內(nèi)核可以正確地管理和操作三級頁表。
頭文件保護
使用頭文件保護機制,防止重復包含。
#ifndef _ASM_X86_PGTABLE_3LEVEL_H
#define _ASM_X86_PGTABLE_3LEVEL_H錯誤打印宏
定義了用于打印 PTE、PMD 和 PGD 錯誤的宏。
#define pte_ERROR(e)
pr_err("%s:%d: bad pte %p(%08lx%08lx)
",
__FILE__, __LINE__, &(e), (e).pte_high, (e).pte_low)//當檢測到無效的 PTE 時,調(diào)用此宏以輸出詳細的錯誤信息。
#define pmd_ERROR(e)
pr_err("%s:%d: bad pmd %p(%016Lx)
",
__FILE__, __LINE__, &(e), pmd_val(e))//當檢測到無效的 PMD 時,調(diào)用此宏以輸出詳細的錯誤信息。
#define pgd_ERROR(e)
pr_err("%s:%d: bad pgd %p(%016Lx)
",
__FILE__, __LINE__, &(e), pgd_val(e))//當檢測到無效的 PGD 時,調(diào)用此宏以輸出詳細的錯誤信息。交換宏
定義了用于交換 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 指向的當前值,并存儲在 _o 中。
do { } while (!try_cmpxchg64(_p, &_o, (_val)));//使用 try_cmpxchg64 函數(shù)嘗試將 _val 設置為 _p 指向的值。如果當前值與 _o 不同,交換操作將失敗,循環(huán)將繼續(xù)嘗試,直到成功為止。
native_make_##_pxx(_o);//調(diào)用 native_make_##_pxx 函數(shù),將 _o 轉(zhuǎn)換為適當?shù)念愋筒⒎祷亍?nbsp;
})設置 PTE、PMD 和 PUD 的函數(shù)
定義了設置 PTE、PMD 和 PUD 的函數(shù)。
static inline void native_set_pte(pte_t *ptep, pte_t pte)//native_set_pte 用于設置給定的頁表項,pte_t *ptep: 指向要設置的頁表項的指針,pte_t pte: 要設置的頁表項值。
{
WRITE_ONCE(ptep->pte_high, pte.pte_high);//使用 WRITE_ONCE 宏將 pte_high 寫入 ptep 指向的頁表項的高位部分。
smp_wmb();//調(diào)用 smp_wmb(),確保在多處理器環(huán)境中寫入的順序。
WRITE_ONCE(ptep->pte_low, pte.pte_low);//使用 WRITE_ONCE 宏將 pte_low 寫入 ptep 指向的頁表項的低位部分。
}
static inline void native_set_pte_atomic(pte_t *ptep, pte_t pte)//以原子方式設置給定的頁表項
{
pxx_xchg64(pte, ptep, native_pte_val(pte));//調(diào)用 pxx_xchg64 宏,執(zhí)行原子交換操作,將 pte 的值設置到 ptep 指向的頁表項中。
}
static inline void native_set_pmd(pmd_t *pmdp, pmd_t pmd)//用于設置給定的頁中間目錄
{
pxx_xchg64(pmd, pmdp, native_pmd_val(pmd));//將 pmd 的值設置到 pmdp 指向的頁中間目錄中。
}
static inline void native_set_pud(pud_t *pudp, pud_t pud)//設置給定的頁上級目錄
{
#ifdef CONFIG_PAGE_TABLE_ISOLATION
pud.p4d.pgd = pti_set_user_pgtbl(&pudp->p4d.pgd, pud.p4d.pgd);//設置用戶頁表
#endif
pxx_xchg64(pud, pudp, native_pud_val(pud));//將 pud 的值設置到 pudp 指向的頁上級目錄中。
}清除 PTE、PMD 和 PUD 的函數(shù)
定義了清除 PTE、PMD 和 PUD 的函數(shù)。
static inline void native_pte_clear(struct mm_struct *mm, unsigned long addr,pte_t *ptep)//清除給定的頁表項(PTE),將其設置為無效狀態(tài)。
{
WRITE_ONCE(ptep->pte_low, 0);//將 pte_low 設置為 0,清除頁表項的低位部分。
smp_wmb();//確保在多處理器環(huán)境中寫入的順序
WRITE_ONCE(ptep->pte_high, 0);//將 pte_high 設置為 0,清除頁表項的高位部分。
}
static inline void native_pmd_clear(pmd_t *pmdp)//清除給定的頁中間目錄(PMD),將其設置為無效狀態(tài)。
{
WRITE_ONCE(pmdp->pmd_low, 0);//將 pmd_low 設置為 0,清除頁中間目錄的低位部分。
smp_wmb();//確保在多處理器環(huán)境中寫入的順序。
WRITE_ONCE(pmdp->pmd_high, 0);//將 pmd_high 設置為 0,清除頁中間目錄的高位部分。
}
static inline void native_pud_clear(pud_t *pudp)//清除給定的頁上級目錄(PUD)
{
}
static inline void pud_clear(pud_t *pudp)//清除給定的頁上級目錄(PUD),將其設置為無效狀態(tài)。
{
set_pud(pudp, __pud(0));//將 pud 設置為無效狀態(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)//獲取并清除給定的頁表項(PTE),pte_t *ptep: 指向要獲取并清除的頁表項的指針。
{
return pxx_xchg64(pte, ptep, 0ULL);將 ptep 指向的頁表項的值替換為 0ULL,并返回原來的 PTE 值。
}
static inline pmd_t native_pmdp_get_and_clear(pmd_t *pmdp)//獲取并清除給定的頁中間目錄(PMD)
{
return pxx_xchg64(pmd, pmdp, 0ULL);//將 pmdp 指向的頁中間目錄的值替換為 0ULL,并返回原來的 PMD 值。
}
static inline pud_t native_pudp_get_and_clear(pud_t *pudp)//獲取并清除給定的頁上級目錄(PUD)
{
return pxx_xchg64(pud, pudp);//將 pudp 指向的頁上級目錄的值替換為無效值,并返回原來的 PUD 值。
}三、arm架構(gòu)下頁表分頁機制源碼分析1.頁表分配這段代碼定義了與 ARM 架構(gòu)相關的頁表分配操作。
源碼:arc/arm/include/asm/pgtable.h
定義PGD分配和釋放函數(shù)
extern pgd_t *pgd_alloc(struct mm_struct *mm);//為給定的內(nèi)存管理結(jié)構(gòu)(mm_struct)分配一個新的頁全局目錄(PGD)。
extern void pgd_free(struct mm_struct *mm, pgd_t *pgd);//釋放之前分配的頁全局目錄(PGD)定義清理 PTE 表的函數(shù)
static inline void clean_pte_table(pte_t *pte)//清理與頁表項相關的緩存
{
clean_dcache_area(pte + PTE_HWTABLE_PTRS, PTE_HWTABLE_SIZE);//pte + PTE_HWTABLE_PTRS:計算要清理的緩存區(qū)域的起始地址,調(diào)用clean_dcache_area以清理指定區(qū)域的緩存。
}定義 PTE 分配函數(shù)
這段代碼展示了內(nèi)核如何為內(nèi)核空間和用戶空間分配頁表。
pte_alloc_one_kernel 函數(shù):為內(nèi)核分配一個新的頁表項(PTE)頁,使用 __pte_alloc_one_kernel 分配PTE,如果分配成功,調(diào)用 clean_pte_table 清理新分配的頁表,返回分配的PTE指針。
pte_alloc_one 函數(shù):為用戶空間分配一個新的頁表項頁,使用 __pte_alloc_one 分配一個頁,使用 GFP_PGTABLE_USER | PGTABLE_HIGHMEM 標志,如果分配失敗,返回 NULL,如果分配的頁不是高端內(nèi)存頁,調(diào)用 clean_pte_table 清理它,返回分配的頁。
clean_pte_table 函數(shù):用于初始化新分配的頁表,可能涉及清零或設置特定的初始值。
使用CONFIG_HIGHPTE 允許在某些架構(gòu)上將頁表存儲在高端內(nèi)存中,這可以節(jié)省低端內(nèi)存。
GFP_PGTABLE_USER 是一個特殊的分配標志,用于用戶空間頁表分配。
PageHighMem 檢查是否為高端內(nèi)存頁。
page_address 函數(shù):用于獲取頁的虛擬地址。
#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)核分配一個頁表項(PTE)
{
pte_t *pte = __pte_alloc_one_kernel(mm);//調(diào)用 __pte_alloc_one_kernel(mm) 分配一個頁表項。
if (pte)
clean_pte_table(pte);//如果分配成功,調(diào)用 clean_pte_table(pte) 清理頁表項相關的緩存。
return pte;//返回分配的頁表項指針。
}
#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)//為用戶空間分配一個頁表項(PTE),struct mm_struct *mm是指向當前進程的內(nèi)存管理結(jié)構(gòu)的指針。
{
struct page *pte;
pte = __pte_alloc_one(mm, GFP_PGTABLE_USER | PGTABLE_HIGHMEM);//分配一個頁表項,使用用戶空間分配標志和高內(nèi)存標志。
if (!pte)
return NULL;//如果分配失敗,返回 NULL。
if (!PageHighMem(pte))
clean_pte_table(page_address(pte));//如果分配的頁表項不在高內(nèi)存中,調(diào)用 clean_pte_table(page_address(pte)) 清理頁表項相關的緩存。
return pte;//返回分配的頁表項指針。
}定義 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)核如何管理多級頁表結(jié)構(gòu),主要定義了與 ARM 架構(gòu)相關的頁表分配操作,包括頁表的分配和釋放、頁表的填充等。通過這些定義,內(nèi)核可以正確地管理和操作 ARM 架構(gòu)的頁表。它是內(nèi)存管理子系統(tǒng)中關鍵的一部分,負責維護虛擬內(nèi)存到物理內(nèi)存的映射關系。
static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,pmdval_t prot)//填充給定的頁中間目錄(PMD),將其設置為指向特定的頁表項,pmd_t *pmdp指向要填充的 PMD 的指針,phys_addr_t pte是頁表項的物理地址,pmdval_t prot是PMD 的保護標志。
{
pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;//計算 pmdval,將頁表項地址加上 PTE_HWTABLE_OFF(頁表項的偏移量),并與保護標志進行按位或操作。
pmdp[0] = __pmd(pmdval);//將計算得到的 pmdval 轉(zhuǎn)換為 PMD 類型并賦值給 pmdp[0]。
#ifndef CONFIG_ARM_LPAE
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));//如果未啟用 ARM LPAE(大頁表擴展),則填充 pmdp[1],將 pmdval 加上 256 個頁表項的大小。
#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,將頁表項的物理地址和內(nèi)核頁表的保護標志 _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;//存儲用戶 PMD 表的保護標志。
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,將頁表的物理地址(通過 page_to_phys(ptep) 獲。┖捅Wo標志傳遞給它。
}2.arm架構(gòu)相關的兩級頁表操作源碼:arc/arm/include/asm/pgtable-2level.h
定義兩級頁表結(jié)構(gòu)
定義了兩級頁表結(jié)構(gòu)的相關常量和宏。
#define __PAGETABLE_PMD_FOLDED 1
#define PTRS_PER_PTE 512//每個頁表項(PTE)指向的條目數(shù)量,通常為 512。
#define PTRS_PER_PMD 1//每個頁中間目錄(PMD)指向的條目數(shù)量,通常為 1。
#define PTRS_PER_PGD 2048//每個頁全局目錄(PGD)指向的條目數(shù)量,通常為 2048。
#define PTE_HWTABLE_PTRS (PTRS_PER_PTE)//硬件表中每個頁表項的指針數(shù)量。
#define PTE_HWTABLE_OFF (PTE_HWTABLE_PTRS * sizeof(pte_t))//頁表項的偏移量,計算為頁表項數(shù)量乘以每個頁表項的大小。
#define PTE_HWTABLE_SIZE (PTRS_PER_PTE * sizeof(u32))//頁表項的總大小,計算為頁表項數(shù)量乘以每個頁表項的大小。
#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 的相關常量和宏。
#define L_PTE_VALID (_AT(pteval_t, 1) 定義 PUD 操作函數(shù)
這段代碼定義了一系列與頁表管理相關的內(nèi)聯(lián)函數(shù)和宏,特別是針對PUD(Page Upper Directory)和PMD(Page Middle Directory)級別的操作。這些定義通常用于ARM架構(gòu),特別是在不使用三級或四級頁表的情況下。
PUD(Page Upper Directory)相關函數(shù):
lhdm5run5i264076327515.png (96.1 KB, 下載次數(shù): 7)
下載附件
保存到相冊
lhdm5run5i264076327515.png
2024-10-12 00:57 上傳
PMD(Page Middle Directory)相關函數(shù):
dip3nwqmgw364076327615.png (97.2 KB, 下載次數(shù): 7)
下載附件
保存到相冊
dip3nwqmgw364076327615.png
2024-10-12 00:57 上傳
這段代碼針對的是一個簡化的頁表結(jié)構(gòu),可能是兩級頁表系統(tǒng)(PGD -> PMD -> PTE),PUD級別在這個架構(gòu)中被忽略或不存在,所有PUD相關操作都是空操作或返回固定值,PMD操作被定義為直接操作頁表條目,包括設置、清除、復制等,使用位操作來判斷PMD的狀態(tài)(如large、leaf、bad等),這是一種常見的優(yōu)化技術(shù),包含了一些架構(gòu)特定的操作,如flush_pmd_entry和clean_pmd_entry,用于確保頁表更改對硬件可見。
#ifndef __ASSEMBLY__
static inline int pud_none(pud_t pud)
{
return 0;//檢查給定的 PUD 是否為空。當前實現(xiàn)總是返回 0,表示 PUD 不為空。
}
static inline int pud_bad(pud_t pud)
{
return 0;//檢查給定的 PUD 是否無效。當前實現(xiàn)總是返回 0,表示 PUD 是有效的。
}
static inline int pud_present(pud_t pud)
{
return 1;//檢查給定的 PUD 是否存在。當前實現(xiàn)總是返回 1,表示 PUD 存在。
}
static inline void pud_clear(pud_t *pudp)
{
}//清除給定的 PUD。當前實現(xiàn)為空,表示沒有具體的清除邏輯。
static inline void set_pud(pud_t *pudp, pud_t pud)
{
}//設置給定的 PUD。當前實現(xiàn)為空,表示沒有具體的設置邏輯。
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 中提取物理頁框號,使用 pmd_val 獲取 PMD 的值,并與物理掩碼進行按位與操作。
#define pmd_large(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否為大頁
#define pmd_leaf(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否為葉節(jié)點。
#define pmd_bad(pmd)(pmd_val(pmd) & 2)//檢查 PMD 是否無效。
#define pmd_present(pmd)(pmd_val(pmd))//檢查 PMD 是否存在。
#define copy_pmd(pmdpd,pmdps) //復制 PMD 條目并刷新目標 PMD 條目。
do {
pmdpd[0] = pmdps[0];
pmdpd[1] = pmdps[1];
flush_pmd_entry(pmdpd);
} while (0)
#define pmd_clear(pmdp)//清除 PMD 條目,將其設置為無效,并調(diào)用 clean_pmd_entry 清理相關緩存。
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)//設置頁表項(PTE)并擴展其屬性
#define pmd_hugewillfault(pmd)(0)
#define pmd_thp_or_huge(pmd)(0)//檢查 PMD 是否會導致故障或是否為大頁。當前實現(xiàn)總是返回 0,表示不會導致故障或不是大頁。
#endif /* __ASSEMBLY__ */3.arm架構(gòu)相關的三級頁表操作源碼:arc/arm/include/asm/pgtable-3level.h
定義三級頁表結(jié)構(gòu)
定義了三級頁表結(jié)構(gòu)的相關常量和宏。
#define PTRS_PER_PTE 512//每個頁表項(PTE)指向的條目數(shù)量,通常為 512。
#define PTRS_PER_PMD 512//每個頁中間目錄(PMD)指向的條目數(shù)量,通常為 512。
#define PTRS_PER_PGD 4//每個頁全局目錄(PGD)指向的條目數(shù)量,通常為 4。
#define PTE_HWTABLE_PTRS (0)//每個頁表項的指針數(shù)量,這里設置為 0,表示沒有硬件表。
#define PTE_HWTABLE_OFF (0)//頁表項的偏移量,這里設置為 0。
#define PTE_HWTABLE_SIZE (PTRS_PER_PTE * sizeof(u64))//頁表項的總大小,計算為頁表項數(shù)量乘以每個頁表項的大小
#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 定義大頁相關常量
#define HPAGE_SHIFT PMD_SHIFT//將大頁的位移量定義為 PMD 的位移量。
#define HPAGE_SIZE (_AC(1, UL) 定義 Linux PTE
#define L_PTE_VALID (_AT(pteval_t, 1) 定義 PUD 操作函數(shù)
這段代碼主要定義了與 ARM 架構(gòu)相關的三級頁表操作,包括頁表的結(jié)構(gòu)、頁表項的定義、PUD 和 PMD 操作函數(shù)等。通過這些定義,內(nèi)核可以正確地管理和操作 ARM 架構(gòu)的三級頁表。
ngfjsubq0jx64076327716.png (55.15 KB, 下載次數(shù): 4)
下載附件
保存到相冊
ngfjsubq0jx64076327716.png
2024-10-12 00:57 上傳
lshim5w5dmv64076327816.png (102.21 KB, 下載次數(shù): 4)
下載附件
保存到相冊
lshim5w5dmv64076327816.png
2024-10-12 00:57 上傳
df0co35lq5h64076327916.png (55.42 KB, 下載次數(shù): 6)
下載附件
保存到相冊
df0co35lq5h64076327916.png
2024-10-12 00:57 上傳
#ifndef __ASSEMBLY__
#define pud_none(pud) (!pud_val(pud))//檢查 PUD 是否為空
#define pud_bad(pud) (!(pud_val(pud) & 2))//檢查 PUD 是否無效
#define pud_present(pud) (pud_val(pud))//檢查 PUD 是否存在
#define pmd_table(pmd) ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_TABLE)//檢查 PMD 是否指向頁表
#define pmd_sect(pmd) ((pmd_val(pmd) & PMD_TYPE_MASK) == PMD_TYPE_SECT)//檢查 PMD 是否指向段
#define pmd_large(pmd) pmd_sect(pmd)//檢查 PMD 是否為大頁
#define pmd_leaf(pmd) pmd_sect(pmd)//檢查 PMD 是否為葉節(jié)點
#define pud_clear(pudp) //清除 PUD,將其設置為無效,并清理相關緩存
do {
*pudp = __pud(0);
clean_pmd_entry(pudp);
} while (0)
#define set_pud(pudp, pud) //設置 PUD,并刷新相關條目
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 是否無效
#define copy_pmd(pmdpd,pmdps) //復制 PMD 條目并刷新目標 PMD
do {
*pmdpd = *pmdps;
flush_pmd_entry(pmdpd);
} while (0)
#define pmd_clear(pmdp) //清除 PMD,將其設置為無效,并清理相關緩存
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 中提取物理頁框號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__ */四、示例實踐(1)代碼功能
編寫一個內(nèi)核模塊示例來演示如何在內(nèi)核中創(chuàng)建一個頁表映射。
(2)實現(xiàn)步驟
a.編寫內(nèi)核模塊代碼。
b.編譯內(nèi)核模塊。
c.加載內(nèi)核模塊。
d.檢查內(nèi)核日志以分析結(jié)果。
e.卸載內(nèi)核模塊。
(3)代碼實現(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;
// 分配一個物理頁
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);
// 釋放物理頁
__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é)果
paup4ajpv0m64076328016.png (21.41 KB, 下載次數(shù): 4)
下載附件
保存到相冊
paup4ajpv0m64076328016.png
2024-10-12 00:57 上傳
卸載內(nèi)核模塊
sudo rmmod pagetable_example分析結(jié)果
物理地址:0x199fe000 是分配的物理頁的地址。
虛擬地址:000000007e626547 是映射到內(nèi)核虛擬地址空間的地址。
數(shù)據(jù):Hello, kernel page! 是寫入和讀取的測試數(shù)據(jù)?偨Y(jié)
這個例子展示了如何在內(nèi)核中分配一個物理頁,將其映射到內(nèi)核虛擬地址空間,進行讀寫操作,然后取消映射并釋放物理頁。
五、心得體會通過對這一部分的學習,我真切的感受到了線性地址和物理地址的映射關系、內(nèi)存空間的分配,以及在內(nèi)存中讀寫信息的過程,對內(nèi)核也有了進步一的認識,Linux內(nèi)核頁表映射確實是一個深入理解操作系統(tǒng)內(nèi)存管理機制的重要過程。此次分析的內(nèi)核源碼為Linux6.8.0。
猜你喜歡:
WiFi6+藍牙+星閃,三合一開發(fā)板,真香!
Github上熱門 C 語言項目匯總!
嵌入式,可測試性軟件設計!
一些低功耗軟件設計的要點!
嵌入式 C 保護結(jié)構(gòu)體的方式
實用 | 10分鐘教你通過網(wǎng)頁點燈
談談嵌入式軟件的兼容性!
分享一個嵌入式代碼生成器設計思路!
點擊閱讀原文,查看更多分享。 |
|