RISCV Linux 虛擬內存精講系列三 -- setup_vm()

? ? ? ? 在 Linux 使用虛擬地址前,需要先配置頁表,這就是 setup_vm() 的作用。然而,Linux 的頁表配置,并不是一次過完成的,分了兩個階段,如下:

? ? ? ? 在 setup_vm() 中,主要初始化了:

1.?struct kernel_mapping kernel_map __ro_after_init;

2.?pgd_t early_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE);

3.?pgd_t trampoline_pg_dir[PTRS_PER_PGD] __page_aligned_bss;

? ? ? ? 其中,early 指的是 early boot 階段,即 完整的虛擬頁表還沒有配置完成,即 swapper_pg_dir 還未可用。

? ? ? ? 在 linux/arch/riscv/mm/init.c 中,查看 setup_vm() 代碼,可以直觀地看出 kernel_map?各成員變量的賦值,如下:

kernel_map.virt_addr = KERNEL_LINK_ADDR;
kernel_map.phys_addr = (uintptr_t)(&_start);
kernel_map.size = (uintptr_t)(&_end) - kernel_map.phys_addr;
kernel_map.va_pa_offset = PAGE_OFFSET - kernel_map.phys_addr;
kernel_map.va_kernel_pa_offset = kernel_map.virt_addr - kernel_map.phys_addr;

????????通過 GDB,可以查看賦值后的結果,如下:

? ? ? ? 對應的結構定義如下:

1. virt_addr 是 kernel 鏈接地址,也是 kernel 初始地址。

2. phys_addr 是 kernel 的物理內存地址。

3. size 是 kernel 的大小。

? ? ? ? 接下來,就在?early_pg_dir 里配置?fixmap 。

? ? ? ? 也就是,從create_pgd_mapping() 的聲明可以看出,

void __init create_pgd_mapping(pgd_t *pgdp,uintptr_t va, phys_addr_t pa,phys_addr_t sz, pgprot_t prot);

????????該語句的作用是,在根頁表 early_pg_dir 里,

1. 配置一項 PTE;

2. 用于映射?FIXADDR_START (VA)到?fixmap_pgd_next(PA);

3. 其大小為?PGDIR_SIZE(1GB);

4. PTE屬性為?PAGE_TABLE。

? ? ? ? 其定義如下,是用于創建首級頁表(Page Global Directory, pgd):

void __init create_pgd_mapping(pgd_t *pgdp,uintptr_t va, phys_addr_t pa,phys_addr_t sz, pgprot_t prot)
{// 訪問地址:// 如果未啟動 MMU,則是物理地址// 如果啟動MMU,則是虛擬地址// pdg_t *pgdp 為訪問地址// 在 setup_vm() 中 pgdp, pa 為物理地址,通過 PC 相對尋址,給入// 下一級頁表的訪問地址pgd_next_t *nextp;// 下一級物理地址phys_addr_t next_phys;// 當前頁表索引號,即根頁表 VPN,// 給定 Sv39,三級頁表,VA = VPN[2](9) | VPN[1](9) | VPN[0](9) | OFFSET(12)// pgd_idx = VA.VPN[2]uintptr_t pgd_idx = pgd_index(va);// 如果映射大小為 1GB,即 PGDIR_SIZE(30 bits),那么只需在根頁表配置一項PTE即可if (sz == PGDIR_SIZE) {// 如果對應PTE未配置,則配置新值,即 PPN[2] == 0if (pgd_val(pgdp[pgd_idx]) == 0)// 即設置 PPN[2] = pa.PPN[2]pgdp[pgd_idx] = pfn_pgd(PFN_DOWN(pa), prot);return;}// 否則,需要在根頁表與下一級頁表都要配置PTE。// 也就是配置大小 小于 1GBif (pgd_val(pgdp[pgd_idx]) == 0) {// 如果根頁表對應的PTE未配置,則配置新值// 申請下一級頁表,pmd,并返回其物理地址。// 在 setup_vm 中,是返回 靜態的 early_pmd 的物理地址。next_phys = alloc_pgd_next(va);// 配置根頁表對應的 PTE,其 PPN = (next_pyhs >> 12)pgdp[pgd_idx] = pfn_pgd(PFN_DOWN(next_phys), PAGE_TABLE);// 獲取下一級頁表的虛擬地址// 在 setup_vm 中,物理地址等于虛擬地址// 即 early_pmd 的物理地址nextp = get_pgd_next_virt(next_phys);// 零化memset(nextp, 0, PAGE_SIZE);} else {// 如果根頁表對應的PTE已存在,則直接獲取下一級頁表的物理地址next_phys = PFN_PHYS(_pgd_pfn(pgdp[pgd_idx]));// 獲取下一級頁表的訪問地址nextp = get_pgd_next_virt(next_phys);}// 構建下一級頁表,如 pmd// 在 setup_vm 中,為 early_pmdcreate_pgd_next_mapping(nextp, va, pa, sz, prot);
}

? ? ? ? 對應的?create_pmd_mapping()? 也與?create_pgd_mapping() 類似,是用于創建倒數中級頁表(Page Medium Directory, pmd),定義如下:

static void __init create_pmd_mapping(pmd_t *pmdp,uintptr_t va, phys_addr_t pa,phys_addr_t sz, pgprot_t prot)
{pte_t *ptep;phys_addr_t pte_phys;uintptr_t pmd_idx = pmd_index(va);if (sz == PMD_SIZE) {if (pmd_none(pmdp[pmd_idx]))pmdp[pmd_idx] = pfn_pmd(PFN_DOWN(pa), prot);return;}if (pmd_none(pmdp[pmd_idx])) {pte_phys = pt_ops.alloc_pte(va);pmdp[pmd_idx] = pfn_pmd(PFN_DOWN(pte_phys), PAGE_TABLE);ptep = pt_ops.get_pte_virt(pte_phys);memset(ptep, 0, PAGE_SIZE);} else {pte_phys = PFN_PHYS(_pmd_pfn(pmdp[pmd_idx]));ptep = pt_ops.get_pte_virt(pte_phys);}create_pte_mapping(ptep, va, pa, sz, prot);
}

? ? ? ??create_pte_mapping() 是用于末級頁表(Page Table Entry,pte),與上述定義類似,如下:

static void __init create_pte_mapping(pte_t *ptep,uintptr_t va, phys_addr_t pa,phys_addr_t sz, pgprot_t prot)
{uintptr_t pte_idx = pte_index(va);BUG_ON(sz != PAGE_SIZE);if (pte_none(ptep[pte_idx]))ptep[pte_idx] = pfn_pte(PFN_DOWN(pa), prot);
}

? ? ? ? 如果,多于三級頁表,還有 PUD (Page Upper Directory), P4D(Page 4th Directory)。

即, 如果五級頁表的話,有 PGD --> P4D --> PUD --> PMD --> PTE 。

? ? ? ? 從上面定義來看,構建頁表項時,其大小只能是每級頁表中定義的一個,如 PGDIR_SIZE, PMD_SIZE, PAGE_SIZE。

? ? ? ? 梳理清楚?create_pgd_mapping(),?create_pmd_mapping(),?create_pte_mapping(),三個函數后,回到 setup_vm(),看看它是如何配置?early_pg_dir,?fixmap_pmd,?trampoline_pg_dir,trampoline_pmd的。

? ? ? ? 首先是在 early_pg_dir 根頁表中,配置 fixmap 的映射,其大小為 1GB,屬性為 頁表。也就是說,fixmap_pgd_next 為?fixmap_pmd,是一個中級頁表,其整個描述大小為 1GB = 512 × 2MB。

/* Setup early PGD for fixmap */create_pgd_mapping(early_pg_dir, FIXADDR_START,(uintptr_t)fixmap_pgd_next, PGDIR_SIZE, PAGE_TABLE);

? ? ? ? 然后是 fixmap_pmd 中級頁表中,配置?fixmap_pte 末級頁表,其大小為 2MB,屬性為 頁表。如下:

/* Setup fixmap PMD */create_pmd_mapping(fixmap_pmd, FIXADDR_START,(uintptr_t)fixmap_pte, PMD_SIZE, PAGE_TABLE);

? ? ? ? 同理,設置了?trampoline_pg_dir 與?trampoline_pmd,如下:

	/* Setup trampoline PGD and PMD */
create_pgd_mapping(trampoline_pg_dir, kernel_map.virt_addr,(uintptr_t)trampoline_pmd, PGDIR_SIZE, PAGE_TABLE);
create_pmd_mapping(trampoline_pmd, kernel_map.virt_addr,kernel_map.phys_addr, PMD_SIZE, PAGE_KERNEL_EXEC)

? ? ? ? 在配置trampoline_pmd頁表時,映射了 內核虛擬地址 到 內核的物理地址,大小為 2MB,屬性為內核執行頁。也就是說,可以通過虛擬地址 0xffffffff80000000 去訪問 物理地址 0x200200000,范圍是 2MB。這里覆蓋了 內核的前2MB內容。而內核大小大于2MB,因此,trampoline_pg_dir 的頁表配置并不能完全覆蓋整個內核所有的代碼段、數據段 及 設備樹。所以,只能作為臨時頁表來使用。即,其中 a2?為 early_pg_dir:

?????????early_pg_dir 頁表配置覆蓋了整個 linux kernel 的代碼段、數據段 及 設備樹,如下:

/** Setup early PGD covering entire kernel which will allow* us to reach paging_init(). We map all memory banks later* in setup_vm_final() below.*/create_kernel_page_table(early_pg_dir, true);/* Setup early mapping for FDT early scan */create_fdt_early_page_table(__fix_to_virt(FIX_FDT), dtb_pa);

? ? ? ? 對應的定義如下:


/** Setup a 4MB mapping that encompasses the device tree: for 64-bit kernel,* this means 2 PMD entries whereas for 32-bit kernel, this is only 1 PGDIR* entry.*/
static void __init create_fdt_early_page_table(uintptr_t fix_fdt_va,uintptr_t dtb_pa)
{
#ifndef CONFIG_BUILTIN_DTBuintptr_t pa = dtb_pa & ~(PMD_SIZE - 1);/* Make sure the fdt fixmap address is always aligned on PMD size */BUILD_BUG_ON(FIX_FDT % (PMD_SIZE / PAGE_SIZE));/* In 32-bit only, the fdt lies in its own PGD */if (!IS_ENABLED(CONFIG_64BIT)) {create_pgd_mapping(early_pg_dir, fix_fdt_va,pa, MAX_FDT_SIZE, PAGE_KERNEL);} else {create_pmd_mapping(fixmap_pmd, fix_fdt_va,pa, PMD_SIZE, PAGE_KERNEL);create_pmd_mapping(fixmap_pmd, fix_fdt_va + PMD_SIZE,pa + PMD_SIZE, PMD_SIZE, PAGE_KERNEL);}dtb_early_va = (void *)fix_fdt_va + (dtb_pa & (PMD_SIZE - 1));
#else/** For 64-bit kernel, __va can't be used since it would return a linear* mapping address whereas dtb_early_va will be used before* setup_vm_final installs the linear mapping. For 32-bit kernel, as the* kernel is mapped in the linear mapping, that makes no difference.*/dtb_early_va = kernel_mapping_pa_to_va(XIP_FIXUP(dtb_pa));
#endifdtb_early_pa = dtb_pa;
}
static void __init create_kernel_page_table(pgd_t *pgdir, bool early)
{uintptr_t va, end_va;end_va = kernel_map.virt_addr + kernel_map.size;for (va = kernel_map.virt_addr; va < end_va; va += PMD_SIZE)create_pgd_mapping(pgdir, va,kernel_map.phys_addr + (va - kernel_map.virt_addr),PMD_SIZE,early ?PAGE_KERNEL_EXEC : pgprot_from_va(va));
}

? ? ? ? 至此,setup_vm()的工作大致完成了,也就是將 kernel_map,?trampoline_pg_dir 及?early_pg_dir 配置完成,使得后續的 relocate 可以正常執行,切入虛擬地址訪問機制。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88140.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88140.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88140.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

創客匠人:解析創始人 IP 打造的底層邏輯與知識變現路徑

在數字經濟時代&#xff0c;創始人 IP 的價值被不斷放大&#xff0c;而知識變現作為 IP 商業閉環的核心環節&#xff0c;正成為無數創業者探索的方向。創客匠人深耕知識付費領域多年&#xff0c;見證了大量創始人從 0 到 1 打造 IP 并實現變現的全過程&#xff0c;其背后的邏輯…

Visual Studio 2022 MFC Dialog 添加Toolbar及Tips提示

主要步驟&#xff1a;在主框架類中添加消息處理函數聲明在 OnCreate 函數中啟用工具欄提示在消息映射中注冊 TTN_NEEDTEXT 消息使用 OnToolTipText 函數實現自定義提示文本1.在主程序的.h文件中加入afx_msg BOOL OnToolTipText(UINT id, NMHDR* pNMHDR, LRESULT* pResult); 2.在…

2025Q2大模型更新匯總(大語言模型篇)

摘要 2025年Q2大語言模型更新匯總&#xff1a; Qwen3&#xff0c;Deepseek-R1-0528&#xff0c;Doubao-Seed-1.6, MiniMax-M1, GPT4.1/O3/O4&#xff0c;Claude4/Gemini2.5 Qwen3 ? 開源MOE模型&#xff0c; ? MOE模型&#xff1a;Qwen3-235B-A22B&#xff0c;Qwen3-30B-…

【STM32】定時器中斷 + 含常用寄存器和庫函數配置(提供完整實例代碼)

通用定時器基礎知識 參考資料:STM32F1xx官方資料:《STM32中文參考手冊V10》-第14章通用定時器 通用定時器工作過程: 時鐘選擇 計數器時鐘可以由下列時鐘源提供: ① 內部時鐘(CK_INT) ② 外部時鐘模式1:外部輸入腳(TIx) ③ 外部時鐘模式2:外部觸發輸入(ETR) ④ 內部觸…

集群Redis

文章目錄前言一、Redis主從復制配置1.1.配置文件redis_master.conf,redis_slave.conf1.2.啟動服務1.3.檢查成果二、Redis集群配置2.1.服務器40.240.34.91集群配置2.2.其它服務器xxx.92,xxx.93集群配置2.3.啟動服務2.3.啟動集群服務2.4.檢查成果三、優劣四、結束前言 提示&…

ORA-600 kokiasg1故障分析---惜分飛

故障總結:客戶正常關閉數據庫,然后啟動報ORA-600 kokiasg1錯誤,通過對啟動分析確認是由于IDGEN1$序列丟失導致,修復該故障之后,數據庫啟動成功,但是后臺大量報ORA-600 12803,ORA-600 15264等錯誤,業務用戶無法登錄.經過深入分析,發現數據庫字典obj$中所有核心字典的序列全部被刪…

[RPA] 影刀RPA基本知識

1.應用的構成一個應用&#xff1a;由多條指令疊加組成一條指令代表了一個操作動作許多條指令按照一定的邏輯關系編排起來&#xff0c;就構成了一個應用(這里的應用可理解為軟件機器人RPA)一個應用 多個自動化指令的集合 2. 指令的一般構成在XXX對象上&#xff0c;對XXX元素執行…

pytest中測試特定接口

在pytest中只測試特定接口有以下幾種常用方法&#xff1a; 1. 通過測試函數名精確匹配 直接指定測試文件和函數名&#xff1a; pytest test_api.py::test_upload_image_with_library這將只運行test_api.py文件中名為test_upload_image_with_library的測試函數。 2. 使用關鍵字匹…

HMI圖形渲染優化:OpenGL ES與Vulkan的性能對比實戰

HMI 圖形渲染優化&#xff1a;OpenGL ES 與 Vulkan 的性能對比實戰**摘要想讓 HMI 界面的圖形渲染又快又流暢&#xff0c;卻在 OpenGL ES 和 Vulkan 之間糾結不已&#xff01;用 OpenGL ES&#xff0c;擔心性能不夠強勁&#xff0c;無法滿足復雜場景需求&#xff1b;選 Vulkan&…

Python數據分析基礎01:描述性統計分析

下一篇&#xff1a; 《Python數據分析基礎04&#xff1a;預測性數據分析》 《Python數據分析基礎03&#xff1a;探索性數據分析》 《python數據分析基礎02&#xff1a;數據可視化分析》 《Python數據分析基礎01&#xff1a;描述性統計分析》 描述性統計分析是統計學中最基…

成員不更新項目進度,如何建立進度更新機制

項目成員不及時更新進度的主要原因包括責任不明確、缺乏更新規則、溝通機制不暢、進度意識薄弱、工具使用不當等。其中尤其需要關注的是建立清晰的進度更新規則。明確規定成員應何時、如何、向誰匯報進度情況&#xff0c;使得項目的每項任務都有責任人和明確的更新頻率及形式&a…

JVM 整體架構詳解:線程私有與線程共享內存區域劃分

Java 虛擬機&#xff08;JVM&#xff09;作為 Java 程序運行的基礎&#xff0c;其內存模型和線程結構設計直接影響著程序的執行效率和穩定性。本文將從 線程是否共享 的角度出發&#xff0c;對 JVM 的整體內存結構進行清晰分類與簡明解析。一、JVM 內存區域劃分概覽 根據是否被…

【Linux庖丁解牛】— 庫的理解與加載!

1. 目標文件編譯和鏈接這兩個步驟&#xff0c;在Windows下被我們的IDE封裝的很完美&#xff0c;我們?般都是?鍵構建?常?便&#xff0c; 但?旦遇到錯誤的時候呢&#xff0c;尤其是鏈接相關的錯誤&#xff0c;很多?就束??策了。在Linux下&#xff0c;我們之前也學 習過如…

QML事件處理:鼠標、拖拽與鍵盤事件

在QML應用開發中&#xff0c;用戶交互是構建動態界面的核心。本文將全面解析QML中的三大交互事件&#xff1a;鼠標事件、拖拽事件和鍵盤事件&#xff0c;通過實際代碼示例展示如何實現豐富的用戶交互體驗。一、鼠標事件處理1. MouseArea基礎MouseArea是QML中處理鼠標交互的核心…

MySQL 8.0 OCP 1Z0-908 題目解析(20)

題目77 Choose the best answer. Which step or set of steps can be used to rotate the error log? ○ A) Execute SET GLOBAL max_error_count . ○ B) Rename the error log file on disk, and then execute FLUSH ERROR LOGS. ○ C) Execute SET GLOBAL log_error ‘’…

八股學習(四)---MySQL

一、MySQL如何進行SQL調優&#xff1f;我的回答&#xff1a;面試官好&#xff01;我想從SQL語句本身和數據庫結構兩方面來做MySQL的SQL調優。首先會優化SQL寫法&#xff0c;比如避免用SELECT *、減少子查詢嵌套&#xff0c;用JOIN代替&#xff0c;還有合理使用索引&#xff0c;…

華中科大首創DNN衍射量子芯片登《Science Advances》:3D打印實現160μm3高維邏輯門

01 前言華中科技大學王健/劉駿團隊在《Science Advances》發表突破性研究&#xff0c;利用飛秒激光三維打印技術&#xff0c;制造出全球首個聚合物基超緊湊高維量子光芯片。該芯片僅160微米見方&#xff08;約頭發絲直徑的1.5倍&#xff09;&#xff0c;卻實現了光子空間模式的…

【排序】插入排序

如果你已經對排序略知一二&#xff0c;現在正在復習排序的一些重點知識 ------------------------------------------------------------------------------------------------------------------------- 點贊收藏&#x1f308;&#xff0c;每天更新總結文章&#xff08;多以圖…

扣子Coze怎么模仿人類輸出(分段輸出)?

效果&#xff1a; 讓AI回復的更像人類 教程&#xff1a; 工作流&#xff1a; 假設大模型節點就是需要的回復&#xff0c;并且已經按句號&#xff08;。&#xff09;區別開每句話 后面連接一個 文本處理 節點&#xff0c;選擇“字符串分隔”&#xff0c;按“。”進行分割 分…

Android 應用開發 | 一種限制拷貝速率解決因 IO 過高導致系統卡頓的方法

文章目錄一、問題背景二、代碼實現一、問題背景 經常做 Android 應用的小伙伴應該會有經驗&#xff0c;就是如果應用在寫入文件的時候&#xff0c;即使寫文件的動作是在子線程&#xff0c;也會出現 UI 上的卡頓&#xff0c;這是因為文件的 IO 是由內核去完成的&#xff0c;此時…