深入解析UBOOT启动流程:BL0、BL1与BL2的协同工作机制

张开发
2026/4/11 9:26:59 15 分钟阅读

分享文章

深入解析UBOOT启动流程:BL0、BL1与BL2的协同工作机制
1. UBOOT启动流程概述第一次接触嵌入式开发的朋友可能会好奇当我们按下开发板的电源键后系统是如何从一片空白变成可以运行操作系统的这背后隐藏着一个精密的接力赛跑过程。以常见的ARM架构为例UBOOT作为最常用的BootLoader其启动流程就像一场精心设计的三级跳远比赛BL0、BL1和BL2这三个阶段各司其职又紧密配合。想象一下电脑开机时的BIOS界面嵌入式系统中的UBOOT就扮演着类似的角色。但不同于PC的是嵌入式设备通常没有BIOS这样的固件UBOOT需要从零开始搭建整个运行环境。这个搭建过程分为三个阶段BL0是芯片出厂时固化的点火程序BL1是存储在外部介质的启动引信BL2则是我们熟悉的完整UBOOT功能。在实际项目中我遇到过不少开发者对这三个阶段的关系感到困惑。比如有位同事曾疑惑为什么我修改了UBOOT代码但启动没变化后来发现他改的是BL2部分的代码但问题出在BL1的加载过程。理解这三个阶段的协作机制就像掌握了一把解开嵌入式启动奥秘的钥匙。2. BL0芯片固化的启动引擎2.1 BL0的核心作用BL0是启动流程中的第一棒它直接固化在芯片的ROM中业内常称为iROM。这就好比汽车发动机的点火系统——只要通电就会自动运行。我在使用三星Exynos芯片时BL0会占据芯片地址空间最开始的64KB区域这个区域用户无法修改就像被焊死在芯片里的微型操作系统。BL0主要完成两个关键任务硬件启动方式检测通过读取芯片的OM引脚电平判断是从SD卡、NAND Flash还是USB等设备启动。这就像电脑BIOS检测启动顺序。加载BL1到SRAM根据检测到的启动方式从相应存储设备的特定位置加载BL1到芯片内部SRAM。以SD卡启动为例BL0会固定读取block1的内容。2.2 BL0的典型工作流程让我们通过一个实际案例来理解BL0的工作。假设我们使用SD卡启动/* 伪代码示意BL0的工作流程 */ void BL0() { // 1. 初始化基本硬件 init_watchdog(); // 关闭看门狗 init_clock(); // 设置系统时钟 init_stack(); // 设置临时栈指针 // 2. 检测启动介质 boot_device read_om_pins(); // 读取OM引脚电平 // 3. 从指定设备加载BL1 switch(boot_device) { case SDCARD: load_from_sd(BL1_START_BLOCK, BL1_SIZE, SRAM_ADDR); break; case NAND: load_from_nand(BL1_START_PAGE, SRAM_ADDR); break; // 其他启动方式... } // 4. 校验BL1镜像签名 if(verify_signature(SRAM_ADDR)) { jump_to(SRAM_ADDR); // 跳转到BL1执行 } else { halt_with_error(); // 校验失败则停止 } }需要特别注意的是不同芯片厂商的BL0实现可能有差异。比如我在使用TI的AM335x芯片时发现其BL0还会初始化部分外设时钟而全志的H3芯片则支持直接从SPI Flash启动。这些细节都需要查阅具体的芯片手册。3. BL1关键的第二阶段加载器3.1 BL1的存储与加载BL1通常由芯片厂商提供二进制文件如三星的E4412芯片会提供bl1.bin我们需要将其烧写到存储介质的特定位置。这个位置有严格规定例如SD卡必须放在第1个block512字节处NAND Flash必须放在第0页eMMC必须放在boot分区block0在我的一个车载项目中出现过启动失败的问题最后发现是因为BL1烧写位置偏移了4个字节。这种错误往往表现为启动卡在最初阶段连串口都没有输出。3.2 BL1的核心任务BL1运行在芯片内部的SRAM中它的主要工作可以用承上启下来概括关键硬件初始化设置CPU为SVC模式超级管理员模式关闭中断和看门狗定时器初始化DRAM控制器为加载BL2做准备加载BL2到内存// 伪代码BL1加载BL2的过程 void BL1() { init_uart(); // 初始化串口用于调试输出 puts(BL1: Initializing DRAM...); init_dram(); // 初始化DDR内存 puts(BL1: Loading BL2 from storage...); if (bl2_size 14KB) { load_bl2(BL2_SRC_ADDR, SRAM_BASE 16KB, bl2_size); } else { // 大BL2分两阶段加载 load_bl2(BL2_SRC_ADDR, SRAM_BASE 16KB, 14KB); load_bl2(BL2_SRC_ADDR 14KB, DRAM_BASE, bl2_size - 14KB); } puts(BL1: Jumping to BL2...); jump_to(DRAM_BASE); }安全校验验证BL2的完整性和签名防止恶意代码执行。我在金融设备项目中就遇到过因为BL2签名校验失败导致系统拒绝启动的情况。4. BL2全功能BootLoader阶段4.1 BL2的两种存在形式BL2就是我们通常编译生成的uboot.bin文件它有两种存在状态存储状态位于SD卡、NAND等存储介质中运行状态被BL1加载到DRAM中执行这里有个关键点BL2的链接地址编译时确定的运行地址必须与BL1实际加载的地址一致。我在早期项目中曾因为链接地址设置错误导致代码运行异常现象是串口有输出但很快跑飞。4.2 BL2的详细工作流程BL2的工作可以分为汇编和C语言两个阶段汇编阶段start.S设置异常向量表初始化关键寄存器关闭MMU和缓存设置临时栈指针跳转到C入口函数C语言阶段board.c// 简化版的BL2主流程 void bl2_main() { init_serial(); // 初始化串口控制台 init_clocks(); // 设置所有时钟 init_memory(); // 内存检测与初始化 init_storage(); // 初始化存储设备 load_environment(); // 加载环境变量 if (autoboot_enabled()) { load_kernel(); // 加载Linux内核 setup_bootargs(); // 设置启动参数 boot_kernel(); // 启动内核 } else { start_shell(); // 进入UBOOT命令行 } }在实际开发中BL2最常遇到的问题就是内存初始化。记得有一次在定制板卡上DDR3初始化参数不正确导致系统不稳定后来通过示波器捕捉内存时钟信号才找到问题根源。5. 三阶段协同工作机制5.1 地址空间的精密配合这三个阶段在内存中的分布就像精心设计的俄罗斯套娃阶段存储位置运行位置大小限制BL0芯片ROM芯片ROM64KB固定BL1外部存储介质芯片内部SRAM16KB典型BL2外部存储介质外部DRAM无严格限制5.2 典型问题排查技巧当启动过程出现问题时可以按照以下步骤排查确认BL0执行测量电源时序和复位信号用逻辑分析仪抓取启动引脚检查BL1加载在BL0最后添加调试代码输出加载状态到串口或GPIO验证BL2运行在BL2起始处添加特殊字符打印如BL2 START我曾经用这种方法解决过一个棘手的启动问题发现BL1能正常加载BL2但BL2就是不执行。最后发现是DDR初始化时序参数有误导致BL2虽然被加载但无法正常运行。6. 实际开发中的经验分享6.1 启动时间优化在汽车电子项目中启动速度是关键指标。通过以下优化我们将UBOOT启动时间从1.2秒缩短到400msBL1阶段简化不必要的硬件初始化使用更快的DRAM训练算法BL2阶段延迟初始化非必要外设并行化硬件初始化流程使用压缩镜像减少加载时间6.2 安全增强实践在IoT设备中我们实现了安全启动方案BL0验证BL1的RSA签名BL1验证BL2的SHA-256哈希BL2验证内核和文件系统的完整性这套方案配合OTP一次性可编程存储器存储密钥有效防止了固件被篡改。7. 不同芯片平台的差异虽然BL0/BL1/BL2的概念通用但不同芯片实现各有特点芯片平台BL0特点BL1要求BL2特殊考虑三星S5PV210支持多种启动介质必须包含8字节头信息需要重定位到DRAM高端TI AM335x支持SPI Flash启动使用MLO格式需配置DDR参数全志H3从eMMC boot分区启动需要sunxi-spl工具需要修改DRAM初始化代码NXP i.MX6支持高安全模式使用IVT头部结构需配置DCD表在跨平台移植时这些差异往往是最需要关注的。建议在移植前仔细阅读芯片的启动手册BootROM文档。8. 调试技巧与实用工具8.1 常用调试手段GPIO调试法在每个阶段开始和结束点设置不同的GPIO电平用示波器观察波形// 示例在BL1开始时点亮LED set_gpio(PIN_LED, HIGH);串口打印尽早初始化串口输出调试信息// 早期串口初始化示例 void early_uart_init() { // 简单配置UART时钟和引脚 writel(CLK_REG, 0x1234); writel(PINMUX_REG, 0x5678); // 基本UART配置 writel(UART_LCR, 0x3); // 8N1 writel(UART_FCR, 0x1); // 使能FIFO }JTAG调试在关键点设置断点单步跟踪代码执行8.2 实用工具推荐逆向分析工具IDA Pro分析厂商提供的BL1二进制binutilsobjdump反汇编查看代码逻辑烧写工具OpenOCD开源JTAG调试工具dd命令Linux下烧写SD卡镜像调试神器J-Link支持多种ARM芯片的调试器Saleae逻辑分析仪捕捉启动时序记得有一次用Saleae捕捉到BL0加载BL1时的SPI通信异常发现是时钟极性配置错误这种问题用传统调试方法很难发现。

更多文章