
U-Boot启动流程深度解析从汇编到C语言的3个关键阶段引言为什么需要理解U-Boot启动机制在嵌入式Linux开发中系统上电后的第一段运行代码就是Bootloader。作为连接硬件与操作系统的桥梁U-Boot的启动过程直接影响着整个系统的稳定性和性能表现。许多开发者在使用现成开发板时可能只需关注U-Boot命令操作但当面临自定义硬件设计、性能调优或疑难排查时深入理解U-Boot的启动机制就变得至关重要。本文将聚焦ARM架构下U-Boot从SoC上电到执行main_loop的完整生命周期重点剖析三个阶段的核心任务Stage1汇编阶段关中断/MMU/Cache的底层硬件准备Stage1.5重定位阶段代码从Flash到RAM的关键跳跃Stage2 C语言阶段外设初始化与内核引导的完整链条通过分析每个阶段的关键代码片段和硬件操作原理读者将获得调试U-Boot启动问题的系统化思路并为后续系统移植打下坚实基础。1. Stage1汇编阶段ARM核的安全模式初始化1.1 上电第一条指令的执行位置当i.MX6ULL处理器上电复位后会根据BOOT_MODE[1:0]引脚选择的启动方式从以下预定义地址读取第一条指令启动模式存储介质初始PC地址BOOT_MODE00FUSE启动由efuse指定BOOT_MODE01串行下载USB/UART接口BOOT_MODE10/11内部BootROM根据启动设备选择对于常见的SD卡启动方式BootROM会从SD卡的第1KB位置偏移量0x400加载U-Boot的SPLSecondary Program Loader而对于没有SPL的配置则直接从第17KB位置偏移量0x4200加载U-Boot主体。1.2 关键硬件模块的关闭顺序在arch/arm/cpu/armv7/start.S中U-Boot的启动代码会依次关闭以下硬件功能_start: /* 1. 关闭中断 */ cpsid i 禁止IRQ中断 cpsid f 禁止FIQ中断 /* 2. 关闭MMU和Cache */ mrc p15, 0, r0, c1, c0, 0 读取CP15的c1寄存器到r0 bic r0, r0, #0x00002000 清除bit13 -- 关闭VIC bic r0, r0, #0x00000007 清除bit2:0 -- 关闭MMU/D-Cache/I-Cache mcr p15, 0, r0, c1, c0, 0 写回c1寄存器 /* 3. 设置SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f 清除模式位 orr r0, r0, #0x13 设置为SVC模式 msr cpsr, r0关闭这些模块的原因如下表所示模块关闭原因相关寄存器中断避免启动过程中意外中断导致程序跑飞CPSR的I/F位MMU启动初期使用物理地址避免页表未配置导致的地址转换错误CP15 c1控制寄存器Cache防止缓存中可能存在的历史数据影响内存访问CP15 c1控制寄存器看门狗防止启动代码执行时间超过看门狗超时周期WDT_CR寄存器1.3 时钟与内存控制器的初始化在完成基本安全设置后U-Boot需要初始化SoC的核心时钟和内存控制器。以i.MX6ULL为例时钟树配置流程如下配置ARM核PLL将24MHz晶振时钟倍频到792MHz设置系统PLL生成528MHz的系统主频配置DDR时钟通常设置为400MHz外设时钟分频设置AHB、IPG等总线时钟DDR初始化则需要严格按照芯片手册的时序要求依次配置/* DDR控制器初始化示例 */ writel(0x00080000, DDRC_CR); // 设置控制寄存器 writel(0x00000004, DDRC_MRCTRL0); // 模式寄存器控制 writel(0x00000000, DDRC_TIMING1); // 配置时序参数1 writel(0x00000000, DDRC_TIMING2); // 配置时序参数2 mdelay(1); // 等待稳定关键点不同容量的DDR芯片需要配置不同的时序参数这些参数通常由芯片厂商提供或在开发板参考设计中给出。2. Stage1.5重定位阶段代码的自我搬运2.1 为什么需要重定位U-Boot在编译时会被链接到高地址如0x87800000但启动时可能运行在NOR Flash或SD卡等低速存储介质上。重定位Relocation的主要目的是提升执行速度将代码从Flash复制到RAM中运行地址空间对齐使运行地址与链接地址一致避免位置无关代码带来的性能损耗为内核腾出空间通常将U-Boot放在内存高端留出低端内存给内核2.2 重定位过程详解重定位的核心代码在arch/arm/lib/crt0.S中实现主要步骤包括计算目标地址ldr r0, _start 获取当前运行地址 ldr r1, CONFIG_SYS_TEXT_BASE 获取链接地址 sub r4, r1, r0 计算偏移量复制代码段ldr r2, __bss_start 代码段结束地址 add r2, r2, r4 调整为目标地址 copy_loop: ldmia r0!, {r9-r10} 从源地址加载 stmia r1!, {r9-r10} 存储到目标地址 cmp r1, r2 比较是否到达末尾 blo copy_loop设置新栈指针ldr sp, CONFIG_SYS_INIT_SP_ADDR 设置新栈地址跳转到新地址ldr pc, relocated_code 绝对跳转 relocated_code:2.3 重定位后的内存布局典型i.MX6ULL开发板完成重定位后的内存布局如下内存区域地址范围用途中断向量表0x87800000ARM异常处理入口.text代码段0x87800040U-Boot主体代码.rodata只读数据0x8781A000常量字符串和配置数据.data数据段0x8781E000已初始化全局变量.bss段0x87820000未初始化全局变量清零堆空间0x87840000malloc动态内存区栈空间0x87880000函数调用栈3. Stage2 C语言阶段从board_init_f到main_loop3.1 全局数据结构初始化在进入C语言环境后U-Boot首先初始化关键数据结构gd_tglobal datatypedef struct global_data { bd_t *bd; // 板级信息结构体 unsigned long flags; // 状态标志 unsigned long baudrate; // 串口波特率 unsigned long cpu_clk; // CPU时钟频率 unsigned long bus_clk; // 总线时钟频率 unsigned long mem_clk; // 内存时钟频率 unsigned long ram_size; // RAM大小 unsigned long env_addr; // 环境变量地址 // ...其他字段... } gd_t;初始化流程通过board_init_f函数实现void board_init_f(ulong boot_flags) { gd (gd_t *)CONFIG_SYS_INIT_SP_ADDR; // 设置gd指针 memset(gd, 0, sizeof(gd_t)); // 清零结构体 gd-flags | GD_FLG_RELOC; // 标记重定位完成 gd-baudrate CONFIG_BAUDRATE; // 设置默认波特率 gd-cpu_clk CONFIG_SYS_CLK_FREQ; // 设置CPU时钟 arch_cpu_init(); // 架构相关初始化 board_early_init_f(); // 板级早期初始化 serial_init(); // 串口初始化 // ...其他初始化... }3.2 外设初始化的顺序与技巧U-Boot的外设初始化遵循特定顺序以确保依赖关系正确时钟系统确认各总线时钟频率GPIO控制器配置复用功能和电气属性串口调试口确保后续调试信息输出存储设备初始化SD/MMC、NAND等网络PHY配置MAC和PHY芯片显示设备可选初始化LCD控制器以i.MX6ULL的串口初始化为例void uart_io_setup(void) { /* 配置UART1的TXD和RXD引脚 */ imx_iomux_v3_setup_pad(MX6_PAD_UART1_TX_DATA__UART1_DCE_TX | MUX_PAD_CTRL(UART_PAD_CTRL)); imx_iomux_v3_setup_pad(MX6_PAD_UART1_RX_DATA__UART1_DCE_RX | MUX_PAD_CTRL(UART_PAD_CTRL)); /* 设置UART控制器 */ writel(UCR1_UARTEN, uart-ucr1); // 使能UART writel(0x0000002B, uart-ucr2); // 8位数据无校验 writel(0x0000047F, uart-ufcr); // 分频设置 }3.3 环境变量与bootcmd处理U-Boot通过环境变量实现灵活的启动配置关键环境变量包括变量名典型值示例作用描述bootdelay3启动倒计时秒数bootcmdmmc dev 0; fatload mmc 0 80800000 zImage; bootz 80800000自动启动命令序列bootargsconsolettymxc0,115200 root/dev/mmcblk1p2 rootwait rw传递给内核的启动参数ipaddr192.168.1.100开发板IP地址serverip192.168.1.1TFTP服务器IP地址环境变量的存储位置由CONFIG_ENV_OFFSET定义通常在存储设备的保留区域。加载流程如下env_init() -- 初始化环境变量结构体 env_relocate() -- 从存储设备加载环境变量 env_get()/env_set() -- 读取/设置环境变量3.4 内核引导的完整链条当执行bootz或bootm命令时U-Boot完成以下操作加载内核镜像从存储设备读取内核到内存fatload mmc 0:1 0x80800000 zImage fatload mmc 0:1 0x83000000 dtb设置启动参数通过设备树传递硬件信息int fdt_chosen(void *fdt) { int node fdt_add_subnode(fdt, 0, chosen); fdt_setprop_string(fdt, node, bootargs, getenv(bootargs)); return 0; }ARM启动协议设置寄存器并跳转到内核mov r0, #0 必须为0 ldr r1, 0xFFFFFFFF 机器ID ldr r2, 0x83000000 设备树地址 ldr pc, 0x80800000 跳转到内核4. 调试技巧与常见问题分析4.1 关键调试手段串口日志分析修改include/configs/board.h中的CONFIG_BAUDRATE通过gd-flags判断启动阶段LED指示灯法void show_boot_stage(int stage) { gpio_set_value(LED1, stage 0x1); gpio_set_value(LED2, stage 0x2); gpio_set_value(LED3, stage 0x4); }内存检测工具mtest 0x80000000 0x800100004.2 典型问题排查表现象可能原因排查方法卡在Starting kernel设备树地址错误检查bootz参数和内存映射DDR初始化失败时序参数不匹配使用mw命令测试内存读写环境变量丢失存储介质损坏尝试env default -a恢复默认网络无法ping通PHY地址或复位信号配置错误检查mii info输出启动后立即复位看门狗未关闭在board_init_f中禁用看门狗5. 进阶话题U-Boot移植实战要点5.1 移植到新硬件平台的关键步骤创建板级目录cp -r board/freescale/mx6ullevk board/mycompany/myboard修改Kconfig配置config TARGET_MYBOARD bool Support myboard depends on ARCH_MX6ULL select BOARD_LATE_INIT适配设备树/ { model My Custom Board; compatible mycompany,myboard, fsl,imx6ull; memory80000000 { device_type memory; reg 0x80000000 0x20000000; }; };5.2 性能优化技巧缩短启动时间// 在include/configs/myboard.h中定义 #define CONFIG_SKIP_LOWLEVEL_INIT // 跳过已初始化的硬件 #define CONFIG_BOOTDELAY 1 // 减少等待时间压缩U-Boot镜像make u-boot.imx EXT_DTB/path/to/dtbs自定义SPL对于内存受限系统可以启用SPLSecondary Program LoaderCONFIG_SPLy CONFIG_SPL_BUILDy结语从理解到实践通过本文对U-Boot三个启动阶段的深度解析开发者应该能够准确定位启动过程中各阶段的执行点根据硬件特性合理调整初始化顺序和参数快速诊断常见的启动失败问题为自定义硬件平台进行必要的移植工作建议读者结合具体开发板的U-Boot源码通过添加调试打印、LED指示灯等方式实际观察启动流程这将极大提升对嵌入式系统启动过程的理解深度。