1. 布局设计原因分析1.0 要点速览 (TL;DR)1.1 设计哲学:约束驱动的唯一解1.2 起始地址选择原因1.3 组件间距离设计:空洞就是保险1.4 异常向量表 0x87800020 地址原因1.5 布局优势:从原则反推价值1.6 常见陷阱与排错线索2. 内部存储器映射2.1 OCRAM 详细布局 (启动阶段)3. 外部DDR3内存映射 (512MB)3.1 ARM 物理地址空间划分3.2 为何选 0x800000003.3 参考依据3.4 小结4. U-Boot详细内存布局 (0x87800000-0x87FFFFFF)5. Linux 运行时内存布局说明5.1 物理地址 vs 虚拟地址5.2 内核物理内存分区 (Zone)5.3 内核虚拟地址布局 (1GB)5.3.1 vmalloc 与 ioremap 为什么共用一段虚拟地址第一性原理分析三个设计动机实战启示5.4 运行时主要分配器5.5 用户进程虚拟地址布局 (单进程 3GB)5.6 为什么 0x80000000 – 0x80080000 (512KB) 留空5.7 ARM32 → ARM64 内存模型迁移对照表
The memory system consists of these components:
- Level 1 cache—32 KB instruction, 32 KB data cache
- Level 2 cache—unified instruction and data (128 KB)
- On-Chip Memory:
- Boot ROM, including High-Assurance Boot (HAB, 96 KB)
- Internal fast access RAM (OCRAM, 128 KB)
- External memory interfaces:
- 16-bit LP-DDR2, 16-bit DDR3-400, and LV-DDR3-400
- 8-bit NAND-flash, including support for Raw MLC/TLC, 2 KB ,4 KB, and 8 KB page size, BA-NAND, PBA-NAND, LBA-NAND, OneNAND™, and others
- BCH ECC up to 40 bits
- 16-bit NOR flash
- 16-bit PSRAM, Cellular RAM
- Dual-channel/single-channel QuadSPI flash
这篇文档解决什么:嵌入式内存布局最难回答的不是"表长什么样",而是"为什么每个地址必须是这个值"。本文从硬件 / 工具链 / 运行时三组约束出发,推导 IMX6ULL 从 BootROM 到 Linux 运行时的完整内存布局,并对比 ARM64 的演进方向。
读完你能拿到:
- 看懂任意 ARM32 SoC 内存布局的方法论(不再只是背表)。
- 面试 / 排错时能说清每个地址的"因"而不只是"果"。
- 向 ARM64 迁移驱动时知道哪些假设会失效(见 §5.7 对照表)。
如何阅读:§1 建立设计哲学(强烈建议先读);§2–§4 是硬件 + 启动阶段的静态视图;§5 是 Linux 运行时动态视图 + ARM64 对照。时间紧张只读 §1.0 TL;DR + §1.6 常见陷阱表即可应急。
1. 布局设计原因分析
1.0 要点速览 (TL;DR)
核心思想:嵌入式启动期的内存布局不是"随便放",而是被硬件 / 工具链 / 运行时安全三组约束挤出来的唯一解。
三条铁律:
- 硬件决定起点——BootROM、OCRAM、DDR 窗口的物理地址都写死在 SoC RTL 里,软件不能挪。
- 阶段决定分区——SPL → U-Boot → Kernel → 用户态四个阶段互不干扰:每一阶段的代码 + 数据 + 堆栈必须独占一块不会被下一阶段覆盖的空间。
- 对齐与间距换稳健性——2GB / 1MB section / 32B 向量表对齐减少 MMU / 解码逻辑成本,大段保留区换 DMA / 解压 / 设备树修改的缓冲。
四个关键原则:
- 固定对齐(2GB 自然边界 / 1MB section / 32B 向量)
- 安全间距(组件间几 MB~几十 MB 的空洞,容忍溢出)
- 就近放置(BootROM 近 0、OCRAM 给 SPL、Kernel 在 DDR 低端、U-Boot 在 DDR 高端)
- 早晚分离(OCRAM = 早期;DDR 低端 = 中期;DDR 高端 = 运行时)
1.1 设计哲学:约束驱动的唯一解
不要把这张表理解成"工程师品味",它是一组约束方程的解。
约束来源 | 硬约束 | 软约束 |
硬件 (SoC) | 上电 PC = 0x00000000;DDR 窗口 = 0x80000000+;MMU 一级页表 1MB 对齐;异常向量 32B 对齐 | OCRAM 只有 128KB;DDR 实际容量 512MB |
工具链 / 协议 | Linux zImage 解压需偏移;DTB 要对齐;initramfs 自解析头 | U-Boot CONFIG_SYS_TEXT_BASE 默认值、bootm 预期地址 |
运行时安全 | DMA buffer 不能覆盖 kernel;栈不能溢出到 .bss;U-Boot 不能被解压到自身 | 调试期望固定地址;升级时镜像大小增长留 headroom |
所以下面每一个"为什么",都应该读成:在这些约束全部成立时,这个地址 / 这个间距是当前硬件下唯一合理的落点。
1.2 起始地址选择原因
BootROM
0x00000000 — 完全由硬件锁死- CPU 复位后 PC 硬连线到
0x00000000,第一条指令必须在这里。
- 厂商把 ROM 固化在低地址,任何改变都要改 SoC 硅版,软件层面零自由度。
- 反例:没有哪款 ARM Cortex-A 可以把复位向量挪到 DDR,因为复位时 DDR 控制器还没初始化。
OCRAM
0x00900000 — 启动早期唯一可用 RAM- DDR 控制器需要软件 (SPL) 跑一段 DDR PHY 校准后才能用,在那之前 CPU 能写的 RAM 只有 OCRAM。
- 128KB 容量是 NXP 在成本和 SPL 实际大小之间的折中:SPL Code + Stack + Heap + ROM 工作区刚好塞进 128KB(见 §2.1)。
- 地址
0x00900000并非随意——在低 2GB 片内资源区划里,为 OCRAM 专门留出一个 1MB 对齐窗口,避免和 AIPS 寄存器区 (0x02000000+) 冲突。
Kernel Image
0x80080000 — DDR 基址 + 512KB 头部偏移0x80000000由 MMDC 路由写死(已在 §3 展开),这是 DDR 的硬起点。
- 为什么 +512KB 偏移:
- ARM Linux zImage 的解压器会原地解压到紧邻位置,头部需要空间放解压缓冲和 stub。
- BootROM / SPL 返回时可能还在
0x80000000附近留有临时数据,避免踩踏。 - 刚好对齐 1MB section 边界 (
0x80080000可被0x100000整除的下一个位置是0x80100000,实际用0x80008000的 ARM 默认起点加 512KB =0x80080000给 defconfig 留空间),让 MMU 早期页表建起来零碎片。
U-Boot
0x87800000 — DDR 高端避让 + 对齐自然边界- 放在 DDR 低 128MB 边界上方(
0x87800000 = 0x80000000 + 120MB),确保下面的 119MB 都给 Kernel Image 用。
0x87800000本身是 8MB 对齐,方便 U-Boot 内部用 1MB/2MB section 建页表。
- 给 U-Boot 预留 8MB 是 NXP vendor defconfig 惯例:code + data + heap + env + stack + 命令缓冲累计 ≈ 7MB,留 1MB headroom 应付版本升级。
1.3 组件间距离设计:空洞就是保险
每一个间距都有明确的工程意义:
- Kernel ↔ DTB 之间 ~40MB:当你内核开了 KASAN / debug / 大驱动(如 GPU、ISP),vmlinux 可能从 ~8MB 膨胀到 30MB+。40MB 让你不用动 bootcmd。
- DTB ↔ Ramdisk 之间 ~16MB:U-Boot 的
fdt resize / fdt set会就地扩展 DTB;16MB 足够任何 overlay。
- Ramdisk ↔ U-Boot 之间 ~56MB:initramfs 可能从几 MB 涨到几十 MB(busybox + modules + firmware),同时给 DMA 传输留 headroom——U-Boot 加载镜像时 SDHC 的 DMA 窗口容易覆盖相邻区域。
反直觉的点:这些间距看似"浪费",但在 512MB 板子上只占 约 23%,换来的是 bootcmd 可复用、镜像升级不挪地址、OTA 脚本一份走天下——这才是嵌入式批量生产真正值钱的地方。
1.4 异常向量表 0x87800020 地址原因
依据 | 硬/软 | 如果违反 |
32 字节对齐 (ARM 要求) | 硬 | 触发 Undefined Instruction,U-Boot 无法启动 |
位于 DDR (非 OCRAM) | 软 | 跑在 OCRAM 要搬 128KB 限制;DDR 容量充足、cache 友好 |
远离内核 / 用户区 | 软 | 调试时不容易被误写;隔离让 fault 分析更容易 |
固定地址 | 软 | JTAG / T32 脚本写好 watchpoint 后可复用 |
紧贴 U-Boot .text 起点 | 软 | U-Boot 的 relocation 机制期望 vector + text 连续布局 |
关键理解:U-Boot 阶段的向量表并不等同于 Linux 内核的向量表(内核后来会把向量搬到
0xFFFF0000 高向量,见 §5.3)。两者分属不同运行阶段,互不冲突。1.5 布局优势:从原则反推价值
优势 | 具体体现 | 底层原则 |
启动效率 | OCRAM (SPL) → DDR (U-Boot) → DDR (Kernel) 一次性线性推进,没有二次搬移 | 早晚分离 + 就近放置 |
内存保护 | 组件间数十 MB 空洞 + 512KB 头部保留,镜像升级不越界 | 安全间距 |
扩展性 | kernel 119MB / rootfs 32MB / U-Boot 8MB 留有足够 headroom | 头部容量超配 |
调试友好 | 所有关键地址固定,T32/OpenOCD 脚本一份通吃全系列板 | 固定对齐 + 硬件决定起点 |
安全性 | BootROM 只读 + 低地址;OCRAM 断电丢失;Kernel 远离用户区 | 早晚分离 + 物理特性 |
生态一致性 | 与 i.MX6 / 8 系列 DDR 起点一致,NXP 工具链通用 | 硬件决定起点 |
1.6 常见陷阱与排错线索
跨 SoC 通用的完整排错速查卡(SPL / U-Boot / Kernel / 运行时 四阶段)已抽取为独立资产:ARM32 嵌入式启动排错速查卡。本节只保留与 IMX6ULL 内存布局直接相关的 6 条。
把这张表贴在工位上,能挡掉 90% 的布局类 bug:
症状 | 根因 | 排查方向 |
SPL 跑到一半挂住,串口无输出 | SPL 体积超 16KB,覆盖到 SPL stack | arm-none-eabi-size u-boot-spl 查段大小 |
Uncompressing Linux... 卡死 | 解压缓冲 0x80800000 被 initrd / DTB 覆盖 | 检查 bootm 参数地址是否互相重叠 |
U-Boot 启动 OK,启动内核后 hang | DTB 地址错误或 DTB 被内核镜像覆盖 | md.l 0x83000000 看 DTB magic 0xd00dfeed |
DMA 数据偶发损坏 | DMA buffer 落在 Ramdisk / Kernel 尾部 | 看 /proc/iomem • dma-ranges |
大版本升级后 bootcmd 挂了 | kernel 或 initramfs 膨胀超出预留间隔 | ls -l 镜像 + 对照 §1.3 空洞裕量 |
Kernel panic: "Unable to handle kernel paging request at virtual address 0xXXXX" | 驱动硬编码地址没用 ioremap,或 ioremap 后走出 vmalloc 区 | 查 /proc/vmallocinfo (§5.3.1) |
2. 内部存储器映射
起始地址 | 结束地址 | 容量 | 内容 | 说明 |
0x00000000 | 0x00017FFF | 96KB | BootROM Code | CPU上电后执行的固化代码 |
0x00900000 | 0x0091FFFF | 128KB | OCRAM (On-Chip RAM) | 内部高速缓存,启动时使用 |
2.1 OCRAM 详细布局 (启动阶段)
起始地址 | 结束地址 | 容量 | 内容 | 用途 |
0x00900000 | 0x00903FFF | 16KB | SPL Code | Secondary Program Loader |
0x00904000 | 0x00907FFF | 16KB | SPL Stack | SPL运行时栈空间 |
0x00908000 | 0x0091BFFF | 80KB | SPL Heap/BSS | SPL数据段和堆空间 |
0x0091C000 | 0x0091FFFF | 16KB | BootROM数据 | ROM代码工作区域 |
3. 外部DDR3内存映射 (512MB)
起始地址 | 结束地址 | 容量 | 内容 | 说明 |
0x80000000 | 0x8007FFFF | 512KB | 保留区域 | 系统保留,避免冲突 |
0x80080000 | 0x8077FFFF | ~119MB | Linux Kernel Image | 内核镜像加载区域 |
0x80800000 | 0x8087FFFF | 512KB | Kernel解压缓冲区 | 内核解压临时空间 |
0x83000000 | 0x8307FFFF | 512KB | Device Tree Blob | 设备树二进制文件 |
0x84000000 | 0x85FFFFFF | 32MB | Initial Ramdisk | 初始化RAM磁盘 |
0x87800000 | 0x87FFFFFF | 8MB | U-Boot运行区域 | 详见下表 |
0x88000000 | 0x9FFFFFFF | 384MB | Linux运行时内存 | 内核运行时动态分配 |
IMX6ULL 外部 DDR3 起始地址
0x80000000(即 2GB 偏移)由 SoC 硬件总线地址映射决定,不是软件可配置的。核心原因如下:3.1 ARM 物理地址空间划分
ARM Cortex-A7 使用 32 位物理地址(4GB 空间)。NXP 将这 4GB 划分为多个固定区域:
0x00000000 – 0x7FFFFFFF(低 2GB)→ 片内资源:BootROM、OCRAM、外设寄存器(AIPS/IP Bus)、GPU、PCIe 等
0x80000000 – 0xFFFFFFFF(高 2GB)→ MMDC(Multi-Mode DDR Controller)映射窗口,即外部 DDR
这是 SoC 内部总线互联矩阵(AXI interconnect)的硬连线路由规则,写死在 RTL 中。
3.2 为何选 0x80000000
- 对齐 2GB 自然边界。大功率对齐简化地址解码逻辑——只需看最高位 bit[31] 即可区分片内(0)和 DDR(1)。
- 为片上外设预留充足空间。IMX6ULL 有大量 IP 模块(USB、ENET、UART、GPIO、CSI、LCDIF……),寄存器地址密集分布在
0x02000000–0x02200000一带,加上 BootROM、OCRAM、GPU 等,低 2GB 刚好容纳。
- NXP i.MX 家族一致性。从 i.MX6Q/DL 到 i.MX6ULL 再到 i.MX8 系列,DDR 起始地址均为
0x80000000(或0x40000000于部分 i.MX8 型号),保持软件生态兼容。
3.3 参考依据
IMX6ULL Reference Manual (Chapter 2, Memory Map) 明确规定:
MMDC - DDR Controller:0x80000000 – 0xFFFFFFFF(up to 2 GB)
即使你的板子只焊了 512MB DDR3,地址窗口仍然从
0x80000000 开始,有效范围到 0x9FFFFFFF(512MB),高地址部分未映射。3.4 小结
因素 | 说明 |
硬件决定 | MMDC 端口在总线矩阵中路由到 bit[31]=1 的地址段 |
对齐优势 | 2GB 自然边界,解码仅需 1 bit |
生态兼容 | i.MX 全系列统一约定 |
不可修改 | 由 SoC RTL 固化,软件无法重映射 |
4. U-Boot详细内存布局 (0x87800000-0x87FFFFFF)
起始地址 | 结束地址 | 容量 | 内容 | 详细说明 |
0x87800000 | 0x8780001F | 32B | 异常向量表 | ARM异常向量表(8个向量×4字节) |
0x87800020 | 0x878003FF | 992B | 异常处理代码 | 各种异常的处理函数 |
0x87800400 | 0x8785FFFF | ~384KB | U-Boot主代码段(.text) | 主要功能代码 |
0x87860000 | 0x8787FFFF | 128KB | 只读数据段(.rodata) | 常量、字符串等 |
0x87880000 | 0x878BFFFF | 256KB | 数据段(.data) | 已初始化全局变量 |
0x878C0000 | 0x878FFFFF | 256KB | BSS段(.bss) | 未初始化全局变量 |
0x87900000 | 0x879FFFFF | 1MB | 堆空间(Heap) | 动态内存分配 |
0x87A00000 | 0x87BFFFFF | 2MB | 栈空间(Stack) | 函数调用栈 |
0x87C00000 | 0x87DFFFFF | 2MB | 环境变量区 | U-Boot环境变量存储 |
0x87E00000 | 0x87EFFFFF | 1MB | 设备树临时区 | 设备树修改缓冲区 |
0x87F00000 | 0x87FFFFFF | 1MB | 命令缓冲区 | 命令行解析和缓存 |
5. Linux 运行时内存布局说明
Linux 内核从
0x80080000 解压启动后,会接管整段 DDR3(0x80000000 – 0x9FFFFFFF,共 512MB)的内存管理。其中 0x88000000 – 0x9FFFFFFF(384MB)是运行时可动态分配的主力池,承载内核对象、用户进程、页缓存等。5.1 物理地址 vs 虚拟地址
IMX6ULL 跑 Linux (ARM 32-bit,默认 3G/1G 切分)时,地址空间是两套视角:
视角 | 范围 | 说明 |
物理地址 | 0x80000000 – 0x9FFFFFFF | MMDC 路由到 DDR3 的真实硬件地址 |
内核虚拟地址 | 0xC0000000 – 0xFFFFFFFF | 内核空间共 1GB;其中与物理内存等大的 512MB (lowmem) 做 1:1 线性映射,剩余给 vmalloc / ioremap / fixmap 使用 |
用户虚拟地址 | 0x00000000 – 0xBFFFFFFF | 每个用户进程独立的 3GB 空间 |
线性映射关系:
PAGE_OFFSET(0xC0000000) ↔ PHYS_OFFSET(0x80000000),即 内核虚拟地址 = 物理地址 + 0x40000000。澄清误区:"1GB 内核空间全部线性映射"是错的
- "线性映射"只要求
VA = PA + const,对实际存在的物理内存成立即可。
- IMX6ULL 物理内存只有 512MB,所以 lowmem 线性映射区只有 512MB(
0xC0000000 – 0xDFFFFFFF)。
- 1GB 内核空间 = 512MB lowmem 线性区 + ≈488MB vmalloc/ioremap 非线性区 + 顶部 fixmap 等。
- 对应不存在物理内存的虚拟地址,内核根本不建页表,访问直接 oops。
- 只有当物理内存 > ~760MB,lowmem 装不下时,才需要
ZONE_HIGHMEM+kmap()。IMX6ULL 远不到阈值,全部 RAM 都能被 lowmem 吃下。
5.2 内核物理内存分区 (Zone)
ARM 32-bit + 512MB DDR 的典型配置不启用 HIGHMEM,全部物理内存直接线性映射进内核空间:
Zone | 范围 (物理) | 用途 |
ZONE_DMA | 起始若干 MB | 兼容老外设的 DMA 分配 |
ZONE_NORMAL | 剩余全部 | 内核 / 用户的常规分配 |
ZONE_HIGHMEM | 不存在 | 512MB 全部已直映射,无需高端内存 |
5.3 内核虚拟地址布局 (1GB)
区段 | 大致范围 | 用途 |
lowmem 线性映射区 | 0xC0000000 – 0xDFFFFFFF (512MB) | 与物理内存 1:1 映射, VA = PA + 0x40000000,kmalloc 所在 |
保护间隙 (VMALLOC_OFFSET) | 0xE0000000 – 0xE07FFFFF (8MB) | lowmem 与 vmalloc 之间的越界保护带 |
vmalloc 区 | 0xE0800000 – 0xFEFFFFFF (≈488MB) | 非连续物理页的虚拟连续映射; ioremap 外设也在这里 |
fixmap / pkmap / 向量表 | 0xFF000000 – 0xFFFFFFFF (≈16MB) | 固定映射、early console、高向量表 0xFFFF0000 等 |
实际边界可用
cat /proc/iomem 和内核启动日志中的 Virtual kernel memory layout 段核对确认。真机核对:抓一份你板子的 dmesg | grep -A 20 "Virtual kernel memory layout",对照表 5.3 看编译配置下真实边界(不同 defconfig 的 vmalloc 大小会变)。
5.3.1 vmalloc 与 ioremap 为什么共用一段虚拟地址
看上去两者用途截然不同:vmalloc 是给内核模块 / 大缓冲申请虚拟连续的 RAM,ioremap 是把外设寄存器物理地址映射成虚拟地址供内核读写。然而在 ARM32 Linux 里,它们共用同一段
VMALLOC_START – VMALLOC_END 区域。第一性原理分析
把事情拆到最底层,内核对"非线性映射"的需求只有两类:
- 给一段 非连续物理页 拼一个虚拟连续区(vmalloc)。
- 给一段 物理地址不在 RAM 中的设备 (MMIO) 建一段虚拟映射(ioremap)。
两个需求完全共享同一套机制:
问题 | 解决方法 |
从哪里找一块没人用过的虚拟地址? | vmap_area 红黑树 / 位图。vmalloc 和 ioremap 都开同一份树。 |
怎么在页表里建映射? | 都调 vmap_page_range() / ioremap_page_range(),最终落到 __vmalloc_node_range() 同一条路径。 |
怎么记账、怎么释放? | 同一链表 vmlist,/proc/vmallocinfo 里你会同时看到两类条目。 |
既然下面的虚拟地址分配器 + 页表操作 + 元数据管理全部是同一套代码,就没有理由在最稀缺的 ARM32 内核虚拟空间里给它们各开一个切分好的子区。
三个设计动机
1. 虚拟地址资源太帊。 ARM32 3G/1G 下,内核只有 1GB,扣掉 512MB lowmem 后只剩不到 500MB 可用于非线性映射。如果还硬拆成 "vmalloc 区 / ioremap 区" 两块固定预留,任一边爆一次 → 全有 OOM 风险。共池动态占用,互相貢惠。
2. 两者的核心操作本质一样。 都是"拿一段商定的虚拟地址 + 建 PTE + 指向某些物理地址",分别只是:
vmalloc | ioremap | |
物理页来源 | buddy 新分 4KB 页 | 设备寄存器的固定物理地址 |
PTE 属性 | Normal Cacheable | Device-nGnRE / Strongly-ordered |
物理页连续性 | 可以不连续 | 必须连续(寄存器本来就连续) |
骨架都是
alloc_vmap_area() + 页表设 PTE,差别只在 flag。Linus 没理由写两套。3. 给 ARM 的 early ioremap 留路。 内核启动早期(buddy 还没准备好)需要映射 UART / GIC 调试。既然后期
ioremap 走 vmalloc 区,早期的 early_ioremap 走另一块固定的 fixmap 区,两者就能平滑过渡——这也是为什么 fixmap 在 0xFF000000 顶部单独留到的一个原因。实战启示
cat /proc/vmallocinfo会看到两种类型:vmalloc、vmap、module:真正占用 RAM 页的项。ioremap:映射 MMIO,不占用物理 RAM,只吃虚拟地址和页表项。
- 算 vmalloc 区剩余时务必把 ioremap 的占用算进去;外设多的 SoC (如 IMX6ULL 调满 LCDIF + CSI + GPU) 可能吃掉十几 MB 虚拟地址。
- 在 ARM64 上这个约束消失:arm64 把
VMALLOC_START/END开成数十 TB 并且 ioremap 另开独立区段,因为地址空间不再是稀缺资源,独立管理更方便调试和安全隔离。
5.4 运行时主要分配器
分配器 | 粒度 | 典型用途 |
Buddy System | 页 (4KB) 的 2^n | 最底层物理页分配 |
Slab / SLUB | 对象级 | task_struct、inode 等高频小对象 |
kmalloc | 对象级 | 内核常规小块分配 (背后走 slab) |
vmalloc | 页级 | 大块非连续内存 (模块加载、大缓冲) |
Page Cache | 页级 | 文件 I/O 缓存、mmap 支撑 |
5.5 用户进程虚拟地址布局 (单进程 3GB)
每个进程都有独立的页表,MMU 将同一虚拟地址翻译到不同物理页,进程间天然隔离;内核部分 (
0xC0000000+) 在所有进程页表中共享同一份映射,系统调用时无需切换页表。5.6 为什么 0x80000000 – 0x80080000 (512KB) 留空
- 给 BootROM / SPL 返回时的临时数据留余量,避免内核启动早期踩到。
- 避免 DMA (SDHC、USB 等) 传输覆盖内核镜像头部。
- 对齐 1MB / 2MB section (ARM 一级页表的最小映射粒度),方便 MMU 早期页表构建。
5.7 ARM32 → ARM64 内存模型迁移对照表
IMX6ULL 是 ARMv7 (32-bit),而 i.MX8 / 高通 / 骁龙 / 树莓派 4B+ 都是 ARMv8 (64-bit aarch64)。两者的内存模型在结构上有根本性差异,以下对照表可作为跨架构排错 / 面试 / 驱动调优的参考。
维度 | ARM32 (ARMv7 / IMX6ULL) | ARM64 (ARMv8 / aarch64) | 实战启示 |
虚拟地址宽度 | 32-bit,共 4GB | 48-bit (默认) / 52-bit (ARMv8.2+),每半最大 256TB | ARM64 虚拟空间海量,根本不用抓襟见胘 1GB |
内核 / 用户切分 | 3G/1G (默认) 或 2G/2G,VMSPLIT_* 可选 | 物理上拆成两段:低半 = 用户空间 0x0–TTBR0_TOP,高半 = 内核空间 TTBR1_BASE–...;中间是不可访问的 "canonical hole" | ARM64 内核/用户不再争线性地址空间,还用不完 |
页表寄存器 | TTBR0 / TTBR1 共存,但内核映射多走 TTBR1;切空间要 flush TLB | TTBR0_EL1 = 用户页表、TTBR1_EL1 = 内核页表,硬件天然分离 | ARM64 系统调用不需切 TTBR,上下文切换更便宜 |
页表级数 | 2 级 (section 1MB + page 4KB);LPAE 下 3 级 | 默认 4KB 页 + 4 级 (PGD/PUD/PMD/PTE),支持 16KB / 64KB 页 | ARM64 可通过 64KB 页压减页表级数,限流分析页表开销 |
线性映射 (direct map) | lowmem: PAGE_OFFSET 向上映射只等于物理内存大小,上限 ~760MB | 内核半边内有专用 linear map region(如 5.4+ 的 PAGE_OFFSET 在 0xFFFF000000000000 附近),可容纳 TB 级物理内存 | ARM64 整个 RAM 永远线性映射,所以下面的 HIGHMEM 不存在 |
HIGHMEM / kmap() | 必要时开启(物理内存 > lowmem 上限) | 彻底取消, kmap_local 变成普通 API 没有映射作用 | 迁移代码时别在 arm64 代码路径里再假设 HIGHMEM 路径成立 |
vmalloc / ioremap | lowmem 上方一小段 (~488MB),与 ioremap 共享 | 独立的 VMALLOC_START–VMALLOC_END 区 (数十 TB),ioremap 单独区段 | ARM64 上大块 vmalloc / 大内存模块 不再担心地址空间费 |
内核镜像位置 | 物理 PHYS_OFFSET + TEXT_OFFSET 固定 (如 0x80008000) | 允许 KASLR 随机偏移 kimage_voffset;物理地址也可浮动 | ARM64 驱动中不能硬编码内核虚拟地址 |
Zone 划分 | DMA • NORMAL • (可选) HIGHMEM | DMA (通常 ≤ 4GB 给旧外设) + DMA32 (≤ 4GB) + NORMAL | ARM64 DMA 约束靠 DMA/DMA32,而不再靠物理地址低端 |
ASID 宽度 | 8-bit (256 个),溢出需全局刷 TLB | 16-bit (可选 8-bit),溢出概率低 | ARM64 上上下文切换成本更低 |
异常向量表 | 低向量 0x00000000 或高向量 0xFFFF0000(SCTLR.V) | VBAR_EL1 寄存器自由指定,每个异常级别独立 | ARM64 没有"高/低向量"概念,都用 VBAR |
特权保护 | 仅 U/S 两级;无 PAN(内核可直接读用户指针) | EL0/EL1/EL2/EL3 四级;PAN (ARMv8.1+) 禁止内核误触用户页;PAC/BTI (8.3/8.5+) | ARM64 驱动 copy_from_user 必须走正规 API,不能直接解引用 |
密切相关指令 | dsb/isb/dmb • cp15 寄存器操作 cache/TLB | 同名指令 + 专用的 TLBI / IC / DC 指令类;支持 inner/outer shareable 精细化 | ARM64 cache 维护 更细粒度,多核性能更好 |
用户栈 / mmap 生长 | 栈下长、mmap 从中间向下长,受 3GB 限 | 用户半边成百 TB,mmap 布局更激进随机化(ASLR) | ARM64 用户态内存滥用暴露更难发现,已备好 monitoring |
两句话总结:
- ARM32 的全部"巧思"(lowmem/HIGHMEM/VMSPLIT/高向量)都是在 4GB 虚拟空间太窄的前提下抠出来的。
- ARM64 用 256TB 半边空间 + 硬件双 TTBR + 永久线性映射 一次性把这些巧思全部拿掉,设计回归简单。






