U-Boot启动流程深度解析:从复位到内核移交

张开发
2026/5/8 6:39:51 15 分钟阅读

分享文章

U-Boot启动流程深度解析:从复位到内核移交
1. U-Boot 启动流程深度解析从硬件复位到内核移交的全链路工程实践嵌入式系统启动过程是软硬件协同设计的集中体现其可靠性与效率直接决定产品交付质量。U-Boot 作为工业级嵌入式引导加载程序Bootloader其启动流程并非简单的代码顺序执行而是一套经过精密时序控制、内存空间规划与状态迁移的工程化实现。本文以 ARM920T 架构的 S3C2440 处理器平台为基准结合典型 NOR Flash SDRAM 硬件配置系统性剖析 U-Boot 启动全过程。所有分析均基于实际可运行的源码结构与硬件约束不引入任何平台无关假设目标是使读者在完成硬件原理图设计后能独立完成 Bootloader 移植与调试。1.1 引导加载程序的本质定位Bootloader 是嵌入式系统上电后的第一段可信固件其核心使命是在操作系统内核获得 CPU 控制权前构建一个受控、可预测、功能完备的运行环境。这一过程包含三个不可分割的工程目标硬件抽象层初始化解除处理器复位状态关闭看门狗、禁用中断、配置时钟树、初始化内存控制器执行环境重构将自身代码从只读存储介质如 NOR Flash搬运至可读写高速内存如 SDRAM建立堆栈、清零 BSS 段、初始化全局数据结构内核移交准备加载内核镜像与设备树或 ATAGs、设置启动参数、校验完整性、跳转至内核入口点。U-BootUniversal Boot Loader作为开源 Bootloader 的代表其设计哲学强调“可移植性”与“可配置性”。它通过分层架构CPU 通用层 / 板级支持包 BSP / 驱动抽象层解耦硬件差异使同一份核心代码可在不同 SoC 上运行。理解其启动流程本质是理解嵌入式系统从“裸金属”走向“软件定义”的关键跃迁路径。1.2 存储体系与启动介质选型工程依据启动流程的物理基础是存储器的电气特性与访问机制。S3C2440 平台涉及四类关键存储资源其选型决策直接影响 Bootloader 架构设计存储类型特性访问能力启动角色工程约束NOR Flash非易失、随机读取、写/擦除需特殊时序可直接取指执行XIP存储 Bootloader 初始代码前 4KB Steppingstone 或完整镜像写操作需先擦除扇区.data/.bss段无法驻留成本高、容量小NAND Flash非易失、块寻址、无地址总线无法直接取指需 CPU 通过 NAND 控制器读取存储内核、根文件系统等大容量数据启动需依赖内部 SRAM 加载引导代码存在坏块管理需求SRAM易失、静态保持、无需刷新上电即读写无需初始化提供启动初期最小运行环境堆栈、临时变量容量极小S3C2440 为 4KB Steppingstone成本高昂SDRAM易失、动态刷新、行列复用地址需初始化 DDR 控制器后方可访问运行完整 U-Boot、内核及用户空间的主要内存上电后必须完成时序配置CAS Latency, tRCD, tRP 等容量大、成本低关键工程推论NOR Flash 支持 XIPeXecute In Place是其作为首选启动介质的核心优势但.data和.bss段的写需求迫使 Bootloader 必须在启动早期将自身重定位至 RAM。S3C2440 的 Steppingstone 机制上电自动将 NAND 前 4KB 加载至片内 SRAM解决了 NAND 无法直接启动的难题但该 SRAM 仅够运行极简初始化代码后续仍需将完整 U-Boot 搬运至 SDRAM。SDRAM 初始化是启动流程中不可绕过的硬性步骤。未完成 DDR 控制器寄存器配置前任何对 SDRAM 的读写操作均会导致总线错误Bus Error或不可预测行为。1.3 链接脚本内存布局的顶层设计编译器生成的目标代码需被精确映射到物理地址空间此任务由链接脚本.lds文件完成。它定义了各代码段.text,.rodata,.data,.bss的起始地址、对齐方式及装载顺序是启动流程的逻辑起点。以u-boot/board/mini2440/u-boot.lds为例其核心结构如下OUTPUT_FORMAT(elf32-littlearm, elf32-littlearm, elf32-littlearm) OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . 0x00000000; /* 链接起始地址0x0 */ . ALIGN(4); .text : { cpu/arm920t/start.o (.text) /* 第一执行代码start.S */ cpu/arm920t/s3c24x0/nand_read.o (.text) /* NAND 读取支持 */ *(.text) /* 其余代码段 */ } . ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . ALIGN(4); .data : { *(.data) } . ALIGN(4); .got : { *(.got) } . .; __u_boot_cmd_start .; .u_boot_cmd : { *(.u_boot_cmd) } /* U-Boot 命令表存放区 */ __u_boot_cmd_end .; . ALIGN(4); __bss_start .; .bss (NOLOAD) : { *(.bss) . ALIGN(4); } __bss_end .; }工程意义解析ENTRY(_start)指定_start符号为程序入口点该符号位于start.S文件头部确保上电后第一条指令即执行汇编初始化代码。.text段首项强制为start.o保证start.S中的异常向量表与复位处理逻辑位于镜像最前端。.u_boot_cmd段显式收集所有命令函数指针为后续find_cmd()提供连续内存区域避免哈希查找开销。.bss (NOLOAD)属性告知链接器该段不占用镜像文件空间仅在运行时由 Bootloader 主动清零节省 Flash 存储。地址空间冲突的规避策略 尽管链接脚本指定.text起始于0x00000000但实际运行地址由config.mk中的TEXT_BASE宏定义如0x33F80000覆盖。这是因为S3C2440 的内存映射中0x30000000–0x33FFFFFF为 SDRAM 地址空间TEXT_BASE0x33F80000表示 U-Boot 重定位后的最终运行地址预留0x33F80000–0x33FFFFFF512KB作为代码与数据区链接脚本中的0x0仅用于 Steppingstone 模式下前 4KB 代码的相对寻址计算非真实运行地址。1.4 启动流程第一阶段汇编级硬件初始化第一阶段Stage 1完全由 ARM 汇编语言编写运行于 Flash 或 Steppingstone SRAM目标是建立最简运行环境并完成向 RAM 的迁移。其主干流程在cpu/arm920t/start.S中实现。1.4.1 异常向量表与复位入口ARM 处理器复位后PC 指针强制指向0x00000000或0xFFFF0000取决于CONFIG_SYS_ARM_WITH_VIRT配置。此处必须放置异常向量表_start: b start_code /* 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 _undefined_instruction: .word undefined_instruction _software_interrupt: .word software_interrupt /* ... 其他向量定义 ... */工程要点b start_code是唯一被复位触发的跳转其余向量在未注册中断服务程序前不会执行所有向量均为 32 位字确保 4 字节对齐符合 ARM 指令集要求向量表位置由链接脚本. 0x00000000保证是硬件强制约定。1.4.2 CPU 模式切换与中断屏蔽复位后 CPU 处于SVC32Supervisor模式但需显式确认并禁用中断start_code: mrs r0, cpsr /* 读取当前 CPSR */ bic r0, r0, #0x1f /* 清除 M[4:0] 模式位 */ orr r0, r0, #0xd3 /* 设置 SVC 模式 禁用 IRQ/FIQ (I1,F1) */ msr cpsr, r0 /* 写回 CPSR */模式选择依据SVC模式提供完整的寄存器访问权限含影子寄存器r13_svc,r14_svc满足硬件寄存器操作需求USR模式因权限受限无法访问协处理器CP15及内存控制器寄存器故不可用IRQ/FIQ模式为中断响应专用启动初期无中断服务程序无需提前进入。1.4.3 关键外设初始化此阶段需快速解除硬件锁定状态看门狗禁用ldr r0, pWTCON /* S3C2440 看门狗控制寄存器地址 */ mov r1, #0x0 str r1, [r0] /* 写 0 禁用看门狗 */中断全局屏蔽mov r1, #0xffffffff ldr r0, INTMSK /* 主中断屏蔽寄存器 */ str r1, [r0] ldr r1, 0x7ff /* 辅助中断屏蔽值修正原文笔误 */ ldr r0, INTSUBMSK str r1, [r0]MMU 与 Cache 关闭mrc p15, 0, r0, c1, c0, 0 /* 读 CP15 控制寄存器 c1 */ bic r0, r0, #0x00002300 /* 清除 V, R, S 位 */ bic r0, r0, #0x00000087 /* 清除 B, C, A, M 位 */ orr r0, r0, #0x00000002 /* 设置 A 位对齐检查 */ orr r0, r0, #0x00001000 /* 设置 I 位指令 Cache */ mcr p15, 0, r0, c1, c0, 0 /* 写回 c1关闭 MMU/CACHE */关闭原因Cache 在未初始化内存控制器前可能缓存无效数据MMU 未配置页表时启用将导致地址转换失败。二者均需在 SDRAM 初始化完成后才可安全启用。1.4.4 SDRAM 初始化lowlevel_init.S核心逻辑lowlevel_init.S承担 SDRAM 控制器寄存器配置任务其关键在于正确设置内存时序参数。S3C2440 的 BWSCONBank Width Wait Status Control与 BANKCONx 寄存器决定了 SDRAM 访问时序。lowlevel_init: ldr r0, SMRDATA /* 加载 SMRDATA 数据表地址 */ ldr r1, _TEXT_BASE /* 获取 TEXT_BASE0x33F80000 */ sub r0, r0, r1 /* 计算相对于当前运行地址的偏移 */ ldr r1, BWSCON /* BWSCON 寄存器基址 */ add r2, r0, #13*4 /* SMRDATA 共 13 个 word */ 0: ldr r3, [r0], #4 /* 逐字读取 SMRDATA */ str r3, [r1], #4 /* 逐字写入 BWSCON/BANKCONx */ cmp r2, r0 bne 0b mov pc, lr SMRDATA: .word ((B1_BWSCON4)(B2_BWSCON8)...(B7_BWSCON28)) .word ((B0_Tacs13)(B0_Tcos11)...(B0_PMC)) /* ... 其余 11 行时序参数 ... */ .word ((REFEN23)(TREFMD22)...REFCNT) /* 刷新计数器 */ .word 0x32 /* BANKSIZE */ .word 0x30 /* MRSRB6 */ .word 0x30 /* MRSRB7 */时序参数含义以B0_Tacs为例TacsAddress Set-up Time地址建立时间单位为 HCLK 周期TcosChip Select Set-up Time片选建立时间TaccAccess Cycle Time访问周期TrpRow Precharge Time行预充电时间TrcRow Cycle Time行周期REFCNTRefresh Counter刷新计数器决定 SDRAM 自刷新间隔。这些值需严格依据所用 SDRAM 芯片如 K4S561632N的数据手册计算得出任何偏差都将导致内存读写失败。1.4.5 代码重定位与堆栈建立SDRAM 初始化完成后U-Boot 将自身从 Flash 搬运至 SDRAM 运行#ifndef CONFIG_SKIP_RELOCATE_UBOOT adr r0, _start /* 当前代码起始地址Flash */ ldr r1, _TEXT_BASE /* 目标地址SDRAM */ cmp r0, r1 /* 检查是否已在目标地址运行 */ beq done_relocate ldr r2, _armboot_start /* _armboot_start _start */ ldr r3, _bss_start /* _bss_start end of code */ sub r2, r3, r2 /* r2 代码长度 */ add r2, r0, r2 /* r2 源结束地址 */ copy_loop: ldmia r0!, {r3-r10} /* 多寄存器加载4字对齐 */ stmia r1!, {r3-r10} /* 多寄存器存储 */ cmp r0, r2 ble copy_loop #endif堆栈指针SP设置stack_setup: ldr r0, _TEXT_BASE /* 0x33F80000 */ sub r0, r0, #CFG_MALLOC_LEN /* malloc 区 */ sub r0, r0, #CFG_GBL_DATA_SIZE /* global data 区 */ #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ CONFIG_STACKSIZE_FIQ) #endif sub sp, r0, #12 /* 保留 3 个字用于 abort stack */CFG_MALLOC_LEN通常 128KB与CFG_GBL_DATA_SIZEgd_t结构体大小约 36 字节共同构成 U-Boot 运行时内存池sp指向其顶部为 C 语言函数调用提供栈空间。1.4.6 BSS 段清零与第二阶段跳转BSS 段存放未初始化全局/静态变量需在使用前清零clear_bss: ldr r0, _bss_start ldr r1, _bss_end mov r2, #0 clbss_l: str r2, [r0] add r0, r0, #4 cmp r0, r1 ble clbss_l最后跳转至 C 语言主函数start_armbootldr pc, _start_armboot _start_armboot: .word start_armboot此为绝对跳转pc直接加载start_armboot的链接地址0x33F80000 offset标志着第一阶段结束控制权移交 C 运行时环境。2. 启动流程第二阶段C 语言环境构建与内核移交第二阶段Stage 2在 SDRAM 中以 C 语言为主执行完成系统级初始化、命令行环境搭建及内核加载。其入口函数start_armboot()位于lib_arm/board.c是整个 Bootloader 的“心脏”。2.1 全局数据结构GD与板级信息BD初始化U-Boot 使用两个核心数据结构管理运行时状态gd_tGlobal Data全局数据结构存放运行时关键参数波特率、重定位偏移、环境变量地址等bd_tBoard Data板级信息结构存放硬件配置DRAM 大小、MAC 地址、IP 地址等。二者在内存中紧邻布局gd指向bd的上方DECLARE_GLOBAL_DATA_PTR; // 声明 gd 为 r8 寄存器变量 void start_armboot(void) { gd (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)); // ... }内存布局示意图以TEXT_BASE0x33F80000为例0x33F80000 → ------------------ ← _armboot_start (U-Boot 代码起始) | U-Boot Code | ------------------ | U-Boot Data | ------------------ 0x33F70000 → | malloc pool | ← _armboot_start - CFG_MALLOC_LEN (128KB) ------------------ 0x33F6FC00 → | gd_t | ← gd (sizeof(gd_t) ≈ 36 bytes) ------------------ 0x33F6FBDC → | bd_t | ← gd-bd (sizeof(bd_t) ≈ 256 bytes) ------------------ 0x33F6FB00 → | Stack (SP) | ← sp (向下增长)DECLARE_GLOBAL_DATA_PTR宏将gd绑定至r8寄存器避免频繁内存访问提升性能。2.2 初始化序列init_sequence模块化硬件配置start_armboot()通过函数指针数组init_sequence[]串行调用各初始化函数init_fnc_t *init_sequence[] { cpu_init, /* CPU 核心初始化CP15 配置 */ board_init, /* 板级初始化时钟、GPIO、串口引脚 */ interrupt_init, /* 中断控制器初始化VIC */ env_init, /* 环境变量初始化从 Flash 加载 */ init_baudrate, /* 波特率设置从环境变量或默认值 */ serial_init, /* 串口驱动初始化 */ console_init_f, /* 控制台第一阶段初始化仅输出 */ display_banner, /* 打印 U-Boot 启动横幅 */ dram_init, /* DRAM 容量探测读写测试 */ display_dram_config, /* 打印 DRAM 配置信息 */ NULL, };关键初始化函数解析dram_init()通过向 SDRAM 地址写入特定模式如0x55AA55AA并读回验证探测可用 DRAM 容量与地址范围结果存入gd-bd-bi_dram[0].sizeenv_init()若环境变量存储于 Flash需先初始化 Flash 驱动再从预设地址如0x33F00000加载环境变量到 RAMconsole_init_f()仅初始化串口发送功能确保printf()可用为后续调试提供基础输出能力。2.3 命令行环境与主循环main_loop初始化完成后U-Boot 进入交互式命令行环境核心逻辑在common/main.c的main_loop()中void main_loop(void) { static char lastcommand[CFG_CBSIZE] {0}; int len; int rc 1; char *s; s getenv(bootdelay); // 读取环境变量 bootdelay int bootdelay s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; s getenv(bootcmd); // 读取启动命令 if (!nobootdelay bootdelay 0 s !abortboot(bootdelay)) { run_command(s, 0); // 自动执行 bootcmd } 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); else rc run_command(lastcommand, flag); } }命令执行流程run_command()调用builtin_run_command()进行宏展开与参数解析cmd_process()通过find_cmd(argv[0])在.u_boot_cmd段中查找命令函数指针cmd_call()执行具体命令函数如do_bootm()加载内核。2.4 内核加载与移交do_bootm的工程实现内核加载由do_bootm命令完成其核心步骤为镜像解析识别 uImage带 U-Boot 头部或 zImage 格式校验 CRC内存准备根据内核要求将镜像解压/拷贝至指定加载地址如0x30007FC0参数传递构造 ATAGs 或设备树DTB并置于内存指定位置跳转执行禁用中断设置r00,r1machine_id,r2atags/dtb_addr执行mov pc, r4r4指向内核入口。// 简化版 do_bootm 流程 if (image_check_magic(hdr)) { // 验证 uImage 头部 image_copy_to_ram(hdr, load_addr); // 拷贝内核 if (image_check_type(hdr) IH_TYPE_KERNEL) { kernel_entry (void (*)(int, int, void*))load_addr; kernel_entry(0, machid, bd-bi_boot_params); // 跳转 } }至此U-Boot 完成其历史使命CPU 控制权完全移交 Linux 内核启动流程终结。3. 实践要点与常见问题诊断3.1 启动失败的典型现象与定位方法现象可能原因诊断手段串口无任何输出串口时钟配置错误、TX 引脚未使能、电平不匹配示波器测 TX 引脚是否有波形检查board_init()中GPHCON配置卡在Starting kernel ...内核加载地址错误、ATAGs 地址无效、内核未压缩用md.b命令检查加载地址内存内容验证bootargs中mem参数SDRAM 初始化失败时序参数与芯片手册不符、PCB 信号完整性差修改SMRDATA中Tacc/Trp值逐步放宽用逻辑分析仪捕获 SDRAM 信号环境变量丢失Flash 驱动未初始化、环境变量分区损坏printenv命令返回空saveenv后重启验证3.2 性能优化关键点重定位加速在copy_loop中使用ldmia/stmia多寄存器传输比单条ldr/str快 3-4 倍BSS 清零优化对齐到 64 位边界后使用stmia一次写 8 字节Flash 读取优化NOR Flash 启用CFI接口的Read Buffer模式提升连续读取速度。3.3 安全启动扩展考量现代产品需考虑安全启动Secure BootU-Boot 可通过以下方式增强签名验证在do_bootm前调用verify_signature()验证内核镜像 RSA 签名TrustZone 集成将bl31ARM Trusted Firmware作为 BL31 加载U-Boot 作为 BL33 运行于 Secure WorldOTP 锁定将公钥哈希写入 SoC OTP 区域防止恶意固件刷写。U-Boot 启动流程的每一个环节都是硬件工程师与固件工程师协同工作的结晶。从start.S中对 CPSR 寄存器的精确操控到u-boot.lds里对.got段的显式声明再到board.c中对gd_t内存布局的严谨设计无不体现嵌入式开发对底层细节的极致追求。掌握此流程不仅意味着能调试一块开发板更意味着具备了构建任何 ARM 架构嵌入式系统启动框架的能力——这正是硬件工程师核心竞争力的基石。

更多文章