第三章:启动流程与Bootloader
嵌入式系统上电后,到底发生了什么?
很多刚入行的朋友问我:为什么我的程序烧进去,板子就是不跑?其实,问题往往出在启动流程上。今天我就带大家把这块彻底搞明白。
3.1 嵌入式系统上电启动流程
先说说整体流程。一个典型的嵌入式系统,从上电到运行用户程序,大致要经历这么几个阶段:
- 上电复位:硬件自动复位,CPU从复位向量地址取第一条指令
- ROM代码执行:芯片内部固化的启动代码开始运行
- Bootloader加载:从存储介质(NAND、eMMC、SPI Flash等)加载Bootloader
- Bootloader初始化硬件:设置时钟、DDR、串口等
- 加载操作系统:从存储介质读取内核镜像到内存
- 跳转到内核:将控制权交给操作系统
嗯,这里要注意:不同芯片的启动顺序可能略有差异。比如有些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 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
这张图把整个启动流程串起来了。从上电复位开始,经过ROM代码、SPL、U-Boot proper,最后到内核启动。每一步都有它存在的理由,少一环都不行。
好了,关于启动流程和Bootloader,今天就聊这么多。记住一句话:Bootloader不是操作系统,但它是操作系统能跑起来的前提。把这个搞透了,后面移植内核、调试驱动都会顺手很多。
个人建议:初学者可以先拿QEMU模拟器练手,不用买开发板也能跑U-Boot。等流程走通了,再上真实硬件。这样效率高,还省钱。
可可精品资料库