第三章 Bootloader基础与U-Boot移植
3.1 U-Boot简介
说到嵌入式Linux,Bootloader是绕不开的第一关。它就像PC上的BIOS,负责把内核从存储介质里请出来,再交给CPU去跑。U-Boot,全称Universal Boot Loader,是目前最主流的Bootloader方案。
我最早接触U-Boot是在2010年左右,那时候它还不叫U-Boot,叫PPCBoot。后来因为支持了ARM、MIPS、x86等架构,才改名叫U-Boot。说白了,它就是一块「万能钥匙」,能适配各种开发板。
U-Boot的核心功能其实就三件事:
- 硬件初始化:把CPU、内存、串口这些基本外设先跑起来
- 加载内核:从Flash、SD卡、网络等介质读取内核镜像
- 传递参数:把设备树、启动参数传给内核
重要概念:U-Boot分为两个阶段——SPL(Secondary Program Loader)和U-Boot本体。SPL负责初始化DDR等关键硬件,然后加载完整的U-Boot。这个设计是为了解决早期芯片内部SRAM太小的问题。
3.2 U-Boot源码结构
U-Boot的源码目录看起来挺吓人,但其实有规律可循。我习惯把它分成几个核心区域:
| 目录 | 作用 |
|---|---|
| arch/ | 架构相关代码,比如arm、riscv、x86 |
| board/ | 具体开发板的板级代码 |
| drivers/ | 各种外设驱动,串口、网卡、存储等 |
| include/ | 头文件,特别是configs/下的板级配置 |
| cmd/ | U-Boot命令行命令的实现 |
| dts/ | 设备树源文件 |
嗯,这里要注意一个细节。很多新手会直接去改board/下的文件,但其实大部分配置都在include/configs/里。我踩过这个坑,改了半天board文件,结果发现配置根本没生效。
我的习惯:拿到一个新板子,先看arch/arm/dts/下的设备树,再看include/configs/下的头文件。这两个地方基本决定了板子的行为。
3.3 U-Boot配置与编译
U-Boot的配置系统借鉴了Linux内核的Kconfig。但说实话,它比内核简单多了。基本流程就三步:
- 选择配置:make <板子名>_defconfig
- 微调配置:make menuconfig(可选)
- 编译:make -j4
举个例子,我手头有块STM32MP157的开发板,配置命令是这样的:
# 配置
make stm32mp15_defconfig
# 如果要做个性化调整
make menuconfig
# 编译,-j4表示4线程并行
make -j4
编译完成后,你会得到几个关键文件:
- u-boot.bin:完整的U-Boot镜像
- u-boot-spl.bin:SPL阶段的镜像
- u-boot.dtb:编译好的设备树
避坑指南:我曾经在编译时忘了装交叉编译工具链,结果报了一堆莫名其妙的错误。记得先确认你的工具链路径对不对,用arm-linux-gnueabihf-gcc -v检查一下。
3.4 U-Boot设备树基础
设备树(Device Tree)是U-Boot和内核之间沟通的桥梁。它用文本描述硬件信息,比如CPU型号、内存地址、外设寄存器等。U-Boot启动时会把设备树传给内核,内核根据它来初始化驱动。
设备树的文件后缀是.dts(源文件)和.dtb(编译后的二进制)。U-Boot的设备树放在arch/arm/dts/目录下,命名规则一般是:
<厂商>-<板子名>.dts
例如:stm32mp157d-atk.dts
一个简单的设备树节点长这样:
/ {
model = "STMicroelectronics STM32MP157D-DK1";
compatible = "st,stm32mp157d-dk1", "st,stm32mp157";
memory@c0000000 {
device_type = "memory";
reg = <0xc0000000 0x20000000>;
};
chosen {
stdout-path = &uart4;
};
};
你看,model是板子名称,compatible是兼容性字符串,memory定义了内存范围,chosen告诉内核用哪个串口输出日志。这些信息对内核来说至关重要。
核心要点:设备树不是代码,是数据。它只描述硬件,不包含逻辑。驱动程序的逻辑仍然在C代码里,设备树只是告诉驱动「你该操作哪个寄存器、用哪个中断号」。
我个人习惯在移植U-Boot时,先拿官方设备树做模板,然后根据实际板子的差异去改。比如内存大小不同、串口号不同,这些在设备树里一目了然。改完记得用dtc工具检查语法:
dtc -I dts -O dtb -o test.dtb test.dts
如果报错,多半是括号没配对或者属性名写错了。嗯,这个坑我也踩过,后来养成了每次改完都检查一遍的习惯。
3.5 本章小结
U-Boot移植说白了就是三板斧:配置、编译、调设备树。配置决定了U-Boot的功能集,编译生成镜像,设备树告诉内核硬件长什么样。这三件事搞明白了,移植工作就完成了一大半。
你想想看,U-Boot其实没那么神秘。它就是一个跑在裸机上的小程序,只不过功能比普通单片机程序复杂一些。掌握了它的源码结构和配置方法,后面移植内核就会轻松很多。
小技巧:如果你在移植过程中遇到U-Boot启动卡住,先检查串口配置对不对。我遇到过好几次,都是因为设备树里的时钟频率和实际晶振不匹配,导致串口波特率算错了。