一、U-Boot 是什么?
U-Boot 是一个裸机程序 ,属于 bootloader 的一种。它的核心作用是引导 Linux 或其他操作系统 ,最主要的工作是初始化 DDR 内存,因为操作系统内核需要在 DDR 中运行。
二、系统整体启动流程
芯片上电
↓
BootROM(芯片内部ROM)
↓
BL1 / SPL(片上SRAM)
↓
BL2 / U-Boot(DDR)
↓
Linux Kernel(DDR)
↓
Rootfs(存储设备)
| 阶段 | 运行位置 | 主要作用 |
|---|---|---|
| BootROM | 芯片内部ROM | 启动第一级程序(芯片厂商固化,不可修改) |
| BL1 / SPL | 片上SRAM | 初始化基础硬件(时钟、DDR),为加载完整U-Boot做准备 |
| BL2 / U-Boot | DDR | 完成全部系统初始化,引导Linux内核 |
| Kernel | DDR | 启动Linux操作系统 |
| Rootfs | 存储设备(eMMC/NAND/SD等) | 提供用户空间和应用程序运行环境 |
三、基本引导流程详解(多级启动)
1. BootROM(芯片厂商固化)
- 位置:芯片内部ROM,物理不可修改。
- 上电后CPU首先执行BootROM,不依赖任何外部存储。
- 主要工作 :
- CPU最基础初始化:复位处理、设置基本运行状态、初始化关键寄存器。
- 根据配置引脚或启动介质选择器,查找并确定启动设备(SPI Flash、NAND Flash、eMMC、SD卡等)。
- 从选定的启动设备中加载第一级Bootloader(BL1/SPL) 到内部SRAM,然后跳转执行。
2. 为什么需要多级启动?
内部SRAM容量有限(通常几十KB~几百KB),而DDR在启动初期尚未初始化。 因此需要分阶段加载,先用小体积的SPL初始化DDR,再将完整的U-Boot加载到DDR中运行。
3. 启动链
ROM → SPL → U-Boot → Linux
(BL1) (BL2)
每一级负责加载并验证(可选)下一级,将控制权逐级传递。
四、U-Boot 的代码执行阶段
U-Boot 被 SPL 加载到DDR后,其内部执行流程分为以下四个阶段:
① 汇编阶段 (start.S)
↓
② C语言阶段(重定位前)(board_init_f)
↓
③ 重定位阶段 (relocate_code)
↓
④ C语言阶段(重定位后)(board_init_r → main_loop)
① 汇编阶段(start.S)
- 入口文件 :
arch/arm/cpu/armv8/start.S(以ARM64为例) - 主要工作 :
- 设置异常向量表
- 关闭中断、关闭MMU、关闭Cache
- 初始化C运行环境(设置栈指针)
- 最终跳转 :
board_init_f()
② C语言阶段------重定位前(board_init_f)
- 入口函数 :
common/board_f.c中的board_init_f() - 阶段特点:此时U-Boot已在运行,但尚未搬到DDR中的最终运行地址。
- 主要任务 :
- 初始化串口、定时器等基础硬件
- 初始化全局数据结构(GD,Global Data),包含CPU信息、内存信息、环境变量地址、重定位地址等
- 规划内存布局,计算U-Boot在DDR顶部的重定位目标地址
- 结束 :调用
relocate_code()进入重定位
③ 重定位阶段(relocate_code)
- 主要工作 :
- 将U-Boot代码从当前运行位置(通常为SRAM或DDR底部临时区域)搬移到DDR中规划好的最终运行地址(通常在高地址处)
- 修正代码中的重定位地址偏移
- 更新运行环境(如全局数据结构中的指针)
- 跳转到新地址继续执行
重定位的目的:将U-Boot放到DDR的高端地址,把低端内存留给内核使用,避免地址冲突。
④ C语言阶段------重定位后(board_init_r → main_loop)
- 入口函数 :
common/board_r.c中的board_init_r() - 阶段特点:U-Boot已运行在DDR中,拥有完整的运行环境。
- 主要任务 :
- 初始化所有外设和驱动(MMC、NAND、网络、USB等)
- 加载并解析环境变量(从存储介质中读取)
- 最终进入 :
main_loop()(U-Boot命令行主循环),等待用户输入或自动执行bootcmd启动内核。
五、引导 Linux 内核
1. U-Boot在Linux启动中的作用
准备Linux运行环境(内存、设备树、启动参数),然后跳转到Linux内核入口执行,U-Boot不再控制CPU。
2. 启动前准备
| 内容 | 作用 | 存放位置 |
|---|---|---|
| Kernel镜像 | Linux内核程序(Image/uImage/zImage) | DDR中指定地址 |
| DTB设备树 | 描述硬件平台信息(外设、中断、内存映射等) | DDR中指定地址 |
| bootargs启动参数 | 告诉Linux根文件系统位置、控制台、内存配置等 | 通过DTB的chosen节点传递 |
3. 引导执行流程
text
booti / bootm 命令
↓
检查Kernel镜像格式(magic number、头部信息)
↓
准备启动参数(从环境变量 bootargs 获取)
↓
传递DTB地址给内核(通过寄存器 x0/x1/x2 或约定地址)
↓
跳转到Kernel入口地址(CPU完全交给Linux)
↓
Linux接管CPU,U-Boot不再控制
4. Kernel镜像加载方式(将内核放入DDR)
| 加载方式 | 示例命令 |
|---|---|
| eMMC / SD(FAT分区) | fatload mmc 0:1 0x42000000 Image |
| eMMC / SD(EXT4分区) | ext4load mmc 0:2 0x42000000 Image |
| TFTP网络加载 | tftp 0x42000000 Image |
| NAND Flash | nand read 0x42000000 0x100000 0x400000(读NAND偏移0x100000,大小0x400000) |
5. 关键信息传递:设备树与 bootargs
- 设备树(DTB) :通过
booti或bootm命令的第三个参数传递DTB地址,内核启动时解析DTB获取硬件信息。 - 启动参数(bootargs) :通过DTB的
chosen节点传递,本质是一个字符串,常见格式如下:
| 参数 | 作用 | 示例 |
|---|---|---|
console |
指定Linux控制台串口及波特率 | console=ttyS0,115200 |
root |
指定根文件系统位置 | root=/dev/mmcblk0p2 |
rootfstype |
指定根文件系统类型 | rootfstype=ext4 |
rw / ro |
根文件系统挂载为读写/只读 | rw |
示例bootargs:
text
console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootfstype=ext4
6. 分区表
分区表描述存储设备(eMMC、NAND、SD卡)上各区域的位置和大小,为U-Boot提供寻址依据(如内核在哪个分区、设备树在哪个分区)。常见分区信息包括:分区名称、起始地址、大小、文件系统类型、分区编号。
分区表本身不直接传给内核,而是通过 bootargs 中的
root=/dev/mmcblk0pX等方式让内核知道根文件系统位置。
六、U-Boot 环境变量与常用操作
⚠️ 注意 :以下命令均在 U-Boot Shell(命令行环境) 中执行,而非Linux系统内部。
1. 环境变量管理
- 存储位置:eMMC的boot分区、NAND Flash、SPI Flash等(取决于配置)。
- 核心环境变量:
| 变量 | 作用 |
|---|---|
bootcmd |
自启动命令(开机自动执行) |
bootargs |
传递给Linux的启动参数 |
serverip |
TFTP服务器IP(即开发电脑IP) |
ipaddr |
开发板自身IP |
loadaddr |
镜像加载到DDR的默认地址 |
2. 环境变量操作命令
| 操作 | 命令示例 |
|---|---|
| 查看所有变量 | printenv |
| 查看单个变量 | printenv bootcmd |
| 修改变量 | setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2" |
| 保存变量 | saveenv(写入存储介质) |
3. Flash / 存储操作
| 命令 | 功能 | 示例 |
|---|---|---|
mmc list |
列出MMC设备 | mmc list |
mmc dev <n> |
选择MMC设备 | mmc dev 0 |
mmc read |
从MMC读取 | mmc read 0x42000000 0x800 0x1000(块号0x800,块数0x1000) |
sf probe |
初始化SPI Flash | sf probe |
sf read |
从SPI Flash读取 | sf read 0x42000000 0x100000 0x400000 |
nand info |
查看NAND信息 | nand info |
nand read |
从NAND读取 | nand read 0x42000000 0x100000 0x400000 |
4. 网络操作
| 命令 | 功能 | 示例 |
|---|---|---|
setenv serverip |
设置TFTP服务器IP | setenv serverip 192.168.1.100 |
setenv ipaddr |
设置开发板IP | setenv ipaddr 192.168.1.50 |
tftp |
通过TFTP下载文件到DDR | tftp 0x42000000 Image |
ping |
测试网络连通性 | ping 192.168.1.100 |
5. 内存 / 文件系统操作
| 命令 | 功能 | 示例 |
|---|---|---|
md |
读内存(dump) | md.l 0x42000000 10(以32位读10个数据) |
mw |
写内存 | mw.l 0x42000000 0x12345678 1 |
fatload |
从FAT分区加载文件 | fatload mmc 0:1 0x42000000 Image |
ext4load |
从EXT4分区加载文件 | ext4load mmc 0:2 0x42000000 Image |
6. 引导操作
| 命令 | 适用架构 | 用法 |
|---|---|---|
booti |
ARM64 | booti <内核地址> <initrd地址> <设备树地址> |
示例:booti 0x42000000 - 0x43000000(-表示无initrd) |
||
bootm |
ARM32(支持设备树) | bootm <内核地址> [initrd地址] [设备树地址] |
示例:bootm 0x42000000 - 0x43000000 |
七、安全启动(Secure Boot)
- 核心思想:建立信任链,防止未经授权的代码运行。从芯片内部不可修改的信任根(Root of Trust)开始,逐级验证后续镜像的签名。
- 信任根:系统中最初可信的软件或硬件基础,即BootROM内固化的公钥或公钥哈希。
- 验证链:
text
BootROM(固化公钥) → 验证SPL签名 → SPL
SPL(携带公钥) → 验证U-Boot签名 → U-Boot
U-Boot(携带公钥) → 验证内核签名 → 内核(FIT Image)
- 关键技术 :
- 镜像签名 :使用私钥对镜像生成数字签名,设备端用公钥验证,确认镜像完整未被篡改 且来源可信。
- 验证流程:计算镜像Hash值 + 用公钥解密签名比对Hash,两者一致则验证通过。
- FIT Image:U-Boot提供的一种带有描述信息(.its文件)和数字签名支持的镜像格式,可同时包含内核、设备树、ramdisk等多个组件,统一管理并签名。
八、补充说明
U-Boot 与 Linux 的命令区分
| 环境 | 提示符 | 常见命令示例 |
|---|---|---|
| U-Boot Shell | => |
printenv、setenv、fatload、booti |
| Linux Shell | # 或 $ |
ls、cat、ifconfig、mount |
两者命令完全不同,不要混淆。 U-Boot中不存在 ls、cat 等Linux命令。
常见术语解释
| 术语 | 解释 |
|---|---|
| SPL(Secondary Program Loader) | 二级加载器,在SRAM中运行,负责初始化DDR并加载完整U-Boot |
| SRAM | 芯片内部静态随机存储器,容量小但速度快,上电即可使用 |
| DDR | 外部动态随机存储器,容量大,需初始化后才能使用 |
| DTB(Device Tree Blob) | 设备树二进制文件,描述硬件信息 |
| FIT(Flattened Image Tree) | U-Boot的统一镜像格式,支持多组件打包和签名 |