内核深入学习3——分析ARM32和ARM64体系架构下的Linux内存区域示意图与页表的建立流程
内核深入学习3——ARM32/ARM64在Linux内核中的实现(2)
今天我们来讨论的是一个硬核的内容,也是一个老生常谈的话题——那就是分析ARM32和ARM64体系架构下的Linux内存区域示意图的内容。对于ARM64的部分,我们早就知道一个基本的事实——那就是我们的使用的是实际分配虚拟地址位之外的部分,全部置0和全部置1来区分这个地址的映射需要走的是哪个页表基地址寄存器。这个是我们上一个博客就谈论过的。
因此,我们需要做一个最基本的分析:那就是从0x 0000 0000 0000 0000到0x 0000 FFFF FFFF FFFF 的地方都是属于走用户态的页表基地址寄存器做映射的,反之,剩下的从0x FFFF 0000 0000 0000到0x FFFF FFFF FFFF FFFF的部分那就一定属于内核的部分。下面,我们就来仔细的讨论和分析。
需要注意的是——一个基本的事实是我们由于开放了内存的虚拟化,因此,我们能做到的是分析一个进程虚拟内存视图下的内存分配视图。用户态的部分肯定跑的就是自己的应用程序,我们的进程帮助我们维护一个非常经典的堆与栈环境,包括一些动态库文件加载啊等等地区。
+----------------------------+ 0x0000FFFFFFFFFFFF
| User Space (shared libs) |
| Heap, Stack, mmap() |
| Program text / data |
+----------------------------+ 0x0000000000400000
| NULL (reserved) |
+----------------------------+ 0x0000000000000000
当然这里的NULL就是一种检测践踏的地址,比如说,当我们访问空指针的时候,我们的操作系统会向进程发送非法地址访问的异常问题。这里就是这样规划的。当然,更加具体的,那就是栈放在上面,堆放在下面。这样的规划。
但是我们有时候一般更关心的是内核的一些事情,比如说我们的内核的地址是如何排布的。注意到的是——内核的排布中,我们的页表映射是完全线性的。这样的映射是简单的——那就是直接对我们的虚拟地址访问加上偏移量直接得到真实的物理地址
我们现在就得到了最完备的视图。
+----------------------------+ 0xFFFFFFFFFFFFFFFF
| Linear mapping |
+----------------------------+ 0xFFFFFF8000000000 <-- PAGE_OFFSET
| vmemmap area |
+----------------------------+
| PCI IO Area |
| |
+----------------------------+
| Fixmap (Fixed mappings) |
+----------------------------+ <-- VMALLOC_END
| Other VMALLOC |
| KImage Area |
+----------------------------+ <-- VMALLOC_START, KIMAGE_START
| Modules Area (128MB) |
+----------------------------+
| |
| Linear Mapping (DRAM) |
| Direct physical mapping |
ARM32的分析
ARM32的分布上,则是遵循着默认的比例用户态内存3G:内核内存1G的划分,由于分析上完全镜像,这里直接给出视图
ARM32 Linux Kernel Virtual Address Layout (i.MX6ULL, 3G/1G split)+----------------------------+ 0xFFFF0000
| CPU vectors (vectors page)|
+----------------------------+ 0xFFFE0000
| Fixmap (cpu/mmio/etc) |
+----------------------------+
| ioremap / consistent mem |
| Dynamically mapped I/O |
+----------------------------+
| vmalloc area |
| kmalloc large objects |
+----------------------------+
| Module code/data (rare) |
+----------------------------+
| |
| Lowmem (Linear mapping) | <-- 线性映射物理内存
| phys 0x00000000 - ~DDR |
| |
+----------------------------+ 0xC0000000 (PAGE_OFFSET)↑ 内核空间 (1GB) ↑
--------------------------------------------------↓ 用户空间 (3GB) ↓+----------------------------+ 0xBFFFFFFF
| |
| User stack, mmap, heap |
| Shared libs, ELF .text |
| |
+----------------------------+ 0x00000000
从运行初始到桥接给Linux的Memory Management 子系统的流程
我们后面就会知道,Linux的运行的时候的环境还是主要交给我们的Memory Management子系统来管理的,为此,为了建立起来内存管理子系统,我们首先需要一个保证可用的页表。这个页表的建立将会保证我们平滑的从开机到内核运行的流程。让我们看看这是如何做到的。
我们先从 ARM64 架构说起。在 Linux 内核启动时,BootLoader 会将内核镜像加载到内存中某个偏移位置,例如 0x80000,然后跳转到内核的启动入口。早期初始化代码(位于 arch/arm64/kernel/head.S)会完成最初的页表搭建。这一过程包括为线性映射区域建立页表,也就是我们提到的 PAGE_OFFSET 开始处映射物理地址0的那一大片虚拟空间。除此之外,还包括设置内核本身所占用的代码段、数据段以及设备树、堆栈等的映射。这时 MMU 尚未打开,因此这张最早的页表是通过一段静态结构预先构建出来的,结构清晰,从顶层的 PGD 开始(在ARM64中为4级页表:PGD → PUD → PMD → PTE),每一级都是标准的512项表项,页大小通常是4KB。Linux 内核用 __create_pgd_mapping
和 create_mapping
函数将物理内存与虚拟地址建立映射,最终通过 enable_mmu
指令将 MMU 打开。之后,整个世界就不再是线性的物理世界,而是由虚拟地址主导的内存空间。
而在 ARM32 下,情况要略为复杂也更“传统”一些。ARM32 架构支持两级页表,也就是 PGD 和 PTE,没有中间的 PUD 或 PMD 层。每个进程有自己的一套页目录,但内核部分是共享的。在启动初期,同样由 BootLoader 加载内核到内存,然后跳转到 __start
函数(通常位于 head.S 中)。早期初始化代码中,通过对页表结构的手工设置,构造出一张最小页表,这张表只映射几个关键区域:内核的物理地址、页表本身所在位置、异常向量表位置、早期的串口输出地址(用于 earlyprintk)等。ARM32 的页表结构可以是 coarse 页表(分页大小4KB)或 section(1MB)映射。在 Linux 内核中,为了加快 early boot 的速度,通常采用 1MB 的 section 映射将内核整体映射进虚拟地址空间。这些页表使用内核提供的 create_mapping
、setup_pagetables
等函数来初始化,在 MMU 打开后,就从 __mmap_switched
跳入真正的 C 语言环境,开始调用 start_kernel
。
在内核镜像执行的早期,也就是 head.S
阶段,Linux 还处于纯物理地址运行状态。此时会调用 __create_page_tables
,它是 Linux ARM64 平台用来构建初始页表的关键函数,定义在 arch/arm64/mm/mmu.c
中。该函数会为以下几个区域建立映射:内核镜像本身所在的 .text
、.data
、.bss
段;设备树 blob 所在的物理地址;早期内核堆栈;内核页表自己本身所占用的空间。它调用的底层函数就是 create_mapping
,而 create_mapping
则调用 __create_pgd_mapping
。
__create_pgd_mapping
的逻辑是:从顶层的 PGD 开始,递归分配每一层页表,并填入合适的描述符,最终走到最低层(PTE 或 PMD),填入物理地址并设置页面属性。ARM64 支持 section(大页)映射,如果映射的地址范围是对齐的并且足够大(例如 2MB),就会使用 block 映射以节省页表层级。这种映射结构使得在启动初期建立映射的页表更紧凑、更高效,且符合硬件 MMU 格式。
ARM64 的早期页表是静态分配的,这些页表空间在 idmap_pg_dir
、early_pg_dir
等变量中定义为静态数组,位于 bss
区域,并放在链接脚本指定的位置上。它们使用 __attribute__((aligned(4096)))
保证页表对齐,之后在启用 MMU 之前就已经准备完毕。完成初步映射后,enable_mmu()
函数会写入 MAIR、TCR、TTBR 等寄存器,正式打开 MMU。此时代码运行空间就从物理地址切换到了虚拟地址,内核开始使用它设置好的虚拟内存空间继续运行。
相比之下,ARM32 下的页表建立逻辑显得更加朴素也略显散乱。它的入口是 setup_pagetables
,定义在 arch/arm/mm/mmu.c
中。这个函数的作用是为 0xC0000000
开始的内核虚拟地址映射一段线性空间,用于映射内核映像和其它早期内存区域。它采用的是一级页表 section 映射(每项映射 1MB),通过一个宏 SECTION_ENTRY
来填充页表项:这类表项结构简单,只要填入物理地址与页面属性即可。ARM32 没有复杂的页表层级,页表项直接从 PGD 指向 PTE 或者 section entry,所以建立页表的函数只需要一次映射,不需要递归分配子表。
在 setup_pagetables
中,会依次映射:
0xC0000000
起始的 lowmem 区,对应物理地址0x00000000
;- 内核映像所在地址;
- 页表本身所在的物理地址,保证页表可访问;
- 异常向量表地址(可能在
0xFFFF0000
); - 一些 I/O 设备的 MMIO 区,如串口或调试接口。
ARM32 页表是以一个 16KB 的 swapper_pg_dir
作为根目录构建的,这个数组位于内核的 .bss
区域。在 paging_init()
和 mem_init()
中,这些早期页表会被完善,最终转交给内核内存子系统去接管。
和 ARM64 一样,ARM32 也需要调用类似 set_cr()
的函数设置 MMU 寄存器,启用页表后,CPU 就从使用物理地址跳转到使用虚拟地址运行。后续的所有访问,包括函数调用、堆栈操作、C 函数执行等,都会经过刚刚建立好的页表。
前面提到,__create_pgd_mapping
构建的是早期静态页表。这些页表只能覆盖有限的地址范围,比如内核代码段、早期堆栈和设备树所需空间。接下来,内核会进入 C 函数 start_kernel()
,正式开始 Linux 的各个子系统初始化。在这个函数中,setup_arch()
会负责调用 paging_init()
,完成整个内核空间页表的建立。
paging_init()
位于 arch/arm64/mm/mmu.c
中,它的职责是“真正地初始化整个内核所需的页表结构”,包括 vmalloc 区、fixmap 区、module 区、线性映射区等。在这个函数中,Linux 会依赖一个叫 memblock
的框架,它是启动阶段使用的“临时内存分配器”,以简易链表方式记录哪些物理内存是保留的,哪些是空闲的。内核使用 memblock_alloc()
从早期页表所映射的空间中分配内存,用来动态分配更多页表结构(例如 PGD、PUD、PMD、PTE 的页),扩展页表覆盖的范围。这里会用到 map_kernel_range()
和 create_mapping()
等函数,在多个区域建立新的虚拟地址到物理地址的映射。
比如,vmalloc 区的起始地址是 VMALLOC_START
,它不具备连续的物理页,因此不能用 section 映射,只能用标准 4KB 页和 PTE 映射,因此页表必须分层构建。Fixmap 区则用于映射早期调试设备,如 earlyprintk 的 UART。还有 vmemmap 区,它用于为系统中每一页 RAM 创建一个 struct page
结构,这些结构本身也需页表映射才能使用。整个过程依赖 early_pgtable_alloc()
从 memblock 中临时分配内存页,创建这些表项。
一旦页表建完,paging_init()
的尾声就会调用 mem_init()
或 mem_init_print_info()
,这些函数会清理早期的临时映射(如 identity map)、释放保留内存,并初始化伙伴系统。到此,系统便从“硬编码的页表”过渡到了“完全运行时可分配的动态页表”。
ARM32 的路径和逻辑虽然相似,但细节上有不少不同。它同样在 start_kernel()
中通过 setup_arch()
→ paging_init()
开始切换到正式的内存管理模式。在 ARM32 中,页表仍是 2 级结构,但 Linux 需要对 0xC0000000
开始的内核虚拟地址空间建立映射,并补全早期 section 映射未覆盖的区域。比如早期的页表只映射了内核镜像,那么此时就会补上 vmalloc
区、模块区、pkmap
区(用于高端内存映射)等页表项。这一过程主要通过 map_lowmem()
、mem_init()
、alloc_init_pte()
等函数完成。ARM32 也使用 memblock,但 memblock 的用法和粒度比 ARM64 更“紧凑”,ARM32 的页表使用空间更小,所以内存分配压力相对较低。
一个非常关键的转折点是:伙伴系统(buddy system)的初始化。无论 ARM64 还是 ARM32,在完成页表后,mem_init()
会将早期 memblock
中标记为“可用”的物理内存,正式释放给伙伴系统,让它成为 __get_free_pages()
和 kmalloc()
可使用的内存池。从这时起,整个系统的内存空间便不再是“只读映射”或“静态页表”,而是完全动态可分配的现代虚拟内存空间。值得一提的是:ARM64 的页表一旦建好,未来不会再频繁修改,而 ARM32 有些设备的页表仍需要在运行时动态更新,比如高端内存(highmem)页面就需要不断更新 kmap 区域的页表项。此外,两者都在页表项中设置内存属性,如缓存策略、可执行位、只读位等,以确保数据段和代码段的正确访问权限。
相关文章:
内核深入学习3——分析ARM32和ARM64体系架构下的Linux内存区域示意图与页表的建立流程
内核深入学习3——ARM32/ARM64在Linux内核中的实现(2) 今天我们来讨论的是一个硬核的内容,也是一个老生常谈的话题——那就是分析ARM32和ARM64体系架构下的Linux内存区域示意图的内容。对于ARM64的部分,我们早就知道一个基本的…...
MapReduce基本介绍
核心思想 分而治之:将大规模的数据处理任务分解成多个可以并行处理的子任务,然后将这些子任务分配到不同的计算节点上进行处理,最后将各个子任务的处理结果合并起来,得到最终的结果。 工作流程 Map 阶段: 输入数据被…...

屏幕与触摸调试
本章配套视频介绍: 《28-屏幕与触摸设置》 【鲁班猫】28-屏幕与触摸设置_哔哩哔哩_bilibili LubanCat-RK3588系列板卡都支持mipi屏以及hdmi显示屏的显示。 19.1. 旋转触摸屏 参考文章 触摸校准 参考文章 旋转触摸方向 配置触摸旋转方向 1 2 # 1.查看触摸输入设备 xinput…...

使用 百度云大模型平台 做 【提示词优化】
1. 百度云大模型平台 百度智能云千帆大模型平台  平台功能:演示了阿里云大模型的百炼平台,该平台提供Prompt工程功能,支持在线创建和优化Prompt模板模板类型:平台提供多种预制模板,同时也支持用户自定义…...
C 语言_常见排序算法全解析
排序算法是计算机科学中的基础内容,本文将介绍 C 语言中几种常见的排序算法,包括实现代码、时间复杂度分析、适用场景和详细解析。 一、冒泡排序(Bubble Sort) 基本思想:重复遍历数组,比较相邻元素,将较大元素交换到右侧。 代码实现: void bubbleSort(int arr[], i…...

IJCAI 2025 | 高德首个原生3D生成基座大模型「G3PT」重塑3D生成的未来
国际人工智能联合会议(IJCAI)是人工智能领域最古老、最具权威性的学术会议之一,自1969年首次举办以来,至今已有近六十年的历史。它见证了人工智能从萌芽到蓬勃发展的全过程,是全球人工智能研究者、学者、工程师和行业专…...

Samtec助力电视广播行业
【摘要前言】 现代广播电视技术最有趣的方面之一就是界限的模糊。过去,音频和视频是通过射频电缆传输的模拟技术采集的,而现在,数字世界已经取代了模拟技术。物理胶片和磁带已让位于数字存储设备和流媒体。 在这个过程中,连接器…...

密码学--仿射密码
一、实验目的 1、通过实现简单的古典密码算法,理解密码学的相关概念 2、理解明文、密文、加密密钥、解密密钥、加密算法、解密算法、流密码与分组密码等。 二、实验内容 1、题目内容描述 ①随机生成加密密钥,并验证密钥的可行性 ②从plain文件读入待…...
生成式图像水印研究综述
生成式图像水印研究综述 一、引言二、生成式图像水印研究背景三、生成式图像水印算法研究进展3.1 基于流模型的方案3.2 基于生成对抗网络的方案3.3 基于扩散模型的方案3.3.1 修改图像数据3.3.2 调整生成模型3.3.3 修改隐变量空间四、算法的性能与评价指标五、常用数据集六、本章…...
TCP协议详细讲解及C++代码实例
目录 一. TCP协议详细讲解及C代码实例1、TCP协议概述2、TCP通信流程1) 三次握手2) 数据传输3) 四次挥手 3、关键点解析1) 套接字创建2) 三次握手实现3) 数据传输4) 四次挥手实现 4、TCP与UDP对比 一. TCP协议详细讲解及…...
深度剖析:Vue2 项目兼容第三方库模块格式的终极解决方案
当我们为 Vue2 项目引入某些现代 JavaScript 库时,常常会遇到这样的报错: error in ./node_modules/some-lib/lib/index.mjs Cant import the named export xxx from non EcmaScript module这类问题的本质是模块格式的世纪之争 —— ES Moduleÿ…...
APISQL免费版安装教程(视频)
APISQL 一款通用的API开发管理软件,支持将主流数据库中的表、视图、SQL语句、存储过程等快速封装为标准的 RESTful API,支持多种安全认证方式和可视化管理界面。适用于接口开发、系统集成、数据共享等场景。 支持主流数据库的表、视图、自定义函数、存储…...

SpringBoot整合MQTT实战:基于EMQX实现双向设备通信(附源码)
简言: 在万物互联的时代,MQTT协议凭借其轻量级、高效率的特性,已成为物联网通信的事实标准。本教程将带领您在Ubuntu系统上搭建EMQX 5.9.0消息服务器,并使用Spring Boot快速实现两个客户端的高效通信。通过本指南,您将…...

从零开始掌握FreeRTOS(2)链表之节点的定义
目录 节点 节点定义 节点实现 根节点 根节点定义 精简节点定义 根节点实现 在上篇文章,我们完成了 FreeRTOS 的移植。在创建任务之前,我们需要先了解FreeRTOS的运转机制。 FreeRTOS是一个多任务系统,由操作系统来管理执行每个任务。这些任务全都挂载到一个双向循…...
Java的While循环写的出票简单程序
import java.util.Scanner;public class Hello {public static void main(String[] args) {Scanner in new Scanner(System.in);int balance 0;while(true){System.out.print("请投币: ");int amount in.nextInt();balance balance amount;if(balance >10 )…...
详解Windows(十一)——网络连接设置
Windows网络连接设置完全指南 1. Windows网络连接基础 网络连接类型 有线连接: 通过网线将电脑连接到路由器或调制解调器优点:连接稳定,速度快,延迟低适合:需要高速稳定网络的场景,如游戏、大文件下载、…...
多线程爬虫语言选择与实现
之前文中有人提到:想要一个简单易用、能快速实现多线程爬虫的方案,而且目标是小网站,基本可以确定对反爬虫措施要求不高,这些就比较简单了。 以往我肯定要考虑常见的编程语言中哪些适合爬虫。Python、JavaScript(Node…...

【数据结构】——双向链表
一、链表的分类 我们前面学习了单链表,其是我们链表中的其中一种,我们前面的单链表其实全称是单向无头不循环链表,我们的链表从三个维度进行分类,一共分为八种。 1、单向和双向 可以看到第一个链表,其只能找到其后一个…...
AI助力:零基础开启编程之旅
一、代码调试 三步解决BUG 1. 错误信息翻译 指令模板: 错误诊断模式我遇到【编程语言】报错“粘贴报错信息“ 请: 用小白能懂的话解释问题本质标注可能引发该错误的三个场景给出最可能的修复方案和其他备选方案 2. 上下文分析 进阶指令 结合上下文代…...

mybatis中${}和#{}的区别
先测试,再说结论 userService.selectStudentByClssIds(10000, "wzh or 11");List<StudentEntity> selectStudentByClssIds(Param("stuId") int stuId, Param("field") String field);<select id"selectStudentByClssI…...
【计算机组成原理】第二部分 存储器--分类、层次结构
文章目录 分类&层次结构0x01 分类按存储介质分类按存取方式分类按在计算机中的作用分类 0x02 层次结构 分类&层次结构 0x01 分类 按存储介质分类 半导体存储器磁表面存储器磁芯存储器光盘存储器 按存取方式分类 存取时间与物理地址无关(随机访问&#…...

抗量子计算攻击的数据安全体系构建:从理论突破到工程实践
在“端 - 边 - 云”三级智能协同理论中,端 - 边、边 - 云之间要进行数据传输,网络的安全尤为重要,为了实现系统总体的安全可控,将构建安全网络。 可先了解我的前文:“端 - 边 - 云”三级智能协同平台的理论建构与技术实…...
正则表达式: 从基础到进阶的语法指南
正则表达式语法详解 前言一、基础概念二、基础元字符2.1 字符匹配2.2 字符类2.3 预定义字符类 三、重复匹配3.1 贪婪与非贪婪匹配3.2 精确重复匹配 四、边界匹配4.1 行首与行尾匹配4.2 单词边界匹配 五、分组与引用5.1 分组5.2 反向引用5.3 命名分组 六、逻辑运算符6.1 或运算 …...

uniapp|实现手机通讯录、首字母快捷导航功能、多端兼容(H5、微信小程序、APP)
基于uniapp实现带首字母快捷导航的通讯录功能,通过拼音转换库实现汉字姓名首字母提取与分类,结合uniapp的scroll-view组件与pageScrollTo API完成滚动定位交互,并引入uni-indexed-list插件优化索引栏性能。 目录 核心功能实现动态索引栏生成联系人列表渲染滚动定位联动性…...

【Linux】基础IO(二)
📝前言: 上篇文章我们对Linux的基础IO有了一定的了解,这篇文章我们来讲讲IO更底层的东西: 重定向及其原理感受file_operation文件缓冲区 🎬个人简介:努力学习ing 📋个人专栏:Linux…...
SpringBoot异步处理@Async深度解析:从基础到高阶实战
一、异步编程基础概念 1.1 同步 vs 异步 特性同步异步执行方式顺序执行,阻塞调用非阻塞,调用后立即返回线程使用单线程完成所有任务多线程并行处理响应性较差,需等待前任务完成较好,可立即响应新请求复杂度简单直观较复杂&#…...

【生存技能】ubuntu 24.04 如何pip install
目录 原因解决方案说明关于忽略系统路径 在接手一个新项目需要安装python库时弹出了以下提示: 原因 这个报错是因为在ubuntu中尝试直接使用 pip 安装 Python 包到系统环境中,ubuntu 系统 出于稳定性考虑禁止了这种操作 这里的kali是因为这台机器的用户起名叫kali…...

SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!
SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁! 目录 SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于SHAP分析…...
Tcp 通信简单demo思路
Server 端 -------------------------- 初始化部分 ------------------------------- 1.创建监听套接字: 使用socket(协议家族,套接字的类型,0) 套接字类型有 SOCK_STREAM:表示面向连接的套接字(Tcp协议)&…...
MySQL 8.0安装(压缩包方式)
MySQL 8.0安装(压缩包方式) 下载安装包并解压 下载 https://dev.mysql.com/downloads/mysql/可关注“后端码匠”回复“MySQL8”关键字获取 解压(我解压到D:\dev\mysql-8.4.5-winx64目录下) 创建mysql服务 注意,这步之前一定要保证自己电…...