2. U-Boot源码结构解析:目录、关键文件与编译系统
拿到一份U-Boot源码,第一反应是什么?我当年第一次解压出来,看到满屏目录,说实话有点懵。但别怕,它的结构其实很有规律。今天我们就把它拆开看看。
2.1 顶层目录结构
U-Boot的顶层目录,说白了就几大类:板级相关、CPU架构相关、通用驱动、工具脚本。我习惯先看board/和arch/,因为移植工作主要跟这两个目录打交道。
| 目录/文件 | 作用 | 我关注的点 |
|---|---|---|
arch/ | CPU架构相关代码(ARM、x86、RISC-V等) | ARM下重点关注arch/arm/ |
board/ | 板级初始化、DDR配置、GPIO等 | 移植时主要修改的地方 |
configs/ | 默认配置文件(xxx_defconfig) | 从这里开始配置你的板子 |
drivers/ | 外设驱动(串口、网卡、MMC、USB等) | 调试时经常要翻 |
include/ | 头文件、宏定义、板级配置头文件 | configs/目录下的头文件是关键 |
dts/ | 设备树源文件(.dts、.dtsi) | 新板子必须自己写 |
tools/ | 镜像打包、签名等工具 | mkimage常用 |
scripts/ | Kbuild、Makefile辅助脚本 | 编译系统核心 |
fs/ | 文件系统支持(ext4、fat等) | 启动时加载内核需要 |
net/ | 网络协议栈 | tftp下载用 |
cmd/ | 命令行命令实现 | 可以自己加命令 |
common/ | 通用函数和初始化流程 | 启动流程入口在这里 |
board/目录,再改configs/下的defconfig。这样最快能跑起来。
2.2 关键文件说明
有几个文件,你移植时几乎一定会动到。我列出来,你心里有个数。
2.2.1 板级配置头文件
位置:include/configs/。比如include/configs/rpi.h。这里面定义了DDR大小、时钟频率、环境变量分区等。嗯,这里要注意:不要乱改宏定义,尤其是CONFIG_SYS_TEXT_BASE,改错了U-Boot起不来。
2.2.2 设备树文件
位置:arch/arm/dts/。现在U-Boot也大量使用设备树。你写的.dts文件,编译后会生成.dtb,U-Boot用它来知道板子上有什么外设。
2.2.3 链接脚本
位置:arch/arm/cpu/u-boot.lds。这个文件决定了U-Boot各段在内存中的布局。我曾经因为改了链接脚本,导致U-Boot在DDR初始化前就访问了全局变量,直接死机。避坑指南:链接脚本不要轻易动,除非你很清楚自己在做什么。
2.2.4 默认配置文件
位置:configs/。比如configs/stm32mp15_defconfig。这是编译的入口,里面通过CONFIG_*开关控制哪些功能被编译进去。
include/configs/头文件方式,逐步迁移到Kconfig方式。新板子建议直接用Kconfig,别走回头路。
2.3 编译系统简介:Kbuild/Makefile
U-Boot的编译系统,说白了就是Linux内核那套Kbuild。你如果编译过内核,上手会很快。如果没接触过,也别慌,我带你捋一遍核心流程。
2.3.1 编译三步走
- 配置:
make xxx_defconfig—— 生成.config文件 - 编译:
make—— 根据.config编译源码 - 打包:
make自动生成u-boot.bin、u-boot.img等镜像
举个例子,我移植STM32MP157板子时,第一步就是:
make stm32mp15_defconfig
make -j4
2.3.2 Kconfig体系
Kconfig文件分布在各个目录,比如drivers/serial/Kconfig。它定义了哪些驱动可以选。你执行make menuconfig时看到的界面,就是Kconfig生成的。
我个人习惯,在board/st/xxx/Kconfig里添加板级特有的配置选项。这样代码更干净。
2.3.3 Makefile的层次
- 顶层Makefile:负责整体编译流程,调用各子目录Makefile
- 子目录Makefile:比如
drivers/serial/Makefile,指定哪些文件被编译 - Kbuild:真正的编译规则,定义
obj-y、obj-$(CONFIG_XXX)等变量
你想想看,如果你要添加一个新驱动,只需要在对应目录的Makefile里加一行:
obj-$(CONFIG_MY_UART) += my_uart.o
然后在Kconfig里定义CONFIG_MY_UART,就搞定了。
2.4 知识结构图:U-Boot源码与编译流程
下面这张图,把U-Boot的源码结构、关键文件、编译流程串起来了。你保存下来,以后移植时对照着看。
2.5 避坑指南与个人经验
最后,分享几个我踩过的坑,你遇到了能少走弯路。
- 编译报错找不到头文件:检查
include/configs/里的宏定义是否和Kconfig冲突。我遇到过CONFIG_SYS_TEXT_BASE在两个地方定义,编译直接挂。 - 设备树编译不过:语法错误最常见。用
make dtbs单独编译设备树,看具体报错行。我曾经漏了一个分号,查了半小时。 - U-Boot启动卡死:先确认串口驱动是否选对了。我建议在
board_init_f里加个GPIO点灯,确认代码执行到了哪里。 - 链接脚本问题:如果你改了
u-boot.lds,记得检查段地址是否在DDR范围内。我见过有人把.bss段放到未初始化区域,结果全局变量全是乱的。
V=1可以看到详细编译命令,调试很有用。比如:make V=1。