4、Bootloader基础:U-Boot简介、U-Boot源码结构、编译U-Boot、烧录U-Boot到目标板

嵌入式Linux系统启动,说白了就三步:Bootloader启动内核,内核挂载根文件系统,然后跑应用程序。而Bootloader,就是整个链条的第一环。

我刚开始做嵌入式那会儿,总觉得Bootloader就是个「黑盒子」,烧进去能跑就行。直到有一次板子死活起不来,串口打印卡在「Starting kernel...」就没了反应。折腾了两天,最后发现是U-Boot传给内核的设备树地址错了。嗯,从那以后,我再也不敢轻视Bootloader了。

U-Boot是什么?

U-Boot,全称是「Universal Boot Loader」。它支持ARM、x86、RISC-V等几乎所有主流架构。说白了,它就是嵌入式世界的「BIOS」。

它的核心任务就三件:

  • 初始化硬件:时钟、DDR、串口、网卡等,让板子「活过来」
  • 加载内核:从Flash、网络、SD卡等介质把内核镜像读到内存
  • 传递启动参数:告诉内核硬件信息、启动参数、设备树地址

我个人习惯把U-Boot比作「摆渡人」——它不生产功能,只是把内核安全地送到对岸。

U-Boot源码结构

U-Boot的源码目录乍一看挺吓人,其实核心就几个文件夹。我带你捋一遍:

目录 作用
arch/ 架构相关代码。比如arm、x86、riscv。咱们Intel平台主要看arch/x86/
board/ 板级支持包。每个具体板子有自己的文件夹,比如board/intel/
common/ 通用功能模块。命令行解析、环境变量、启动流程都在这里
drivers/ 各种外设驱动。串口、网卡、MMC、USB等
include/ 头文件。配置宏定义、数据结构都在这里
configs/ 板级默认配置文件。编译前先选它

你想想看,U-Boot支持几百种板子,靠的就是这种分层设计。架构层处理CPU差异,板级层处理外设差异,通用层提供统一接口。我在项目中移植新板子时,通常只需要改board/configs/两个目录,其他基本不动。

编译U-Boot

编译U-Boot其实不复杂,但有几个坑。我直接给你一个完整的流程:

前提条件:安装交叉编译工具链。Intel平台一般用gcc-arm-linux-gnueabihf

# 1. 下载源码
git clone https://github.com/u-boot/u-boot.git
cd u-boot

# 2. 查看支持的Intel板子配置
ls configs/ | grep intel

# 3. 选择配置(以MinnowBoard MAX为例)
make minnowmax_defconfig

# 4. 配置(可选,一般默认即可)
make menuconfig

# 5. 编译
make CROSS_COMPILE=arm-linux-gnueabihf- -j4

编译完成后,你会得到u-boot.bin。这就是我们要烧录的二进制文件。

我的经验:第一次编译时,建议用make V=1打开详细输出。如果报错,能直接看到是哪个文件编译失败。我曾经被一个头文件路径问题卡了半天,开了V=1才发现是交叉编译器版本不匹配。

烧录U-Boot到目标板

烧录方式取决于你的板子。Intel嵌入式平台常见的有三种:

方式一:通过SPI Flash烧录(最常用)

Intel的很多板子(如MinnowBoard、Galileo)用SPI Flash存Bootloader。你需要一个SPI编程器,或者用板子自带的Flash工具。

# 假设你已经通过串口进入了U-Boot命令行
# 从网络加载U-Boot镜像到内存
tftp 0x100000 u-boot.bin

# 擦除SPI Flash
sf probe
sf erase 0x0 0x80000

# 写入新U-Boot
sf write 0x100000 0x0 0x80000

# 重启
reset

注意:擦除和写入的地址、大小一定要和板子的Flash分区对齐。我见过有人把环境变量区给擦了,结果板子启动后连MAC地址都丢了。嗯,那滋味可不好受。

方式二:通过SD卡启动

开发阶段我更喜欢用SD卡。把U-Boot写到SD卡的特定扇区,板子从SD卡启动即可。

# 假设SD卡设备是/dev/sdb
sudo dd if=u-boot.bin of=/dev/sdb bs=512 seek=1
sync

这里的seek=1表示跳过第一个扇区(MBR),从第二个扇区开始写。为什么?因为U-Boot的SPL(第一阶段引导)通常放在这里。

方式三:通过JTAG烧录

如果板子完全「砖」了,串口没反应,那就得上JTAG。用OpenOCD配合调试器,直接往Flash里写。这个方法比较底层,我一般只在救砖时用。

验证烧录是否成功

烧录完成后,上电看串口输出。如果看到类似下面的信息,恭喜你,U-Boot跑起来了:

U-Boot 2024.01 (Jan 15 2024 - 10:30:00 +0800)

CPU:   Intel(R) Atom(TM) Processor
DRAM:  2 GiB
MMC:   mmc@ff3f0000: 0
Net:   eth0: e1000#0

Hit any key to stop autoboot:  0
=> 

看到那个=>提示符了吗?这就是U-Boot的命令行。你可以输入help看看支持哪些命令,或者输入printenv查看环境变量。

避坑指南:如果串口只打印几个乱码字符就停了,八成是波特率不对。Intel平台默认一般是115200,但有些板子用57600。我曾经被这个坑过,换了三个串口工具才发现是波特率设置错了。

好了,到这里你已经掌握了U-Boot的编译和烧录。下一章我们会深入U-Boot的命令行,教你如何用它调试内核启动。记住,Bootloader是嵌入式开发的「第一道门」,把它搞透了,后面的路就好走了。