uboot代码流程分析

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

331.3.4uboot代码流程分析

/* 课程源码版本:U-Boot 2016.03(NXP i.MX 下游版本)。 */
/* 以下仅收录视频实际定位并讲解的主线代码;各片段之间并非连续源码,未展示的条件编译、芯片勘误处理和初始化数组成员不擅自补入。 */

/* arch/arm/lib/vectors.S:ARM 异常向量表入口。 */
.globl _start                                      /* 将 _start 导出为全局符号,链接器可以把它作为入口引用。 */
.section ".vectors", "ax"                         /* 把后续内容放入可分配、可执行的异常向量段。 */
_start:                                            /* 定义 U-Boot 汇编入口 _start。 */
	b reset                                         /* 复位向量:无条件跳到 start.S 中的 reset;b 不保存返回地址。 */
	ldr pc, _undefined_instruction                  /* 未定义指令异常:把处理入口装入 pc。 */
	ldr pc, _software_interrupt                     /* 软件中断异常:跳到相应处理入口。 */
	ldr pc, _prefetch_abort                         /* 指令预取异常:跳到相应处理入口。 */
	ldr pc, _data_abort                             /* 数据访问异常:跳到相应处理入口。 */
	ldr pc, _not_used                               /* 保留向量:跳到预设处理入口。 */
	ldr pc, _irq                                    /* IRQ 异常:跳到 IRQ 处理入口。 */
	ldr pc, _fiq                                    /* FIQ 异常:跳到 FIQ 处理入口。 */

/* arch/arm/cpu/armv7/start.S:reset 的视频重点片段。 */
.globl reset                                       /* 导出 reset 符号,供复位向量跳转。 */
.globl save_boot_params_ret                        /* 导出保存启动参数后的返回标签。 */
reset:                                             /* ARMv7 通用复位入口。 */
	b save_boot_params                              /* 先让板级代码保存 ROM 传入的启动参数。 */
save_boot_params_ret:                              /* save_boot_params 执行后回到这里。 */
	mrs r0, cpsr                                    /* 读取当前程序状态寄存器 CPSR。 */
	and r1, r0, #0x1f                               /* 取 CPSR 的处理器模式位。 */
	teq r1, #0x1a                                   /* 判断当前是否为 Hyp 模式。 */
	bicne r0, r0, #0x1f                             /* 非 Hyp 模式时,先清除原模式位。 */
	orrne r0, r0, #0x13                             /* 非 Hyp 模式时,设置为 SVC32 模式。 */
	orr r0, r0, #0xc0                               /* 置 I、F 位,暂时屏蔽 IRQ 和 FIQ。 */
	msr cpsr, r0                                    /* 把新状态写回 CPSR。 */
	mrc p15, 0, r0, c1, c0, 0                       /* 读取 CP15 系统控制寄存器。 */
	bic r0, #CR_V                                   /* 清除高向量位,选择低地址异常向量。 */
	mcr p15, 0, r0, c1, c0, 0                       /* 写回系统控制寄存器。 */
	ldr r0, =_start                                 /* 取 _start 地址。 */
	mcr p15, 0, r0, c12, c0, 0                      /* 写 VBAR,使异常向量基址指向 _start。 */
	bl cpu_init_cp15                                /* 调用 CP15 初始化;bl 会把返回地址保存到 lr。 */
	bl cpu_init_crit                                /* 调用关键低级初始化。 */
	bl _main                                        /* 转入 crt0.S 的 _main,建立 C 运行环境。 */

/* arch/arm/cpu/armv7/start.S:cpu_init_cp15 的视频重点片段。 */
ENTRY(cpu_init_cp15)                               /* 声明 cpu_init_cp15 汇编函数入口。 */
	mov r0, #0                                      /* 准备全零操作数。 */
	mcr p15, 0, r0, c8, c7, 0                       /* 失效指令与数据 TLB。 */
	mcr p15, 0, r0, c7, c5, 0                       /* 失效指令缓存。 */
	mcr p15, 0, r0, c7, c5, 6                       /* 失效分支预测器阵列。 */
	mcr p15, 0, r0, c7, c10, 4                      /* 执行数据同步屏障。 */
	mcr p15, 0, r0, c7, c5, 4                       /* 执行指令同步屏障。 */
	mrc p15, 0, r0, c1, c0, 0                       /* 读取系统控制寄存器 SCTLR。 */
	bic r0, r0, #0x00002000                         /* 清除 V 位,使用低地址异常向量。 */
	bic r0, r0, #0x00000007                         /* 清除 M、A、C 位:先关闭 MMU、对齐检查和数据缓存。 */
	orr r0, r0, #0x00000002                         /* 重新开启对齐检查 A 位。 */
	orr r0, r0, #0x00000800                         /* 开启分支预测 Z 位。 */
#ifdef CONFIG_SYS_ICACHE_OFF                       /* 若配置明确要求关闭指令缓存。 */
	bic r0, r0, #0x00001000                         /* 清除 I 位,关闭指令缓存。 */
#else                                              /* 否则走此默认分支。 */
	orr r0, r0, #0x00001000                         /* 置 I 位,开启指令缓存。 */
#endif                                             /* 结束指令缓存配置分支。 */
	mcr p15, 0, r0, c1, c0, 0                       /* 把新的控制值写回 SCTLR。 */

/* arch/arm/cpu/armv7/start.S:cpu_init_crit 的完整主体。 */
ENTRY(cpu_init_crit)                               /* 声明关键低级初始化入口。 */
	b lowlevel_init                                 /* 直接跳到 lowlevel_init,不在这里建立返回链。 */
ENDPROC(cpu_init_crit)                             /* 标记 cpu_init_crit 汇编过程结束。 */

/* arch/arm/cpu/armv7/lowlevel_init.S:视频讲解的早期初始化主线。 */
ENTRY(lowlevel_init)                               /* 声明 lowlevel_init 汇编函数入口。 */
	ldr sp, =CONFIG_SYS_INIT_SP_ADDR                 /* 把配置的片上初始地址装入栈指针 sp。 */
	bic sp, sp, #7                                   /* 将 sp 向下对齐到 8 字节边界。 */
	push {ip, lr}                                    /* 保存旧 lr(由 ip 传入)和当前 lr。 */
	bl s_init                                       /* 调用 SoC/板级最早期初始化。 */
	pop {ip, pc}                                     /* 恢复寄存器,并通过装入 pc 返回调用者。 */
ENDPROC(lowlevel_init)                             /* 标记 lowlevel_init 过程结束。 */

/* arch/arm/lib/crt0.S:_main 建立初始 C 环境并进入 board_init_f。 */
ENTRY(_main)                                       /* 声明 _main 汇编入口;这不是 common/main.c 的 main_loop。 */
	ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)               /* 装入初始栈顶地址。 */
	bic sp, sp, #7                                   /* 按 ARM ABI 要求做 8 字节对齐。 */
	mov r0, sp                                       /* 把当前栈指针作为参数放入 r0。 */
	bl board_init_f_alloc_reserve                   /* 在初始栈区域预留早期 global_data 等空间。 */
	mov sp, r0                                       /* 使用函数返回的新栈位置。 */
	mov r9, r0                                       /* ARM32 约定用 r9 保存 global_data 指针 gd。 */
	bl board_init_f_init_reserve                    /* 初始化刚预留的早期数据区。 */
	mov r0, #0                                       /* 准备 board_init_f 的 boot_flags 参数 0。 */
	bl board_init_f                                 /* 进入重定位前的 C 初始化序列。 */

/* arch/arm/lib/crt0.S:_main 完成重定位、BSS 清零后转入 board_init_r。 */
	mov r0, r9                                       /* 第一个参数传入新的 gd 指针。 */
	ldr r1, [r9, #GD_RELOCADDR]                      /* 第二个参数传入 U-Boot 重定位目的地址。 */
	ldr pc, =board_init_r                            /* 非 Thumb 构建中直接转到 board_init_r;它不应返回。 */
ENDPROC(_main)                                     /* 标记 _main 汇编过程结束。 */

/* common/board_f.c:视频定位到的 board_init_f 核心。 */
void board_init_f(ulong boot_flags)                 /* 重定位前的板级通用初始化入口。 */
{                                                  /* 函数体开始。 */
	gd->flags = boot_flags;                          /* 保存启动标志。 */
	gd->have_console = 0;                            /* 标记完整控制台尚未就绪。 */
	if (initcall_run_list(init_sequence_f))          /* 按顺序运行 init_sequence_f,非零表示某一步失败。 */
		hang();                                       /* 初始化失败时停止继续启动。 */
}                                                  /* board_init_f 结束。 */

/* common/board_r.c:init_sequence_r 的末项及 board_init_r 核心。 */
	run_main_loop,                                   /* 重定位后初始化数组的最后一步:进入永久命令主循环。 */
void board_init_r(gd_t *new_gd, ulong dest_addr)    /* 重定位后的板级通用初始化入口。 */
{                                                  /* 函数体开始。 */
	if (initcall_run_list(init_sequence_r))          /* 按顺序执行重定位后的初始化函数数组。 */
		hang();                                       /* 任一初始化函数返回非零时停止。 */
	hang();                                          /* 正常情况下 run_main_loop 不返回;若意外返回则停止。 */
}                                                  /* board_init_r 结束。 */

/* common/main.c:视频框出的命令处理主线。 */
void main_loop(void)                                /* U-Boot 初始化完成后的命令处理入口。 */
{                                                  /* 函数体开始。 */
	const char *s;                                   /* 保存经环境变量选择后的自动启动命令字符串。 */
	cli_init();                                      /* 初始化命令行解释器。 */
	run_preboot_environment_command();               /* 若配置了 preboot,则先执行它。 */
	s = bootdelay_process();                         /* 读取并处理启动延时,返回待执行的 bootcmd。 */
	if (cli_process_fdt(&s))                         /* 若设备树提供了安全启动命令,则进行相应处理。 */
		cli_secure_boot_cmd(s);                       /* 在安全模式下执行该命令;通常不会返回。 */
	autoboot_command(s);                             /* 倒计时未被打断时执行自动启动命令。 */
	cli_loop();                                      /* 自动启动被打断或命令返回后,进入交互式命令循环。 */
}                                                  /* main_loop 结束。 */

本节沿着课程视频实际打开的七个文件,追踪 U-Boot 从 ARM 异常向量入口到交互式命令行的主干流程。课程压缩包中的源码顶层版本号为 U-Boot 2016.03;本文的文件名、函数名和行号语义均以该版本为准,其他版本可能已有调整。

主调用链可以先压缩成一行:

_start → reset → cpu_init_cp15 → cpu_init_crit → lowlevel_init → s_init → _main → board_init_f → 重定位并清零 BSS → board_init_r → run_main_loop → main_loop → 自动启动或 cli_loop

其中,b 是不保存返回地址的直接分支,bl 是会把返回地址写入链接寄存器 lr 的函数调用式分支。因此,阅读汇编流程时不能把所有跳转都理解成普通 C 函数调用。该区别可参见 Arm 官方对 Branch 与 Branch with Link 的说明。

一、uboot源码简要分析

U-Boot 是可移植的通用引导程序,同一套源码覆盖多种处理器、SoC 和开发板。真正参与本次 i.MX6ULL 构建的代码由板级配置和大量 CONFIG_* 条件宏共同决定,所以“在源码树中找到某个函数”不等于“该函数的所有分支都会被当前开发板编译执行”。

还要注意:_start 是 U-Boot 镜像自身的入口,并不是芯片上电后执行的第一条代码。i.MX6ULL 上电后先执行片内 Boot ROM;Boot ROM 完成启动介质判断、镜像装载等工作后,才把控制权交给 U-Boot。这与上一节的 Boot ROM → U-Boot → Linux 启动层次相衔接。

1.1 任务信息资源导图

课程中的操作方式是:在 Ubuntu 虚拟机中保留已经编译过的 U-Boot 源码,通过 Samba 共享目录让 Windows 侧的 VS Code 打开代码,再借助符号跳转追踪调用关系。“Samba”只是跨系统访问文件的方式,不参与 U-Boot 编译或启动。

本节七个文件及其职责如下:

顺序课程源码路径主要职责
1arch/arm/lib/vectors.S定义 _start 和 ARM 异常向量表
2arch/arm/cpu/armv7/start.SARMv7 复位入口、处理器状态与 CP15 早期设置
3arch/arm/cpu/armv7/lowlevel_init.S建立临时栈并调用 s_init()
4arch/arm/lib/crt0.S建立 C 运行环境、组织重定位并衔接前后两阶段初始化
5common/board_f.c执行重定位前的 init_sequence_f[]
6common/board_r.c执行重定位后的 init_sequence_r[]
7common/main.c处理自动启动倒计时并进入命令解释器

源码扩展名应写作大写 .S。大写 .S 通常表示该汇编文件在汇编前还要经过 C 预处理器,因此文件中可以使用 #ifdef、配置宏和头文件;这也是本节源码里条件编译很多的原因。

1.2 uboot源码

阅读这条启动链时,可以把它分为四层:

  1. 纯汇编入口:异常向量、处理器模式、CP15、临时栈。
  2. C 环境建立:准备 sp 和 gd,调用 board_init_f()。
  3. 重定位前后初始化:先运行 init_sequence_f[],再重定位代码、清零 BSS,随后运行 init_sequence_r[]。
  4. 命令处理:根据 bootdelay 决定执行 bootcmd,或进入 => 交互命令行。

U-Boot 官方初始化流程文档也将通用 ARM 路径概括为 start.S、低级初始化、board_init_f()、重定位、board_init_r() 和主循环几个阶段。

1.2.1 vectors.s

实际文件名是 arch/arm/lib/vectors.S。文件中的 .vectors 段保存异常向量表,_start 是这一段的入口。复位发生时,处理器从复位向量取指,第一项 b reset 把控制权交给 start.S。

后续七项并不是普通变量:ldr pc, ... 会改写程序计数器 pc,从而跳往未定义指令、软件中断、预取异常、数据异常、IRQ、FIQ 等处理入口。

视频定位的是课程版本第 48 行附近的 _start,而第 54 行是 b reset。行号只用于帮助回看本版本源码,不应当当作跨版本固定接口。

1.2.2 cpu/armv7/start.s

实际路径为 arch/arm/cpu/armv7/start.S。reset 的主干工作是:

  • 先调用板级 save_boot_params 保存 Boot ROM 传入的启动信息;
  • 在不是 Hyp 模式时切换到 32 位 SVC 模式;
  • 屏蔽 IRQ 和 FIQ,避免基础运行环境尚未建立时被中断打断;
  • 配置低地址异常向量,并让 VBAR 指向 _start;
  • 依次调用 cpu_init_cp15、cpu_init_crit 和 _main。

这里的 SVC 是 ARM 的特权模式之一。屏蔽中断只是早期启动阶段的临时措施,不表示 U-Boot 运行期间永远不使用中断。

1.2.3 cpu_init_cp15

CP15 是 ARMv7-A 的系统控制协处理器接口。课程把这一段概括为“关闭 MMU 和 Cache”,但源码需要更精确地读:

  • bic ... #0x7 先清除 SCTLR 的 M、A、C 位,即关闭 MMU、对齐检查和数据缓存;
  • 随后代码又置位 A,重新开启对齐检查;
  • 代码还开启分支预测;
  • 指令缓存是否关闭取决于 CONFIG_SYS_ICACHE_OFF。默认的 #else 分支会开启 I-Cache,而不是关闭全部缓存。

函数中还有视频未展开的 CPU 勘误处理分支。它们属于当前源码真实内容,但不应脱离具体芯片配置笼统解释为每次都会执行。

1.2.4 cpu_init_crit

cpu_init_crit 的主体只有一条 b lowlevel_init。这里使用 b 而不是 bl,意味着它把 lowlevel_init 当成尾跳转:lowlevel_init 最终恢复的返回地址仍然回到更早调用 cpu_init_crit 的位置,而不是回到这条 b 的下一行。

源码行尾旧注释写有设置 PLL、MUX、Memory,但通用 cpu_init_crit 本身没有逐项做这些事;实际工作要继续看 lowlevel_init、s_init 以及当前 SoC/开发板启用的实现,不能仅凭注释断定。

1.2.5 lowlevel_init.s

实际路径为 arch/arm/cpu/armv7/lowlevel_init.S。它先把 CONFIG_SYS_INIT_SP_ADDR 写入栈指针 sp,按 8 字节对齐;部分配置还会从栈中为早期 global_data 预留空间。这里建立的是临时栈和早期全局数据区,不是堆。

push {ip, lr} 保存返回现场,bl s_init 调用最早期 SoC/板级初始化,随后 pop {ip, pc} 恢复现场并返回。源码注释严格限制 s_init():它不应在此设置 DRAM、不应依赖完整 global_data、不应清零 BSS,也不应启动控制台,因为这些运行条件尚未准备好。

课程口述把 s_init() 简化成“空函数”。对当前 i.MX6ULL 路径而言,arch/arm/cpu/armv7/mx6/soc.c 中的实现检测到 MXC_CPU_MX6ULL 后立即 return,所以运行效果近似空操作;但函数源码本身并非空函数,对其他 i.MX6 型号还包含额外处理。这个边界不能省略。

1.2.6 bl_main

本小节标题沿用原笔记,但源码指令实际是 bl _main,并不存在名为 bl_main 的函数。bl 是指令,_main 才是符号名。

_main 位于 arch/arm/lib/crt0.S,承担从早期汇编环境过渡到 C 代码的枢纽工作:

  1. 建立并按 ABI 对齐初始栈;
  2. 预留并初始化早期 global_data,ARM32 用 r9 保存 gd 指针;
  3. 以参数 0 调用 board_init_f();
  4. 根据 board_init_f() 计算的地址切换新栈和新 gd,重定位 U-Boot;
  5. 重定位异常向量,完成 CPU 运行环境设置并清零 BSS;
  6. 把 gd 与重定位地址作为参数转入 board_init_r()。

global_data 用于在普通全局变量尚不可安全使用的早期阶段保存启动状态;ARM32 使用 r9 保存其指针是 U-Boot 的体系结构约定,参见 U-Boot 官方 global data 文档。

1.2.7 board_init_f

board_init_f() 位于 common/board_f.c。后缀 f 可帮助记忆为重定位前(before relocation)的阶段,但它不是语言层面的关键字。

函数将启动标志写入 gd,把“完整控制台已就绪”标志清零,然后调用:

initcall_run_list(init_sequence_f)

init_sequence_f[] 是以 NULL 结束的初始化函数指针数组。initcall_run_list() 按顺序调用每个成员;任一成员返回非零,board_init_f() 就调用 hang() 停止启动。数组内容受配置影响,课程版本中可包含早期 CPU/定时器/串口准备、DRAM 容量探测、为重定位和运行数据预留地址等步骤。

board_init_f() 本身不直接调用 board_init_r()。在 ARM 主线中,它返回 _main,由 crt0.S 完成重定位、向量调整和 BSS 清零后,再转入 board_init_r()。U-Boot Generic Board 文档同样把 common/board_f.c 和 common/board_r.c 区分为重定位前、后的两个阶段。

1.2.8 board_init_r

board_init_r() 位于 common/board_r.c,负责重定位后的完整初始化。它用 initcall_run_list(init_sequence_r) 运行第二个函数数组;按配置不同,其中会逐步初始化缓存、内存分配器、环境变量、完整控制台、存储设备、网络等子系统。

课程版本中 init_sequence_r[] 从第 868 行附近开始,到第 1084 行结束;原识别结果中的“1804 行”是 OCR 错误。数组末项为 run_main_loop。这个包装函数以无限循环反复调用 main_loop(),因此正常路径不会回到 board_init_r();末尾的 hang() 只是处理异常返回。

1.2.9 main.c

main_loop() 位于 common/main.c。课程重点标出了三处:

  • 第 68 行 s = bootdelay_process();:读取并处理 bootdelay,选择待执行的自动启动命令 bootcmd,将命令字符串返回给 s;它不只是泛泛地“读取环境变量”。
  • 第 72 行 autoboot_command(s);:显示并处理自动启动倒计时。若用户没有中断,才执行 s 指向的命令。在本开发板环境中,bootcmd 通常继续加载内核和设备树并启动 Linux,但这个函数本身并不等同于“启动内核”。
  • 第 74 行 cli_loop();:自动启动被打断,或自动命令失败、执行完后返回时,进入 U-Boot 交互式命令循环。

因此,正常自动启动成功后,内核接管 CPU,autoboot_command() 通常不会返回到 cli_loop();只有启动被打断或未成功转交时,用户才会看到 => 提示符。

1.2.10 实践操作

视频使用开发板自带的 CH340 USB 转串口芯片,Windows 中识别为 COM10。COM10 只是当时电脑分配的端口号,换电脑或 USB 插口后可能变化,应以设备管理器实际显示为准。

串口终端参数设置为:

参数值
波特率115200
数据位8
停止位1
校验位None
流控None

建议先打开终端再给开发板上电,这样才能看到最早期日志。若另接 USB-TTL 模块,应使用开发板允许的 3.3 V TTL 电平、共地,并让 TX/RX 交叉连接;不能把 RS-232 电平直接接入 SoC 串口。课程使用板载 CH340 时,按开发板接口定义连接 USB 即可。

二、U-Boot

2.1 断电重启

视频第一次上电没有按键,倒计时结束后 U-Boot 按环境变量继续读取 zImage 和 topeet_emmc_hdmi.dtb,随后出现 Starting kernel ...,说明控制权开始移交 Linux。

要进入 U-Boot 交互模式,可按以下顺序操作:

  1. 关闭开发板电源。
  2. 确认串口终端已连接并使用 115200 8N1。
  3. 重新上电,观察启动日志。
  4. 出现 Hit any key to stop autoboot 倒计时时按任意键。
  5. 看到 => 提示符后再输入命令。

不必限定按 Enter;普通按键也可中断。按键必须发生在倒计时窗口内,过早可能尚未启用串口输入,过晚则自动启动命令已经执行。

2.2 U-Boot模式

本节所说的两种“模式”实质是同一套 main_loop() 的两条控制路径:

路径条件结果
自动启动倒计时内未检测到中断按键autoboot_command() 执行 bootcmd,通常加载 Linux
交互命令行倒计时被按键打断,或自动命令返回cli_loop() 接管,显示 => 并等待命令

倒计时长度由 bootdelay 环境变量控制。视频环境中 printenv 显示 bootdelay=3,因此提示从 3 秒开始。若环境变量、编译配置或安全启动策略不同,实际行为也会不同。

2.2.1 断电重启

进入 => 后,视频实际输入的命令是:

printenv

它会打印全部 U-Boot 环境变量。视频可见的关键项包括 baudrate=115200、bootdelay=3、较长的 bootcmd=...,以及 fdt_file=topeet_emmc_hdmi.dtb。原 OCR 将命令识别为多个无意义单词,正确拼写是一个完整的 printenv。

若只想核对本节相关值,更清晰的只读命令是:

printenv bootdelay bootcmd baudrate fdt_file

在较新的命令说明中,printenv 是 env print 的兼容别名,参见 U-Boot 官方 env 命令文档。printenv 只读取环境;setenv 会修改内存中的环境副本,而 saveenv 会把环境持久化到存储介质。未确认启动配置前不要随意执行 saveenv,否则可能改变下次开机行为。

2.3 U-Boot源码分析

把串口现象反向映射到代码,可以得到下面的闭环:

  1. _start 和 reset 完成汇编入口与处理器早期状态设置。
  2. lowlevel_init 准备临时栈,当前 i.MX6ULL 的 s_init() 很快返回。
  3. _main 建立 C 环境并调用 board_init_f()。
  4. init_sequence_f[] 完成重定位前准备,_main 随后重定位 U-Boot、清零 BSS。
  5. board_init_r() 运行 init_sequence_r[],其末项进入 run_main_loop()。
  6. main_loop() 取得 bootdelay 和 bootcmd;未按键就执行自动命令,按键中断则落入 cli_loop()。
  7. cli_loop() 显示 =>,用户输入 printenv 后由命令解析器查找并执行对应命令。

这也解释了为何串口日志和源码能够一一对应:Hit any key to stop autoboot 来自自动启动判断,=> 来自命令行循环,而 printenv 输出的是 U-Boot 自己维护的环境数据,并非 Linux Shell 的环境变量。

需要注意,能中断倒计时并不等同于经过身份认证。量产设备若暴露可写的 U-Boot 控制台,攻击者可能改变启动参数或读取存储内容;是否禁用交互、保护环境、启用验证启动,应根据产品安全需求另行设计。

三、课程总结

本节完成了 U-Boot 主干源码的第一次端到端追踪:

  • vectors.S 的 _start 通过复位向量跳到 reset;
  • start.S 建立 ARMv7 早期状态,并进入低级初始化与 _main;
  • lowlevel_init.S 建立临时栈,调用受严格限制的 s_init();
  • crt0.S 的 _main 串起 board_init_f()、重定位、BSS 清零和 board_init_r();
  • board_f.c 与 board_r.c 分别执行重定位前后的初始化函数数组;
  • main.c 根据 bootdelay 在自动执行 bootcmd 与进入 cli_loop() 之间分流;
  • 串口中断倒计时并执行 printenv,验证了源码控制流与真实启动现象。

本节最重要的阅读方法不是死记行号,而是抓住“入口—早期汇编—C 环境—重定位前初始化—重定位后初始化—命令循环”这条骨架,再结合当前开发板配置判断条件编译的真实路径。后续分析编译脚本或板级配置时,也应继续回到这个骨架上定位它影响的是哪一个阶段。

校核依据:课程视频与随课 U-Boot 2016.03 源码;U-Boot Board Initialisation Flow、U-Boot Generic Board、U-Boot Global data、U-Boot environment commands及 Arm Branch and Call Sequences。