U-Boot启动流程与嵌入式系统引导解析

张开发
2026/6/9 14:37:25 15 分钟阅读
U-Boot启动流程与嵌入式系统引导解析
1. U-Boot 启动流程深度解析作为一名嵌入式开发者理解U-Boot的启动流程是基本功。U-Boot作为嵌入式系统中最重要的引导加载程序之一承担着硬件初始化、环境设置和内核加载等关键任务。本文将详细剖析U-Boot的完整启动过程从基本概念到两阶段启动机制再到内存管理和内核加载带你彻底掌握U-Boot的工作原理。1.1 U-Boot的基本概念U-Boot(Universal Boot Loader)是一种开源的引导加载程序主要用于嵌入式系统。当我们需要在硬件平台上运行操作系统时U-Boot就是不可或缺的桥梁。它的主要功能包括硬件初始化包括CPU、内存、时钟、外设等提供命令行接口用于调试和配置加载操作系统内核从存储设备读取内核映像并启动提供环境变量保存系统配置参数与裸机程序不同U-Boot为操作系统内核提供了一个标准化的运行环境。在实际项目中我们经常需要根据具体硬件平台对U-Boot进行定制和移植因此深入理解其启动流程至关重要。1.2 嵌入式系统中的存储器在深入U-Boot启动流程前我们需要先了解嵌入式系统中常见的存储器类型及其特性存储器类型特性在系统中的典型用途NOR Flash非易失性可直接执行代码(XIP)但写入需要先擦除存储U-Boot和内核映像NAND Flash非易失性容量大价格低但不能直接执行代码存储文件系统和大量数据SRAM静态随机存储器速度快无需刷新但容量小CPU内部缓存、暂存数据SDRAM动态随机存储器容量大需要定期刷新系统主内存运行程序在典型的ARM系统中这些存储器的分工如下NOR Flash存储U-Boot和内核映像上电后CPU从这里获取第一条指令SRAM作为初始运行环境特别是在NAND启动时作为临时缓冲区SDRAM系统主内存需要初始化后才能使用NAND Flash存储文件系统和用户数据注意现代嵌入式系统设计时需要考虑存储器的层次结构。NOR Flash虽然可以直接执行代码但成本和容量限制了它的使用。实际产品中我们通常采用NAND Flash SDRAM的组合通过U-Boot的初始化代码来建立完整的运行环境。2. U-Boot的链接与内存布局2.1 链接脚本分析U-Boot的链接脚本(u-boot.lds)定义了程序的内存布局。以ARM平台为例典型的链接脚本包含以下关键部分OUTPUT_FORMAT(elf32-littlearm, elf32-littlearm, elf32-littlearm) OUTPUT_ARCH(arm) ENTRY(_start) /* 指定程序入口点为_start符号 */ SECTIONS { . 0x00000000; /* 链接地址从0开始 */ . ALIGN(4); .text : { cpu/arm920t/start.o (.text) /* 首先放置start.S的代码 */ *(.text) /* 其他所有代码段 */ } .rodata : { *(.rodata) } /* 只读数据段 */ .data : { *(.data) } /* 可读写数据段 */ .got : { *(.got) } /* 全局偏移表 */ .u_boot_cmd : { *(.u_boot_cmd) } /* U-Boot命令表 */ .bss : { *(.bss) } /* 未初始化数据段 */ }链接脚本的关键点入口点为_start对应start.S中的汇编代码代码段首先放置start.o确保它位于镜像最前面各段按4字节对齐满足ARM架构要求定义了特殊的.u_boot_cmd段存放所有命令2.2 实际运行地址与重定位虽然链接地址设为0x00000000但实际运行时U-Boot会被加载到SDRAM的高端地址如0x33F80000。这种设计源于以下考虑上电时CPU从0地址开始执行但实际物理存储器可能是NOR Flash或SRAMU-Boot需要将自己从加载位置复制到SDRAM的高端地址运行高端地址布局为内核加载留出连续空间在config.mk中定义的TEXT_BASE决定了最终运行地址LDFLAGS -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)对于S3C2440平台TEXT_BASE通常设为0x33F80000对应64MB SDRAM的高端512KB空间。实际项目经验在移植U-Boot到新平台时TEXT_BASE的设置需要根据具体内存大小调整。一般原则是为内核保留足够的连续内存确保U-Boot自身有足够空间考虑内存对齐要求通常是1MB边界3. U-Boot第一阶段启动流程3.1 start.S分析第一阶段主要由汇编代码实现位于cpu/arm920t/start.S。其主要流程如下设置异常向量表_start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq设置CPU为SVC模式reset: mrs r0, cpsr bic r0, r0, #0x1f /* 清除模式位 */ orr r0, r0, #0xd3 /* 设为SVC模式禁用中断 */ msr cpsr, r0关闭看门狗和中断ldr r0, pWTCON mov r1, #0x0 str r1, [r0] /* 关闭看门狗 */ mov r1, #0xffffffff ldr r0, INTMSK str r1, [r0] /* 屏蔽所有中断 */设置时钟和关闭MMU/Cachecpu_init_crit: mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* 使I/D Cache失效 */ mcr p15, 0, r0, c8, c7, 0 /* 使TLB失效 */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 /* 清除控制位 */ bic r0, r0, #0x00000087 orr r0, r0, #0x00000002 /* 对齐检查 */ orr r0, r0, #0x00001000 /* I-Cache */ mcr p15, 0, r0, c1, c0, 0 /* 写入控制寄存器 */3.2 lowlevel_init.S分析lowlevel_init.S完成板级特定的初始化主要包括内存控制器初始化lowlevel_init: ldr r0, SMRDATA /* 内存控制器配置数据 */ ldr r1, _TEXT_BASE sub r0, r0, r1 /* 计算相对地址 */ ldr r1, BWSCON /* 内存控制器基址 */ add r2, r0, #13*4 /* 配置项数量 */ 0: ldr r3, [r0], #4 /* 读取配置值 */ str r3, [r1], #4 /* 写入寄存器 */ cmp r0, r2 bne 0bU-Boot重定位relocate: adr r0, _start /* 当前运行地址 */ ldr r1, _TEXT_BASE /* 目标地址 */ cmp r0, r1 /* 检查是否需要重定位 */ beq done_relocate ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* 计算U-Boot大小 */ copy_loop: ldmia r0!, {r3-r10} /* 从源地址读取8个字 */ stmia r1!, {r3-r10} /* 写入目标地址 */ cmp r0, r2 ble copy_loop设置堆栈和清除BSS段stack_setup: ldr r0, _TEXT_BASE sub r0, r0, #CFG_MALLOC_LEN sub r0, r0, #CFG_GBL_DATA_SIZE sub sp, r0, #12 /* 预留abort栈空间 */ clear_bss: ldr r0, _bss_start ldr r1, _bss_end mov r2, #0x00000000 clbss_l: str r2, [r0] /* 清除BSS段 */ add r0, r0, #4 cmp r0, r1 ble clbss_l跳转到第二阶段ldr pc, _start_armboot /* 跳转到C代码 */调试技巧在移植U-Boot时第一阶段的问题最难调试。建议使用点灯法在关键步骤设置LED状态通过串口输出调试信息需先初始化串口使用仿真器单步跟踪执行检查内存控制器配置是否正确4. U-Boot第二阶段启动流程4.1 全局数据结构初始化第二阶段从start_armboot()函数开始主要完成初始化全局数据结构gd和bdgd (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); memset((void*)gd, 0, sizeof(gd_t)); gd-bd (bd_t*)((char*)gd - sizeof(bd_t)); memset(gd-bd, 0, sizeof(bd_t));执行初始化序列for (init_fnc_ptr init_sequence; *init_fnc_ptr; init_fnc_ptr) { if ((*init_fnc_ptr)() ! 0) { hang (); } }初始化序列包括init_fnc_t *init_sequence[] { cpu_init, /* CPU相关初始化 */ board_init, /* 板级初始化 */ interrupt_init, /* 中断控制器初始化 */ env_init, /* 环境变量初始化 */ init_baudrate, /* 初始化串口波特率 */ serial_init, /* 串口初始化 */ console_init_f, /* 控制台初始化 */ display_banner, /* 显示U-Boot信息 */ dram_init, /* 配置可用RAM */ NULL, };4.2 设备初始化和环境设置内存分配器初始化mem_malloc_init(_armboot_start - CFG_MALLOC_LEN);环境变量重定位env_relocate();网络设置gd-bd-bi_ip_addr getenv_IPaddr(ipaddr);设备初始化devices_init(); /* 获取设备列表 */ jumptable_init(); console_init_r(); /* 完全初始化控制台 */ enable_interrupts(); /* 使能中断 */4.3 主循环与命令处理main_loop()函数实现U-Boot的交互式环境处理自动启动s getenv(bootcmd); if (!nobootdelay bootdelay 0 s !abortboot(bootdelay)) { run_command(s, 0); }命令处理循环for (;;) { len readline(CFG_PROMPT); if (len 0) strcpy(lastcommand, console_buffer); else if (len 0) flag | CMD_FLAG_REPEAT; if (len -1) puts(INTERRUPT\n); else rc run_command(lastcommand, flag); }命令执行流程cmd_tbl_t *cmdtp find_cmd(argv[0]); /* 查找命令 */ if (cmdtp NULL) { printf(Unknown command %s - try help\n, argv[0]); return 1; } rc cmd_call(cmdtp, flag, argc, argv); /* 执行命令 */4.4 内核加载流程典型的bootcmd环境变量设置setenv bootcmd nand read 0x30008000 0x60000 0x200000; bootm 0x30008000内核加载主要步骤从存储设备(NAND/NOR/MMC等)读取内核映像到内存解析映像格式(如uImage)设置启动参数(ATAGs或设备树)跳转到内核入口点实际项目经验内核加载失败常见原因内存地址冲突U-Boot与内核重叠映像格式不正确未使用mkimage处理启动参数未正确传递检查ATAGs或设备树存储设备读取错误校验NAND ECC等5. U-Boot移植与调试技巧5.1 移植关键步骤建立板级目录和配置文件修改存储器配置NOR/NAND/SDRAM调整时钟和总线设置实现板级初始化函数添加设备驱动支持测试基础功能串口、内存、存储5.2 常见问题排查启动停滞在Starting kernel...检查内核入口地址是否正确确认机器ID(arch_number)匹配验证启动参数传递内存初始化失败检查SDRAM型号和配置参数验证时序参数刷新率、CAS延迟等使用内存测试命令如mtest环境变量丢失确认环境存储位置NOR/NAND/EEPROM检查环境区大小定义(CFG_ENV_SIZE)验证擦写函数实现网络功能异常检查PHY地址和复位电路验证MAC地址设置测试PHY寄存器读写5.3 性能优化建议启用CPU缓存和MMU优化存储器访问时序减少不必要的设备初始化使用更快的启动方式如SPI NOR裁剪不需要的功能和命令通过本文的详细分析你应该对U-Boot的启动流程有了全面理解。在实际项目中建议结合具体硬件平台参考U-Boot源码使用仿真器和调试工具进行验证。记住U-Boot移植是一个需要耐心和细致的过程每个硬件平台都有其独特性理解基本原理才能快速解决问题。

更多文章