1. 从链接脚本说起2. 跳转到复位处理程序2.1 分析 reset2.2 分析save_boot_params 2.3 分析 save_boot_params_ret 2.3.1 通过操作CPSR寄存器,执行下列操作:2.3.2 通过操作SCTLR寄存器,执行如下操作:2.3.3  配置协处理器cpu_init_cp15 ,执行如下操作:2.3.4 配置寄存器 cpu_init_crit ,执行如下操作:2.3.5 _main,执行如下操作:2.4  分析 relocate_code2.4.1 函数原型和参数2.4.2 执行前的系统状态2.4.3 核心重定位逻辑2.4.4 内存段的复制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 为什么这样设计?
u-boot加载启动内核过程可以大致分为两个阶段上,接下来我们将详细分析u-boot源代码(版本号为2019.04)。
对于imx6ull而言,其第一阶段对应的文件时arch/arm/cpu/armv7/start.S和arch/arm/cpu/armv7/lowlevel_init.S
u-boot启动第一阶段流程图如下所示:

u-boot在第一阶段进行重定位的时候会将其第二阶段的整个u-boot重定位到内存中,内核也一样,因为内核是要运行在DDR中的,因此就要将内核重定位到DDR中。
1. 从链接脚本说起
上面是总的链接脚本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.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:为早期 malloc 和 GD数据分配空间,CONFIG_SYS_INIT_SP_ADDR = 0x0091ff00,r0=0x0091ff00 - (0x400(early malloc arena) + 0x100(GD_SIZE)) = 0x0091fa00;
- board_init_f_init_reserve:该函数主要作用是将GD区域清零,返回最初malloc区域的地址,即 0x0091fb00 = 0x0091fa00 + 0x100(GD_SIZE);
以上总结:初始化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)。 重定位后地址为 0x9ff02000;
- reserve_round_4k- 内存指针指向下一个4kB处,也就是4kB对齐;
- reserve_mmu- 为 TLB 腾4kB 空间,gd->relocaddr = 0x9fffc000 = 0xA0000000 - 4kB,做64kB对齐,赋值给 gd->relocaddr;
- 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页表区域 | ARM架构TLB表,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
2.4.1 函数原型和参数
- 输入参数: r0 = gd->relocaddr (目标地址,如 0x9FF02000)
- 全局状态: r9 = 新的全局数据指针
2.4.2 执行前的系统状态
| 组件 | 位置 | 地址示例 | 状态 | 
| 代码执行 | Flash/DDR低地址 | 0x87800000 | 当前执行位置 | 
| 栈 | DDR高地址 | 0x9EE6BFF0 | 已切换到新栈 | 
| 全局数据 | DDR高地址 | 0x9EE6FE00 | 已切换到新gd | 
| 目标区域 | DDR高地址 | 0x9FF02000 | 空白区域,待复制 | 
2.4.3 核心重定位逻辑
2.4.4 内存段的复制
复制的内容包括:
- .text段:可执行代码
- .rodata段:只读数据
- .data段:已初始化数据
- .got段:全局偏移表
不复制的内容:
- .bss段:未初始化数据(稍后清零)
- 栈和堆:已经在目标位置
2.5 分析 relocate_vectors
基于 
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→0x9FF02000 | 0x00000000/0xFFFF0000 | 代码拷贝,但向量表未更新 | 
| relocate_vectors | 0x9FF02000 | 0x9FF02000 (VBAR) 或 0x00000000/0xFFFF0000 | 向量表重定位完成 | 
- 源地址: 重定位后的 U-Boot 基地址 (如 0x9FF02000)
- 目标地址:
- VBAR 支持: 设置 VBAR 寄存器,无需物理拷贝
- 传统方式: 拷贝到 0x00000000 或 0xFFFF0000
- ARMv7-M: 设置 VTOR 寄存器
- 拷贝内容: 64字节的向量表 (直接表32字节 + 间接表32字节)
- 架构适配: 不同 ARM 架构和 SoC 有不同的处理方式
2.6 分析清除BSS段
BSS 段什么时候重定位到 DDR 高地址?
答案:BSS 段在 
relocate_code 执行时就"重定位"了,但它不是通过物理复制,而是通过符号地址重新计算实现的。2.6.1 BSS 段重定位的特殊性:
1. BSS 段不需要物理复制
2. 链接脚本中的布局
3. 重定位的实际过程
2.6.2 内存布局示例
2.6.3 为什么这样设计?
- BSS 段在 ELF 文件中不占存储空间 - 只是内存布局信息
- 只需要清零的内存空间 - 不需要复制实际数据
- 通过重定位表自动调整符号地址 - 无需额外的复制操作
- 提高效率 - 避免无意义的数据复制
核心理解:BSS 段的"重定位"是地址计算的重新映射,发生在 
relocate_code 的重定位表处理阶段,而不是传统意义上的数据复制重定位!





