Lazy loaded image
Uboot 启动第一阶段
Words 6104Read Time 16 min
2025-9-11
u-boot加载启动内核过程可以大致分为两个阶段上,接下来我们将详细分析u-boot源代码(版本号为2019.04)。
 
对于imx6ull而言,其第一阶段对应的文件时arch/arm/cpu/armv7/start.S和arch/arm/cpu/armv7/lowlevel_init.S
u-boot启动第一阶段流程图如下所示:
notion image
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_ret

2.3 分析 save_boot_params_ret

2.3.1 通过操作CPSR寄存器,执行下列操作:

  1. 将cpu的工作模式设置为SVC32模式(即管理模式);
  1. 屏蔽IRQ和FIQ的中断。

2.3.2 通过操作SCTLR寄存器,执行如下操作:

  1. 设置异常向量表的及地址为0x00000000,且支持重映射;
  1. 向量表重定位到 _start 地址(uboot的运行介质(norflash nandflash sram等)映射地址可能不在0x0起始的地址)。
 
继续分析接下来的汇编代码

2.3.3 配置协处理器cpu_init_cp15 ,执行如下操作:

  1. 使整个数据和指令TLB、指令缓冲、分支预测无效,清空写缓冲区和预取缓冲区。
  1. 禁止内存管理单元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 说明文档
  1. board_init_f_alloc_reserve:为早期 malloc 和 GD数据分配空间,CONFIG_SYS_INIT_SP_ADDR = 0x0091ff00,r0=0x0091ff00 - (0x400(early malloc arena) + 0x100(GD_SIZE)) = 0x0091fa00;
  1. 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 resetldr 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
向量表重定位完成
  1. 源地址: 重定位后的 U-Boot 基地址 (如 0x9FF02000)
  1. 目标地址:
      • VBAR 支持: 设置 VBAR 寄存器,无需物理拷贝
      • 传统方式: 拷贝到 0x00000000 或 0xFFFF0000
      • ARMv7-M: 设置 VTOR 寄存器
  1. 拷贝内容: 64字节的向量表 (直接表32字节 + 间接表32字节)
  1. 架构适配: 不同 ARM 架构和 SoC 有不同的处理方式
 

2.6 分析清除BSS段

BSS 段什么时候重定位到 DDR 高地址?
答案:BSS 段在 relocate_code 执行时就"重定位"了,但它不是通过物理复制,而是通过符号地址重新计算实现的。

2.6.1 BSS 段重定位的特殊性

1. BSS 段不需要物理复制
2. 链接脚本中的布局
3. 重定位的实际过程

2.6.2 内存布局示例

2.6.3 为什么这样设计?

  1. BSS 段在 ELF 文件中不占存储空间 - 只是内存布局信息
  1. 只需要清零的内存空间 - 不需要复制实际数据
  1. 通过重定位表自动调整符号地址 - 无需额外的复制操作
  1. 提高效率 - 避免无意义的数据复制
核心理解:BSS 段的"重定位"是地址计算的重新映射,发生在 relocate_code 的重定位表处理阶段,而不是传统意义上的数据复制重定位!
上一篇
宏的用法
下一篇
Guide to Linux System

Comments
Loading...