U-Boot概述:从历史到启动流程
大家好,我是你们的讲师。今天咱们开始第一讲,聊聊U-Boot到底是什么,它在嵌入式系统里扮演什么角色,以及它上电后都干了些什么。
说实话,我最早接触U-Boot是在2008年,那时候做一款基于ARM9的工控板。板子刚焊好,上电后串口啥输出都没有,折腾了两天才发现是U-Boot的配置没改对。从那以后,我就跟这个“小玩意儿”结下了不解之缘。
U-Boot的历史与作用
U-Boot,全称是“Universal Boot Loader”。它最初是从德国DENX软件实验室的Wolfgang Denx发起的。你想想看,2000年初那会儿,嵌入式芯片五花八门,每个厂家都有自己的启动代码,移植起来特别痛苦。U-Boot的出现,说白了就是想把这事儿统一了。
它的核心作用就三个:
- 初始化硬件:CPU、内存、串口、Flash,这些外设上电后都是“裸”的,U-Boot得先把它们“唤醒”。
- 加载操作系统:从Flash、网络或者SD卡里把内核镜像读出来,放到内存里,然后跳过去执行。
- 提供调试接口:通过命令行,你可以修环境变量、烧写固件、测试内存。我当年调试一块DDR3的板子,就是靠U-Boot的
md和mw命令一点点排查出来的。
重要概念:U-Boot本身也是一个“小操作系统”。它有命令行、文件系统支持、网络协议栈。只不过它运行在实模式或非保护模式下,功能比Linux精简得多。
U-Boot在嵌入式系统中的位置
咱们来看一张图,你就明白了。
嗯,这里要注意。U-Boot并不是直接跑在芯片最底层的。芯片内部有一个很小的BootROM,它先运行,然后加载SPL(如果你用现代芯片的话),SPL再加载完整的U-Boot。我见过不少新手直接跳过SPL去改U-Boot,结果板子起不来,其实就是SPL配置没对上。
个人经验:我在调试i.MX6ULL板子时,遇到过SPL阶段DDR初始化失败的问题。当时串口只打印了一个“CC”就卡住了。后来发现是DDR时序参数配错了。所以,SPL阶段的调试信息非常关键,建议你一开始就把串口输出打开。
U-Boot启动流程概览
U-Boot的启动流程,我习惯把它分成三个阶段:
- 第一阶段:汇编阶段
- 设置CPU模式(通常是SVC模式)
- 关闭中断、MMU、Cache
- 初始化栈指针
- 拷贝U-Boot自身到RAM(如果是从Flash启动)
- 第二阶段:C语言初始化
- 调用
board_init_f():初始化时钟、串口、DDR - 重定位U-Boot到DDR的高地址
- 调用
board_init_r():初始化剩余外设
- 调用
- 第三阶段:命令行循环
- 进入
main_loop() - 检查是否有自动启动命令(比如
bootcmd环境变量) - 如果没有,进入交互式命令行
- 进入
为什么会分成这么多个阶段?说白了,就是硬件资源有限。一开始CPU只能访问片内SRAM,容量很小,放不下完整的U-Boot。所以先跑一小段汇编,把DDR初始化好,再把完整的U-Boot拷贝过去,最后跳转到C代码里。
避坑指南:我曾经在重定位阶段踩过一个坑。U-Boot重定位后,原来的代码段会被覆盖。如果你在重定位前调用了某个函数指针,而这个指针指向的地址没有更新,那就会跑飞。解决办法是确保所有函数指针在重定位后重新计算地址,或者使用RELOCATE宏。
来看一段典型的启动日志,你就知道流程长什么样了:
U-Boot 2023.04 (Apr 10 2024 - 14:30:00 +0800)
CPU: i.MX6ULL rev1.1 at 792 MHz
Board: MY-IMX6ULL
DRAM: 512 MiB
MMC: FSL_SDHC: 0, FSL_SDHC: 1
In: serial
Out: serial
Err: serial
Net: FEC0
Hit any key to stop autoboot: 0
=>
看到没?先是打印版本信息,然后是CPU、板子、内存大小,接着是外设初始化,最后进入命令行。如果卡在“DRAM:”后面没反应,那八成是DDR初始化有问题。我当年调试一块板子,就是卡在这里,最后发现是DDR的ODT配置不对。
小结
这一章咱们把U-Boot的来龙去脉理清楚了。它不是什么神秘的东西,就是一个负责把硬件“伺候”好,然后把操作系统请进来的程序。它的启动流程虽然分了好几步,但每一步都有明确的目的。
下一章咱们会深入U-Boot的源码结构,看看那些.c和.h文件到底是怎么组织起来的。到时候我会拿一个实际项目里的配置来拆解,保证你一看就懂。
核心要点回顾:
- U-Boot是通用引导加载程序,负责硬件初始化和加载OS
- 它位于BootROM和Linux内核之间
- 启动流程分汇编、C初始化、命令行三个阶段
- SPL是现代芯片中U-Boot的前置加载器