第三章:启动流程与Bootloader

嵌入式系统上电后,到底发生了什么?

很多刚入行的朋友问我:为什么我的程序烧进去,板子就是不跑?其实,问题往往出在启动流程上。今天我就带大家把这块彻底搞明白。

3.1 嵌入式系统上电启动流程

先说说整体流程。一个典型的嵌入式系统,从上电到运行用户程序,大致要经历这么几个阶段:

  1. 上电复位:硬件自动复位,CPU从复位向量地址取第一条指令
  2. ROM代码执行:芯片内部固化的启动代码开始运行
  3. Bootloader加载:从存储介质(NAND、eMMC、SPI Flash等)加载Bootloader
  4. Bootloader初始化硬件:设置时钟、DDR、串口等
  5. 加载操作系统:从存储介质读取内核镜像到内存
  6. 跳转到内核:将控制权交给操作系统

嗯,这里要注意:不同芯片的启动顺序可能略有差异。比如有些MCU直接从内部Flash启动,省去了Bootloader这一步。但ARM Cortex-A系列处理器,基本都遵循这个套路。

核心要点:启动流程的本质,就是从「硬」到「软」的过渡。硬件负责把第一段代码拉起来,软件负责把剩下的活儿干完。

我在项目中遇到过一个问题:某款新板子,上电后串口死活没输出。查了半天,发现是复位电路的上电时序不满足芯片要求。说白了,硬件没准备好,软件再牛也白搭。

3.2 Bootloader的作用

Bootloader是什么?你可以把它理解成「操作系统的助产士」。它自己不是操作系统,但负责把操作系统安全地「生」出来。

具体来说,Bootloader干这几件事:

  • 硬件初始化:设置CPU模式、关闭看门狗、初始化时钟和DDR
  • 建立内存映射:让CPU能正确访问外设和内存
  • 加载内核:从Flash、网络或SD卡读取内核镜像
  • 传递启动参数:告诉内核硬件配置信息(如内存大小、控制台设备)
  • 提供交互界面:允许开发者调试、修改启动参数

你想想看,如果没有Bootloader,每次改个内核参数都得重新烧录整个系统,那得多痛苦?

3.3 U-Boot:最流行的Bootloader

说到Bootloader,就不得不提U-Boot。它支持几乎所有主流架构:ARM、x86、RISC-V、MIPS……我这些年做过的项目,十有八九用的都是它。

U-Boot的全称是「Universal Bootloader」,通用性确实很强。但通用也意味着复杂。我第一次接触U-Boot时,光看它的目录结构就花了一整天。

3.3.1 U-Boot的目录结构

目录/文件 说明
arch/ 架构相关代码(arm、x86、riscv等)
board/ 板级相关代码(不同开发板)
common/ 通用功能模块(命令解析、环境变量等)
drivers/ 设备驱动(串口、网卡、存储等)
include/ 头文件和配置文件
net/ 网络协议栈(TFTP、NFS等)
fs/ 文件系统支持(ext4、fat、ubifs等)

我个人习惯,拿到一个新板子,先看 board/ 和 include/configs/ 这两个目录。前者告诉你板子怎么初始化,后者告诉你功能怎么配。

3.3.2 U-Boot的启动流程

U-Boot的启动分两个阶段:

第一阶段(SPL / TPL):

  • 在SRAM或内部ROM中运行
  • 初始化最基本的硬件(时钟、DDR控制器)
  • 加载第二阶段的U-Boot到DDR
  • 代码量很小,通常几十KB

第二阶段(U-Boot proper):

  • 在DDR中运行
  • 初始化所有外设(网卡、存储、USB等)
  • 进入命令行交互模式
  • 加载并启动内核

避坑指南:我曾经在调试一块新板子时,SPL阶段死活过不去。后来发现是DDR初始化参数配错了。DDR的时序参数非常敏感,建议直接用芯片厂商提供的参考值,别自己瞎调。

3.4 U-Boot的编译与烧录

编译U-Boot其实不复杂,但有几个坑要注意。我直接说步骤:

3.4.1 编译U-Boot

# 1. 配置(以qemu-arm为例)
make qemu_arm_defconfig

# 2. 编译
make -j4

# 3. 编译产物
# u-boot.bin    —— 完整的U-Boot镜像
# u-boot.img    —— 带U-Boot头部的镜像
# spl/u-boot-spl.bin —— SPL镜像

为什么会有这么多镜像文件?因为不同启动介质需要的格式不一样。比如从SD卡启动,可能需要 u-boot-spl.bin 加上 u-boot.img。

注意:交叉编译工具链一定要选对。ARMv7和ARMv8的工具链不通用。我见过有人用aarch64的工具链编译ARMv7的U-Boot,结果链接阶段报一堆错。

3.4.2 烧录U-Boot

烧录方式取决于你的板子支持什么。常见的有:

  • SD卡烧录:用 dd 命令写入SD卡
  • 网络烧录:通过TFTP下载到内存再写入Flash
  • JTAG烧录:用调试器直接烧录到Flash
  • 串口烧录:通过XModem/YModem协议传输

以SD卡烧录为例:

# 假设SD卡设备是 /dev/sdb
# 注意:一定要确认设备名,别把硬盘给写了!

# 烧录SPL到偏移1KB处
sudo dd if=spl/u-boot-spl.bin of=/dev/sdb bs=1k seek=1

# 烧录U-Boot到偏移128KB处
sudo dd if=u-boot.img of=/dev/sdb bs=1k seek=128

# 同步并退出
sync

这里有个细节:为什么从1KB开始写?因为SD卡的前面部分(MBR、分区表)不能被覆盖。偏移量具体是多少,要看芯片的启动手册。

经验之谈:我建议第一次烧录时,先用 hexdump 或 xxd 检查一下烧录位置是否正确。别问我为什么这么建议——我曾经把SD卡的分区表覆盖了,整张卡废掉,数据全丢。

3.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

嵌入式系统启动流程与Bootloader知识体系 上电复位 ROM代码执行 Bootloader加载 SPL阶段 U-Boot proper 硬件初始化、命令行 加载并启动内核 关键说明 • 复位向量地址由芯片决定 • ROM代码由芯片厂商固化 • SPL在SRAM中运行 • U-Boot proper在DDR中运行 • 内核镜像从存储介质加载 • 启动参数通过设备树传递 常见启动介质: • NAND Flash • eMMC • SPI Flash • SD卡 • 网络(TFTP) 图3-1 嵌入式系统启动流程与Bootloader知识体系

这张图把整个启动流程串起来了。从上电复位开始,经过ROM代码、SPL、U-Boot proper,最后到内核启动。每一步都有它存在的理由,少一环都不行。

好了,关于启动流程和Bootloader,今天就聊这么多。记住一句话:Bootloader不是操作系统,但它是操作系统能跑起来的前提。把这个搞透了,后面移植内核、调试驱动都会顺手很多。

个人建议:初学者可以先拿QEMU模拟器练手,不用买开发板也能跑U-Boot。等流程走通了,再上真实硬件。这样效率高,还省钱。

可可精品资料库