在 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μm³高维逻辑门

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;此时…