1. 从链接脚本说起U-Boot 链接段布局(u-boot.lds)U-Boot 拷贝源与目标位置2. 跳转到复位处理程序2.1 分析 reset2.2 分析save_boot_params 2.3 分析 save_boot_params_ret2.3 寄存器操作汇总(缩写 + 功能)2.3.1 通过操作CPSR寄存器,执行下列操作:2.3.2 通过操作SCTLR寄存器,执行如下操作:2.3.3 配置协处理器cpu_init_cp15 ,执行如下操作:2.3.4 配置寄存器 cpu_init_crit,执行如下操作:2.3.5 _main,执行如下操作:OCRAM 早期分配布局(board_init_f 之前)2.4 分析 relocate_code2.4.1 函数原型和参数2.4.2 执行前的系统状态2.4.3 核心重定位逻辑2.4.4 .rel.dyn 重定位表详解2.4.5 内存段的复制2.5 分析 relocate_vectors2.5.1 ARM 支持三种向量表配置2.5.2 i.MX6ULL 的向量表重定位过程:2.5.3 小节2.6 分析清除BSS段2.6.1 BSS 段重定位的特殊性:2.6.2 内存布局示例2.6.3 为什么这样设计?3. 第一阶段总结执行流程总览关键地址速查(i.MX6ULL, 512MB DDR)
u-boot加载启动内核过程可以大致分为两个阶段上,接下来我们将详细分析u-boot源代码(版本号为2019.04)。

u-boot启动第一阶段流程图如下所示:

u-boot在第一阶段进行重定位的时候会将其第二阶段的整个 u-boot 重定位到内存中,内核也一样,因为内核是要运行在 DDR 中的,因此就要将内核重定位到 DDR 中。
对于imx6ull而言,其第一阶段对应的文件时arch/arm/cpu/armv7/start.S和arch/arm/cpu/armv7/lowlevel_init.S
1. 从链接脚本说起
U-Boot 链接段布局(u-boot.lds)
段名 | 基地址 (源) | 近似大小 | 属于 image_copy? | 内容说明 |
.text | 0x87800000 | ~384KB | ✅ 是 | vectors.S (异常向量表) + start.o + 其他代码 |
.rodata | 0x87860000 | ~128KB | ✅ 是 | 只读常量、字符串 |
.data | 0x87880000 | ~256KB | ✅ 是 | 已初始化全局/静态变量 |
.u_boot_list | — | — | ✅ 是 | u-boot command 等自有函数表 |
.__image_copy_end | — | 标记 | ⬛ 边界 | relocate_code 复制终点 |
.rel.dyn | — | — | ❌ 否 | 动态重定位表(fixloop 遍历修正) |
.mmutable | — | — | ❌ 否 | MMU 页表 |
.bss | 0x878C0000 | ~256KB | ❌ 否 (OVERLAY) | 未初始化数据,重定位后清零 |
绿色行 =
__image_copy_start → __image_copy_end 范围,即 relocate_code 物理拷贝的全部内容。U-Boot 拷贝源与目标位置
项目 | 源地址 (Flash/DDR 低地址) | 目标地址 (DDR 高地址) | 说明 |
image_copy 整体 | 0x87800000 ( __image_copy_start) | 0x9FE70000 ( gd->relocaddr) | relocate_code 逐 8 字节 copy_loop 拷贝 |
├ .text | 0x87800000 – 0x8785FFFF | 0x9FE70000 – 0x9FECFFFF | 异常向量表 + 启动代码 + 功能代码 |
├ .rodata | 0x87860000 – 0x8787FFFF | 0x9FED0000 – 0x9FEEFFFF | 只读数据随代码一起搬运 |
├ .data | 0x87880000 – 0x878BFFFF | 0x9FEF0000 – 0x9FF2FFFF | 已初始化变量,搬运后 fixloop 修正指针 |
└ .u_boot_list | 紧随 .data 之后 | 对应偏移 | 命令表、驱动表等 |
.rel.dyn | 不拷贝 | — | 仅遍历修正目标地址中的绝对引用 |
.bss | 不拷贝 | 紧随 image_copy 之后 | 符号地址通过重定位表自动映射,随后 memset 清零 |
为什么 U-Boot 要重定位到 DDR 高地址?
根本目的:为 Linux 内核腾出低地址的连续大块内存。 Linux 内核通常被加载到 DDR 低地址区域(i.MX6ULL 上
zImage 典型加载地址为 0x80008000)。如果 U-Boot 留在低地址(0x87800000),内核解压/加载时极可能覆盖正在运行的 U-Boot 代码,导致系统崩溃。board_init_f 中的 reserve_* 系列函数从 ram_top(0xA0000000)向下依次预留页表、U-Boot 代码、malloc 堆、GD、FDT、栈等,U-Boot 自身 + 运行时数据仅占顶部约 18MB,底部 ~494MB 完整保留给内核使用。其他收益:
- 统一启动介质差异:无论从 NOR Flash、NAND、SD 卡还是 eMMC 加载到哪个初始地址,重定位后始终运行在 DDR 高地址的固定位置,简化后续逻辑。
- 位置无关性验证:重定位过程本身验证了 U-Boot 的 PIC 机制(
.rel.dyn修正逻辑)正确工作。
- 访问局部性:代码、堆、栈都集中在 DDR 高地址区域,cache 命中率更高。
上面是总的链接脚本u-boot.lds,将
_start作为入口点,_start 在arch/arm/lib/vectors.S 中定义异常向量表, 当 cpu 产生异常时,便会将对应的异常入口地址加载到 pc 中,进而处理相应的异常处理程序。其中复位异常向量指令“b resets”决定了 u-boot 启动或者复位后将自动跳转到 resets 标志处执行。2. 跳转到复位处理程序
接着分析一下 resets 做了哪些工作,全局搜索我们发现 resets 其实就定义在
arch/arm/cpu/armv7/start.S文件中2.1 分析 reset
分析下面代码 reset 中只有一条跳转指令
“b save_boot_params”2.2 分析save_boot_params
save_boot_params 中只有一条跳转指令 b save_boot_params_ret2.3 分析 save_boot_params_ret
2.3 寄存器操作汇总(缩写 + 功能)
寄存器 | 全称 / 缩写含义 | 关键字段(常见位) | 本段代码做了什么 | 目的 / 效果 |
CPSR | Current Program Status Register(当前程序状态寄存器) | • M[4:0]:CPU 模式位(如 SVC、IRQ、FIQ、HYP)
• I:IRQ 屏蔽位(1=禁止 IRQ)
• F:FIQ 屏蔽位(1=禁止 FIQ) | • mrs r0, cpsr 读取 CPSR
• 清除并设置 M[4:0] 为 0x13(SVC32)(若非 HYP)
• orr r0, r0, #0xc0 置位 I/F(屏蔽 IRQ/FIQ)
• msr cpsr, r0 写回 | • 进入可控的管理模式(SVC)执行启动流程
• 禁止中断,避免早期初始化阶段被异步打断 |
SCTLR | System Control Register(系统控制寄存器,CP15 c1) | • V(bit13):异常向量表基址选择(0=低向量 0x00000000,1=高向量 0xFFFF0000)
• 其他位常用于 MMU/Cache/对齐等全局控制(本段重点是 V) | • mrc p15,0,r0,c1,c0,0 读 SCTLR
• bic r0, #CR_V 清除 V 位(选择低向量)
• mcr p15,0,r0,c1,c0,0 写回 | • 选择低向量地址空间(便于后续通过 VBAR 指向实际向量代码)
• 为异常向量重映射做准备 |
VBAR | Vector Base Address Register(向量表基地址寄存器,CP15 c12) | • 保存异常向量表的基地址(ARMv7-A/R 支持) | • ldr r0, =_start 取向量表入口 _start 地址
• mcr p15,0,r0,c12,c0,0 写 VBAR | • 将异常向量表基址指向 U-Boot 镜像中的 _start(无需把向量表物理拷贝到 0x0)
• 确保异常发生时跳转到正确的向量入口 |
CP15 | ARM 系统控制协处理器(Coprocessor 15),提供 MMU/Cache/TLB/向量表等控制接口 | • 通过 mrc/mcr 访问 CP15 寄存器组(如 SCTLR、VBAR、TLB/Cache 维护寄存器) | • 本段通过 mrc/mcr 访问 SCTLR 与 VBAR | • 在早期启动阶段,用最小代价完成系统关键控制寄存器配置 |
CP15 | ARM 系统控制协处理器(Coprocessor 15),提供 MMU/Cache/TLB/向量表等控制接口 | • 通过 mrc/mcr 访问 CP15 寄存器组(如 SCTLR、VBAR、TLB/Cache 维护寄存器) | • 本段通过 mrc/mcr 访问 SCTLR 与 VBAR | • 在早期启动阶段,用最小代价完成系统关键控制寄存器配置 |
三步操作的执行顺序与因果链
这三个寄存器的配置顺序不可调换,存在严格的因果依赖:
① CPSR → ② SCTLR → ③ VBAR
- 先 CPSR:将 CPU 切换到 SVC32(管理模式) 并屏蔽中断。只有在特权模式下才有权限通过
mcr/mrc访问 CP15 协处理器寄存器(SCTLR、VBAR 均属于 CP15)。若仍处于 User 模式则写 CP15 会触发未定义指令异常。
- 再 SCTLR:清除 V 位(bit13),选择低向量地址空间(0x00000000)。这一步是 VBAR 生效的前提条件——当 V=1 时,CPU 固定从 0xFFFF0000 取异常向量,VBAR 寄存器的值被忽略;只有 V=0 时,CPU 才从 VBAR 指定的基地址取向量。
- 最后 VBAR:将
_start(0x87800000)写入 VBAR。此后异常发生时,CPU 直接跳转到 U-Boot 镜像中的向量表,而非物理地址 0x0。
2.3.1 通过操作CPSR寄存器,执行下列操作:
- 将cpu的工作模式设置为SVC32模式(即管理模式);
- 屏蔽IRQ和FIQ的中断。
2.3.2 通过操作SCTLR寄存器,执行如下操作:
- 设置异常向量表的及地址为0x00000000,且支持重映射;
- 向量表重定位到 _start 地址(uboot的运行介质 - norflash nandflash sram等,映射地址可能不在0x0 起始的地址)。
继续分析接下来的汇编代码
2.3.3 配置协处理器cpu_init_cp15 ,执行如下操作:
- 使整个数据和指令TLB、指令缓冲、分支预测无效,清空写缓冲区和预取缓冲区。
- 禁止内存管理单元mmu、地址对齐检查、数据缓冲。打开ARM系统的跳转预测(分支预测)功能,不打断流水线,提高指令执行效率。(数据cache一定要关闭,避免缓存不一致,指令cache可保留,以提高操作效率。)
2.3.4 配置寄存器 cpu_init_crit,执行如下操作:
lowlevel_init:与特定开发板相关的初始化函数,会做 pll 初始化, 如果不是从内存启动,则会做内存初始化,方便后续拷贝到内存中运行。 设置栈指针指向CONFIG_SYS_INIT_SP_ADDR;2.3.5 _main,执行如下操作:
_main 包括
1. 执行uboot第一阶段,为重定位做准备;
2. 执行重定位代码
3. 执行uboot第二阶段,板级初始化、emmc初始化、控制台初始化、中断初始化及网络初始化其中第2部分另起一个小节 2.4 分析 relocate_code 讲解
其中第3部分在Uboot 启动第二阶段 讲解
_main 说明文档
board_init_f_alloc_reserve:从初始 SP 向下预留 early malloc + GD 空间
board_init_f_init_reserve:将 GD 区域清零,返回 early malloc 基地址
CONFIG_SYS_INIT_SP_ADDR = 0x0091FF00
该值定义在板级头文件
include/configs/mx6ullevk.h 中,计算方式:CONFIG_SYS_INIT_RAM_ADDR (0x00900000) + CONFIG_SYS_INIT_RAM_SIZE (0x20000) - GENERATED_GBL_DATA_SIZE (0x100) = 0x0091FF00即 OCRAM 顶部(0x0091FFFF)向下偏移 256 字节处,预留了一个 GD 大小的安全边界。
为什么 GD/栈放在 OCRAM 而非 DDR? 更准确地说,不是"DDR 不可用"——硬件级 DDR 初始化已由 Boot ROM 通过 DCD 完成,U-Boot 代码实际上已经在 DDR 中执行(加载地址 0x87800000 就在 DDR 范围内)。真正的原因是:
board_init_f 需要 GD 来记录 DDR 大小和布局规划结果(gd->ram_size、gd->relocaddr 等),而这些信息要等到 dram_init 探测/确认后才可用。在 dram_init 之前,U-Boot 不知道 DDR 有多大、该如何规划内存,因此 GD/栈必须先放在 OCRAM 这个无需任何软件初始化即可使用的存储器中。GD(Global Data)区域含义
struct global_data(别名 gd_t,大小 GD_SIZE = 0x100 = 256 字节)是 U-Boot 启动全过程的核心状态结构体,通过寄存器 r9 全局访问。关键字段包括:gd->ram_size— DDR 总容量(512MB)
gd->relocaddr— 重定位目标地址
gd->start_addr_sp— 重定位后新栈顶
gd->mon_len— U-Boot 镜像总长度
gd->fdt_blob— 设备树地址
gd->new_gd— 重定位后 GD 新地址(DDR 高地址区)
在
board_init_f 阶段,GD 存放在 OCRAM;完成内存规划后,setup_reloc 将其搬运到 DDR 高地址的 new_gd 区域。OCRAM 早期分配布局(board_init_f 之前)
起始地址 | 结束地址 | 容量 | 内容 | 说明 |
0x00900000 | 0x0091F9FF | ~126KB | SPL / BootROM 工作区 | 启动早期代码和数据(参见 ) |
0x00900000 | 0x0091F9FF | ~126KB | SPL / BootROM 工作区 | 启动早期代码和数据(参见 ) |
0x0091FA00 | 0x0091FAFF | 256B | GD(global_data) | r9 指向此处, board_init_f_init_reserve 将其清零 |
0x0091FB00 | 0x0091FEFF | 1KB | Early malloc arena | CONFIG_SYS_MALLOC_F_LEN = 0x400,DDR 初始化前的临时堆 |
0x0091FF00 | 0x0091FFFF | 256B | 初始栈空间 | CONFIG_SYS_INIT_SP_ADDR,向下增长 |
以上总结:初始化c语言环境,以便调用board_init_f函数。
board_init_f - 初始化 boot_flags 和 have_console 标志位,运行 initcall_run_list (遍历执行 init_sequence[ ] 中函数,初始化uboot前半段)。下面init_sequence_f包括哪些函数:
setup_mon_len- 根据.lds文件中 __bss_end 与 __bss_end 计算出 u-boot 本身的大,赋给gd->mon_len变量;
fdtdec_setup- 检查 gd->fdt_blob 处是否存在 dtb;
env_init- 使用 default_environment[] 数组初始化 env_addr 和 env_valid 状态;
init_baud_rate- 设置默认串口波特率;
serial_init- 设置 flags 串口准备好标志,返回当前指向的串口设备指针;
console_init_f- 设置 have_console 标志,初始化 console;
display_options- 打印横幅信息;
display_text_info- 打印代码段基地址、BSS 段起始地址以及 BSS 段末尾地址
show_board_info- 从 fdt 中获取 model 信息并打印;
announce_dram_init- 打印 "DRAM: ";
dram_init- 获取 dram 大小,并设置 dram_size;
setup_dest_addr- 初始化 ram_size,ram_base,ram_top,relocaddr,DRAM 的基地址为0x80000000,大小为 0x20000000(512M),RAM顶端地址为 0xA0000000 = 0x80000000 + 0x20000000(512M)。 重定位后地址为 0x9FE70000;
reserve_round_4k- 内存指针指向下一个4kB处,也就是4kB对齐;
reserve_mmu- 为 MMU 一级页表(L1 Translation Table)预留 16KB 空间,做 64KB 对齐后赋值给 gd->relocaddr;
易混淆概念辨析:TLB ≠ 页表
- 页表(Page Table):存放在 DDR 中的数据结构,记录完整的虚拟地址→物理地址映射。由软件(U-Boot / 内核)创建和维护。MMU 硬件通过 TTBR 寄存器读取页表基地址,再从 DDR 中查表完成地址翻译。
- TLB(Translation Lookaside Buffer):CPU 片上硬件缓存,缓存最近使用的页表查询结果。由硬件自动管理,软件仅能执行 invalidate 操作,无需也无法为其分配内存。
- MMU(Memory Management Unit):CPU 内部的地址翻译单元。工作流:虚拟地址 → TLB 命中则直接返回 → 未命中则 MMU 执行 table walk(从 DDR 读页表)→ 结果回填 TLB。
reserve_mmu预留的是 DDR 中的页表空间,不是 TLB。 U-Boot 源码注释中的 "TLB" 是历史遗留用词,实际指 translation table。
为什么是 16KB?
ARMv7 一级页表采用段式映射(Section mapping,每段 1MB),覆盖 4GB 地址空间需要 4096 个条目,每个条目 4 字节:4096 × 4B = 16KB。ARM 架构要求一级页表基地址 16KB 对齐,U-Boot 实际做 64KB 对齐以满足更严格的兼容性要求。
为什么页表放在 DDR 而非片上 SRAM?
i.MX6ULL 的 OCRAM 仅 128KB,而页表 + 后续内核页表扩展会持续占用内存。DDR 容量充足(512MB),是唯一合理的长期存放位置。
为什么 U-Boot 只建立一级页表?
U-Boot 作为 bootloader,仅需粗粒度的内存映射(1MB 段映射)来开启 cache 加速启动过程,不需要 4KB 粒度的二级页表。精细的二级页表由 Linux 内核接管后自行建立。
reserve_video- 空;
reserve_trace- 空;
reserve_uboot- 为 uboot 分配空间,同时做4kB对齐;
reserve_malloc- 为 malloc 腾出一段空间, malloc 空间16MB, Env空间 64KB;
reserve_board- 给bd预留空间,存放板子信息,如 DRAM 起始地址、DRAM大小、SRAM 起始地址、SRAM 大小、boot 参数等,共预留了80字节,gd->start_addr_sp = 0x9eef1fb0;
setup_machine- 空;
reserve_global_data- 预留256字节给 new_gd (新的全局数据);
reserve_fdt- 预留 40032 字节存放设备树信息;
以上内存分配完成。
reloc_fdt- 负责将设备树数据搬运到新分配的 new_fdt 地址中去;
setup_reloc- 将gd重定位到 new_gd 中;
init_sequence_f 执行情况- 执行前: DDR未初始化,使用OCRAM临时环境
- 执行中: DDR初始化,完成内存布局规划
- 执行后: 为重定位做好完整准备,但代码仍在原位置
以上
board_init_f 中的内容就已经分析完了。执行完成后,内存布局从高地址到低地址规划如下(以 i.MX6ULL 为例):
起始地址 | 结束地址 | 容量 | 内容 | 说明 |
0xA0000000 | 0xA0000000 | - | RAM顶部 | gd->ram_top (512MB DDR终点) |
0x9FFF0000 | 0x9FFFFFFF | 64KB | MMU 一级页表区域 | L1 Translation Table(实际 16KB,64KB 对齐) |
0x9FE70000 | 0x9FEEFFFF | ~512KB | U-Boot代码区域 | gd->relocaddr,重定位目标 |
0x9EE70000 | 0x9FE6FFFF | 16MB | malloc堆区域 | TOTAL_MALLOC_LEN=16MB |
0x9EE6FF00 | 0x9EE6FFFF | ~256B | 板信息结构 | sizeof(bd_t) |
0x9EE6FE00 | 0x9EE6FEFF | ~256B | 新全局数据 | sizeof(gd_t) |
0x9EE6E000 | 0x9EE6FDFF | ~8KB | 设备树区域 | 如果不是嵌入式DT |
0x9EE6D000 | 0x9EE6DFFF | 4KB | 启动阶段数据 | 如果启用bootstage |
0x9EE6BFF0 | 0x9EE6CFFF | ~4KB | 栈对齐区域 | 16字节对齐 |
... | 0x9EE6BFF0 | - | 新栈顶 | gd->start_addr_sp |
0x80000000 | 0x9EE6BFEF | ~494MB | 可用内存 | Linux内核使用区域 |
以下 uboot 自身的重定位和 bss 段的初始化。
2.4 分析 relocate_code
relocate_code 主要作用
将 U-Boot 镜像从 DDR 低地址(0x87800000)物理拷贝到 DDR 高地址(
gd->relocaddr),并修正所有绝对地址引用。具体分三步:- copy_loop — 逐 8 字节将
__image_copy_start到__image_copy_end范围的 .text/.rodata/.data 段复制到目标地址
- copy_loop — 逐 8 字节将
__image_copy_start到__image_copy_end范围的 .text/.rodata/.data 段复制到目标地址
- fixloop — 遍历
.rel.dyn重定位表,将目标代码中所有R_ARM_RELATIVE类型的绝对地址加上偏移量r4,使指针指向新位置
- 缓存维护 — 刷新指令缓存,确保 CPU 从新地址取到正确的指令
执行完成后,DDR 高地址区域拥有一份地址修正后可直接运行的 U-Boot 副本,
bx lr 返回时 PC 已跳转到新地址继续执行。2.4.1 函数原型和参数
- 输入参数: r0 = gd->relocaddr (目标地址,如 0x9FE70000)
- 全局状态: r9 = 新的全局数据指针
2.4.2 执行前的系统状态
组件 | 位置 | 地址示例 | 状态 |
代码执行 | Flash/DDR低地址 | 0x87800000 | 当前执行位置 |
栈 | DDR高地址 | 0x9EE6BFF0 | 已切换到新栈 |
全局数据 | DDR高地址 | 0x9EE6FE00 | 已切换到新gd |
目标区域 | DDR高地址 | 0x9FE70000 | 空白区域,待复制 |
2.4.3 核心重定位逻辑
2.4.4 .rel.dyn 重定位表详解
.rel.dyn 是什么?U-Boot 以
-fpie(位置无关可执行)方式编译,代码中所有绝对地址引用(全局变量指针、函数指针、间接向量表的 .word addr 等)在链接时基于 __image_copy_start(0x87800000)。copy_loop 将镜像搬运到新地址后,这些绝对地址仍指向旧位置。.rel.dyn 段是链接器生成的一张"哪些位置存放了绝对地址"的清单,fixloop 遍历该清单,将每个绝对地址加上偏移量 r4,使其指向新位置。条目结构
每个
.rel.dyn 条目占 8 字节(两个 32-bit word):- Word 0 —
r_offset:需要修正的地址(基于原链接地址)
- Word 1 —
r_info:低 8 位 = 重定位类型,高 24 位 = 符号索引
U-Boot 只关心类型
R_ARM_RELATIVE(值 = 0x17 = 23),其余类型直接跳过。具体示例
假设 C 代码中有一个全局函数指针:
编译链接后,
init_func 位于 .data 段,存储的值是 board_early_init_f 的绝对地址,例如 0x87812340。链接器同时在 .rel.dyn 中生成一条记录:fixloop 处理过程(偏移量 r4 = 0x18670000):哪些代码模式会产生条目?
代码模式 | 示例 | 产生原因 |
全局函数指针 | void (*fp)(void) = func; | .data 存绝对地址 |
间接向量表 | _irq: .word irq | .word 存处理程序地址 |
全局变量指针 | int *p = &global_var; | 指针初始值是绝对地址 |
u_boot_list 命令表 | U_BOOT_CMD(...) 宏 | 函数指针数组 |
switch-case 跳转表 | 大型 switch 语句 | 编译器生成绝对跳转表 |
不需要修正的情况:
- PC 相对跳转(
b/bl指令)— 编码为相对偏移,搬运后天然有效
- 立即数 / 常量 — 不包含地址信息
- 栈上局部变量 — 运行时动态分配,不涉及链接时地址
核心要点:
.rel.dyn 是编译器/链接器的产物,它让 U-Boot 无需在运行时解析 ELF 符号表,仅通过一次线性遍历 + 加法即可完成所有绝对地址的修正,实现高效的运行时重定位。2.4.5 内存段的复制
复制的内容包括:
.text段:可执行代码
.rodata段:只读数据
.data段:已初始化数据
.got段:全局偏移表
不复制的内容:
.bss段:未初始化数据(稍后清零)
- 栈和堆:已经在目标位置
2.5 分析 relocate_vectors
relocate_vectors 主要作用
relocate_code 只负责搬运代码,不更新 CPU 的异常向量表入口。如果此时发生异常(如中断、数据访问错误),CPU 仍会跳转到旧地址的向量表,而旧地址的代码可能已被覆盖或失效。relocate_vectors 解决这个问题:将异常向量表的基地址指向重定位后的新位置,确保异常发生时 CPU 跳转到 DDR 高地址区的正确处理程序。三种实现方式(取决于架构):
- ARMv7-A/R(i.MX6ULL):直接写 VBAR 寄存器 =
gd->relocaddr,零拷贝
- 传统 ARM:将 64 字节向量表物理拷贝到 0x00000000 或 0xFFFF0000
- ARMv7-M(Cortex-M):写 VTOR 寄存器
基于
arch/arm/lib/vectors.S 的定义,ARM 异常向量表有以下结构:偏移地址 | 异常类型 | 指令内容 | 处理程序 |
0x00 | Reset | b reset 或 ldr pc, _reset | 系统复位 |
0x04 | Undefined Instruction | ldr pc, _undefined_instruction | 未定义指令异常 |
0x08 | Software Interrupt (SWI/SVC) | ldr pc, _software_interrupt | 软件中断 |
0x0C | Prefetch Abort | ldr pc, _prefetch_abort | 指令预取终止 |
0x10 | Data Abort | ldr pc, _data_abort | 数据访问终止 |
0x14 | Reserved | ldr pc, _not_used | 保留 (未使用) |
0x18 | IRQ | ldr pc, _irq | 普通中断 |
0x1C | FIQ | ldr pc, _fiq | 快速中断 |
其在uboot镜像中向量表布局
relocate_vectors 负责重定位 ARM 异常向量表,确保异常处理程序指向正确的地址。2.5.1 ARM 支持三种向量表配置
1. 标准向量表 (无 VBAR 支持,传统方式) - 拷贝到 0x00000000 或 0xFFFF0000
2. VBAR 支持 (ARMv7-A/R) - 设置 VBAR 寄存器,无需物理拷贝
3. ARMv7-M (Cortex-M) - 设置 VTOR 寄存器
2.5.2 i.MX6ULL 的向量表重定位过程:
2.5.3 小节
阶段 | 向量表存储位置 | CPU访问地址 | 说明 |
重定位前 | 0x87800000 | 0x00000000/0xFFFF0000 | 原始位置,可能已拷贝到系统向量地址 |
relocate_code | 0x87800000→0x9FE70000 | 0x00000000/0xFFFF0000 | 代码拷贝,但向量表未更新 |
relocate_vectors | 0x9FE70000 | 0x9FE70000 (VBAR) 或 0x00000000/0xFFFF0000 | 向量表重定位完成 |
- 源地址: 重定位后的 U-Boot 基地址 (如 0x9FE70000)
- 目标地址:
- VBAR 支持: 设置 VBAR 寄存器,无需物理拷贝
- 传统方式: 拷贝到 0x00000000 或 0xFFFF0000
- ARMv7-M: 设置 VTOR 寄存器
- 拷贝内容: 64字节的向量表 (直接表32字节 + 间接表32字节)
- 架构适配: 不同 ARM 架构和 SoC 有不同的处理方式
2.6 分析清除BSS段
清除 BSS 段主要作用
重定位完成后,BSS 段的符号地址已通过
.rel.dyn fixloop 映射到 DDR 高地址,但该区域的内存内容是随机脏数据。C 语言规范要求未初始化的全局/静态变量必须为零,因此必须对 __bss_start 到 __bss_end 范围执行 memset(0)。关键点:BSS 段不参与物理拷贝(不在
__image_copy_start ~ __image_copy_end 范围内),它的"重定位"仅是符号地址的重新计算。清零操作是 BSS 段在新地址上唯一的初始化动作,完成后 C 运行时环境才真正就绪,可以安全调用 board_init_r 进入第二阶段。BSS 段什么时候重定位到 DDR 高地址?
答案:BSS 段在
relocate_code 执行时就"重定位"了,但它不是通过物理复制,而是通过符号地址重新计算实现的。2.6.1 BSS 段重定位的特殊性:
1. BSS 段不需要物理复制
2. 链接脚本中的布局
3. 重定位的实际过程
2.6.2 内存布局示例
2.6.3 为什么这样设计?
- BSS 段在 ELF 文件中不占存储空间 - 只是内存布局信息
- 只需要清零的内存空间 - 不需要复制实际数据
- 通过重定位表自动调整符号地址 - 无需额外的复制操作
- 提高效率 - 避免无意义的数据复制
核心理解:BSS 段的"重定位"是地址计算的重新映射,发生在
relocate_code 的重定位表处理阶段,而不是传统意义上的数据复制重定位!3. 第一阶段总结
U-Boot 启动第一阶段从复位向量
_start 开始,到 board_init_r 入口前结束,核心目标是将自身从加载地址搬运到 DDR 高地址并建立完整的 C 运行时环境。整个过程可归纳为以下六步:阶段 | 执行函数 | 关键动作 | 执行环境 |
① 异常向量表 | _start (vectors.S) | 定义 8 个异常入口,复位向量跳转到 reset | Flash/DDR 低地址 |
② CPU 早期初始化 | save_boot_params_ret | 设置 SVC32 模式、屏蔽中断、配置 VBAR/SCTLR、失效 TLB/ICache、关闭 MMU | Flash/DDR 低地址 |
③ 板级底层初始化 | lowlevel_init → s_init | 设置临时栈(OCRAM)、PLL/时钟/DDR 控制器初始化 | OCRAM 栈 |
④ 内存规划 | board_init_f → init_sequence_f[] | 初始化 GD、DDR、串口;从 ram_top 向下规划页表/代码/malloc/GD/FDT/栈 | OCRAM 栈 + GD |
⑤ 重定位 | relocate_code • relocate_vectors | copy_loop 搬运镜像、fixloop 遍历 .rel.dyn 修正绝对地址、更新向量表 | DDR 新栈 + 新 GD |
⑥ BSS 清零 | memset(__bss_start, 0, len) | 将 BSS 段归零,C 运行时环境就绪 | DDR 高地址(重定位后) |
执行流程总览
关键地址速查(i.MX6ULL, 512MB DDR)
符号 / 寄存器 | 值 | 含义 |
__image_copy_start | 0x87800000 | U-Boot 加载基地址(源) |
gd->ram_top | 0xA0000000 | DDR 顶端 |
MMU 页表 | 0x9FFF0000 | 64KB 对齐,实际 16KB |
gd->relocaddr | 0x9FE70000 | 重定位目标地址 |
r4(重定位偏移) | 0x18670000 | 0x9FE70000 − 0x87800000 |
gd->start_addr_sp | ~0x9EE6BFF0 | 重定位后新栈顶 |
Linux 可用区域 | 0x80000000 – ~0x9EE6BFEF | ~494MB 连续内存 |
下一步:BSS 清零完成后,
_main 调用 board_init_r 进入Uboot 启动第二阶段,执行板级外设初始化(eMMC、网络、USB)、环境变量加载、控制台初始化,最终进入命令行循环或自动引导 Linux 内核。





