第三章 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。但说实话,它比内核简单多了。基本流程就三步:

  1. 选择配置:make <板子名>_defconfig
  2. 微调配置:make menuconfig(可选)
  3. 编译: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启动卡住,先检查串口配置对不对。我遇到过好几次,都是因为设备树里的时钟频率和实际晶振不匹配,导致串口波特率算错了。

U-Boot移植核心流程 配置阶段 make xxx_defconfig 编译阶段 make -j4 设备树阶段 修改.dts文件 u-boot.bin u-boot-spl.bin u-boot.dtb 烧录到开发板 → 验证启动

专注资料整理