程序入口
分析u-boot.lds链接脚本(图1)可以得知,程序的入口是一个名为_start的函数,哲理的_start指的是文件arch/arm/lib/vector.S中的函数,如图2所示。可以看到,_start后面是中断向量表,从图中的“.section “.vectors”, “ax””可以看出此代码存放在.vectors段中。


u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从图3的 932 行可以看到__image_copy_start 为 0X87800000,而.text 的起始地址也是 0X87800000。

U-Boot启动流程
从图2中可以得知_start函数运行后先跳转到了一个名为reset的函数,这个函数定义在arch/arm/cpu/armv7/start.S,代码如下:
1 | reset: |
reset又跳转到了save_boot_params函数,save_boot_params函数又跳转到了save_boot_params_ret函数,save_boot_params_ret代码如下:
1 | save_boot_params_ret: |
这一段的主要功能就是设置处理器进入SCV模式,并且关闭FIQ和IRQ这两个中断,之后再执行下面的代码:
1 | /* |
这段代码先清理了SCTLR.V位,告诉CPU别再用老的向量表,用VBAR指定的位置,然后设置VBAR为0x87800000,在异常发生时让CPU从0x87800000开始找向量表。接下来就是执行cpu_init_cp15函数,这函数也是在start.S中,这个函数都是一些和CP15有关的内容,通常会进行关闭MMU、配置cache等操作。cpu_init_crit也是定义在start.S中,它仅仅调用了lowlevel_init函数,这个函数定义在arch/arm/cpu/armv7/lowlevel_init.S,内容如下:
1 | #include <asm-offsets.h> |
pc寄存器正在执行的指令
ip是“临时中转寄存器”
lr是保存返回地址的寄存器,无法嵌套调用
这段代码第一步先建立了一个临时栈,给CPU一个可以用的栈指针;第二步决定要不要用global_data(r9),gd是一个通过r9间接访问的一块内存,再U-Boot启动过程中,gd先被放在临时SRAM里,等DDR初始化完成后进行重定位,gd会被memcpy到DDR,r9会更新为新地址;第三步保存ip和lr;第四步调用s_init()函数;第五步将两个数据出栈,此时的pc指向的是调用lowlevel_init指令之后的那句语句(lr入栈时的数据)。
s_init定义在文件arch/arm/cpu/armv7/mx6/soc.c中。这个函数中会判断当前 CPU 类型,如果 CPU 为 MX6SX、 MX6UL、 MX6ULL 或 MX6SLL 中的任意一种,那么就会直接返回,相当于 s_init 函数什么都没做。所以对于 I.MX6UL/I.MX6ULL 来说, s_init 就是个空函数。从 s_init 函数退出以后进入函数 lowlevel_init,但是 lowlevel_init 函数也执行完成了,返回到了函数 cpu_init_crit,函数 cpu_init_crit 也执行完成了,最终返回到 save_boot_params_ret。
graph LR
subgraph Init_Loop[初始化过程]
save_boot_params_ret --> cpu_init_crit
cpu_init_crit --> lowlevel_init
lowlevel_init --> s_init
s_init --> save_boot_params_ret
end
save_boot_params_ret -->|初始化完成| _main
_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:
1 | ENTRY(_main) |
_main多次切栈,多次切gd,做了一次代码的整体移动,最终不返回,它负责把系统从 裸启动态 推进到 可长期运行态。
①、建立最初可用的C环境,这是早期SRAM.ORCAM栈,不是最终栈,只为board_init_f服务。
1 | ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) |
②、预留早期内存。第一步从当前栈顶向下预留空间,再把gd放进去,最后让r9指向新的gd。这段代码中,board_init_f_alloc_reserve会从当前sp向下切一块内存,预留gd、early malloc、board info后返回新的r0,所以需要更新sp和r9寄存器的值。
1 | mov r0, sp |
③、board_init_f函数,主要进行初始化时钟 / PLL、初始化 DDR、填充gd->ram_size、gd->relocaddr、gd->reloc_off。
④、切换到真正的DDR栈、新gd。这几句代码将在SRAM的栈和gd转到了DDR中。
1 | ldr sp, [r9, #GD_START_ADDR_SP] |
⑤、代码重定位。先将lr保存为目前代码的地址,然后将其加上relocation偏移后就变成了here在DDR中应该对应的地址,relocate_code将整个U-Boot从Falsh/Rom复制到了DDR,并跳回lr指向的位置,此时的PC已经在DDR中。
1 | adr lr, here |
⑥、回到here并清BSS。然后跳入最终初始化函数board_init_r。

总结
U-Boot 启动过程中,内存的使用经历了从寄存器到 SRAM、再到 DDR 的逐步解锁过程。通过临时栈、early reserve 区和可迁移的 global_data,U-Boot 在没有 C 运行时的情况下完成了 DDR 初始化和代码重定位,最终建立起完整、稳定的 C 运行内存环境。