最近,工作重心要从裸机开发转移到嵌入式 Linux 系统开发,在之前的博文 Linux 之八 完整嵌入式 Linux 环境、(交叉)编译工具链、CPU 体系架构、嵌入式系统构建工具 中详细介绍了嵌入式 Linux 环境,接下来就是实际动手学习了。
这篇博文我们仅仅关注构建过程本身,想要吃透 U-Boot,有太多东西需要学习!最开始我想放到一篇文章中,写着写着内容越来越多,最终超过了 CSDN 编辑器的限制。。。最终决定把内容拆分成多篇文章。你可能需要:
- U-Boot 之二 移植过程详解、 STM32F769I-EVAL 开发板适配
- U-Boot 之三 详解使用 eclipse + J-Link 进行编译及在线调试
- U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解
- U-Boot 之五 详解 U-Boot 及 SPL 的启动流程
源码
文中涉及的源代码均放到了我个人的 Github 上:https://github.com/ZCShou/BOARD-STM32F769I-EVAL。这个仓库中包含了要搭建的完整嵌入式 Linux 环境的所有源代码,后续博文均以该仓库中的源码为基础来学习!

构建环境
我最开始使用的是 Ubuntu 20.04 LTS + Arm GNU Toolchain 10.3,前几天,我将开发环境升级为了 Ubuntu 22.04.1 LTS + Arm GNU Toolchain 11.3.Rel1-2021.10,升级后的开发环境及需要注意的问题如下所示(本文后续内容同时对新 / 旧这两个环境进行了验证)。

- 由于 Ubuntu 22.04 LTS 默认是标配 OpenSSL 3.x,而旧版 U-Boot 使用的是 OpenSSL 1.x,所以,该环境编译旧版 U-Boot(从 commit e927e21c 开始添加了相关处理) 将出现一堆警告,因此,后续使用 u-boot-v2022.10 这个版本为主:

- Arm GNU Toolchain 10.2-2022.02 存在 BUG,导致编译 U-Boot 报错,不要使用这个版本!

- 新版(Arm GNU Toolchain 10.3 之后的版本)的 Arm GNU Toolchain 在 Linux 上 GDB 需要 Python3.8 支持。然而,Ubuntu 22.04 默认的 Python 是 3.10。直接运行
arm-none-eabi-gdb报错如下:

解决方法就是直接手动安装 Python3.8 即可。 旧版的 Arm GNU Toolchain 10.3 -2021.10 不需要 Python 支持sudo add-apt-repository ppa:deadsnakes/ppa -y sudo apt install python3.8
此外,还有一些工具需要安装。第一个就是 GCC,GCC 就使用 Ubuntu 自带的 9.3.0 版即可。在编译 U-Boot 的过程中,我们还需要安装其他一些依赖工具,这个在后面用到的时候缺啥装啥就可以(出现各种错误的时候再安装相应工具即可)。
这里再介绍一下安装依赖包的一些方法。正常我们应该是先使用 find 命令查找依赖是否存在。因为存在一种情况是,依赖文件本身存在但是没有在环境变量 LD_LIBRARY_PATH 里,如果不存在直接安装即可。
- 直接安装的情况。一般使用命令
sudo apt update->apt-cache search xxxx->sudo apt install xxxx,具体示例(仅仅是个示例,安装了没啥用哈)如下:

这样我们可以查看响应的依赖包的具体说明,然后根据需要来安装。 - 没有在环境变量
LD_LIBRARY_PATH里的情况,目前有两种方法:- 直接把依赖的动态库拷贝到上面执行的可执行命令时显示被找到的库的目录下。
- 把依赖的动态库所在的目录添加到环境变量
LD_LIBRARY_PATH里:export LD_LIBRARY_PATH=/path_to_lib/:/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH。
运行环境
我使用的嵌入式环境是 STM32F769I-EVAL 板子。STM32F769I-EVAL 板子使用的 STM32F769NI 这个 MCU,STM32F769NI 这款 MCU 采用的是 ARM Cortex-M7 的核心,指令集架构是 ARMv7m。此外,还需要注意,这个板子上的的串口的 RX 默认是断开,需要用短路帽连接起来。

U-Boot 本身没有提供对于 STM32F769I-EVAL 板子的支持,但是它支持的 STM32F769-Disco 板子。STM32F769-Disco 开发板与 STM32F769I-EVAL 评估板的 MCU 都使用的是 STM32F769NI,因此,我们直接编译后下载是可以运行的。
不过两者的板载资源不同(例如,DRAM 大小),因此,U-Boot 识别的某些外设信息是不对的。这里仅仅关注如何进行零基础编译,在博文 U-Boot 之二 移植过程详解、 STM32F769I-EVAL 开发板适配 中我详细介绍了如何将 U-Boot 移植到 STM32F769I-EVAL 板子。
编译过程
在开始编译之前,我们先介绍两个命令:make clean 用于清空编译中间文件 和 make distclean 用于清除所有编译产生的文件。如果我们想要重新编译,可以使用以上两个命令清理环境(这两个命令在 U-Boot 根目录的 Makefile 中有详细的定义,在博文 U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解 中会有说明)。
-
第一步肯定是获取 U-Boot 的源代码,我这里直接使用了当前最新存档版:
u-boot-2021.10.tar.bz2。这里需要重点注意,我最开始直接使用 Git 获取了最新的源代码,结果编译之后运行直接 HardFault,分析好久没找到原因,最后决定使用一个稳定发布版试试,结果没有问题。。。

成功下载并解压源代码之后(注意我这里将解压后的文件名命名为了 u-boot),我们需要进入 u-boot 目录下,使用命令:cd u-boot。此后就在 u-boot 目录下进行各种操作。 -
第二步就是生成配置,直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 stm32f769-disco_defconfig。其中,参数O=xx用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。 不出意外的话会出现以下错误:-
/bin/sh: 1: bison: not found

这个错误是由于我们没有安装 bison 这个工具。Bison 是一个通用解析器生成器。GUN 软件之一,官网 https://www.gnu.org/software/bison/。Ubuntu 下直接使用命令:sudo apt install bison即可(这个不是最新版,如果需要最新版需要自己从源码安装)。 -
/bin/sh: 1: flex: not found

这个错误是由于我们没有安装 flex 这个工具。flex 是一个词法分析器。用来将一个.l文件生成一个.c程序文件。源代码托管于 Github:https://github.com/westes/flex。Ubuntu 下直接使用命令:sudo apt install flex即可。 -
正常完成如下所示:

-
-
第三步修改配置(裁剪)。直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 menuconfig。其中,参数O=xx用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。 不出意外的话会出现以下错误:- Unable to find the ncurses package

这个错误是由于我们没有安装 ncurses 这个工具。ncurses(new curses)是一套编程库,它提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。GUN 软件之一,官网:https://invisible-island.net/ncurses/。Ubuntu 下直接使用命令:sudo apt install libncurses-dev即可。安装成功之后,重新make menuconfig,就会进入下面的界面:

我们需要做的就是选择其中的 SPL / TPL 菜单项,然后回车,在其中下翻页找到Activate Falcon Mode项(SPL / TPL ---> Activate Falcon Mode),按n将选择去掉,然后保存退出。

其中还有很多项,这个就需要根据自己需要来具体进行裁剪了。当然有个前提是,修改了配置很大可能还需要配套修改对应的源代码,因为之所以修改肯定是为了适应自己开发板。 - Your display is too small to run Menuconfig!

这个错误提示很明显,就是终端界面太小,把终端拖大一些就好了。
- Unable to find the ncurses package
-
第四步就是真正的编译了,直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 -j$(nproc)。其中,参数O=xx用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。 不出意外的话会出现以下错误:-
/bin/sh: 1: arm-none-eabi-gcc: not found

这个错误是由于我们没有安装 GCC for ARM 导致的。也就是没有编译 U-Boot 使用的编译器。解决方法也非常简单,就是安装 GCC for ARM 即可。这里有个需要注意的点,如果大家搜索 Ubuntu 下安装 GCC for ARM,很多文章都过推荐使用 apt 命令来安装,类似于:
sudo apt-get install gcc-arm-none-eabi,这个版本并不是最新的(貌似使用这个旧版本也可以,我选择了使用最新版)。更重要的是,ARM 之前已经宣布不再更新 Launchpad 上的 GCC for ARM 了(具体见 Launchpad 上的说明)。 其只在 ARM 的官网提供编译好的压缩包及源代码的压缩包。这里简单来讲解一下直接从 ARM 官网下载压缩包的安装方法:
-
下载最新版的 Linux x86_64 Tarball。目前最新的是 gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 。GCC for ARM 下载地址:https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
-
将压缩包解压。我这里将其解压到了
/opt/目录下。直接使用命令:tar -xjvf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt。等待解压完成,/opt/gcc-arm-none-eabi-10.3-2021.10目录下就是最新的 GCC for ARM 的各种可执行程序、库等文件了。 -
此时我们需要将
/opt/gcc-arm-none-eabi-10.3-2021.10添加到系统环境变量这样才能正常在终端中使用各种命令。具体有两种方法(我采用了第二种):- 第一种是创建符号连接
sudo ln -s /opt/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-gcc /usr/bin/arm-none-eabi-gcc sudo ln -s /opt/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-g++ /usr/bin/arm-none-eabi-g++ sudo ln -s /opt/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-gdb /usr/bin/arm-none-eabi-gdb sudo ln -s /opt/gcc-arm-none-eabi-your-version/bin/arm-none-eabi-size /usr/bin/arm-none-eabi-size - 第二种是在
.bashrc文件(在 Linux 系统普通用户目录(cd /home/xxx)或 root 用户目录(cd /root)下,用指令ls -al可以看到该隐藏文件,一般使用用户目录下的即可)最后面增加以下内容:# GCC for ARM export PATH="$PATH:/opt/gcc-arm-none-eabi-10.3-2021.10/bin"
- 第一种是创建符号连接
-
可能需要重启我们之前已经打开的终端以上配置才会生效。
-
这里有一点需要注意,通过压缩包安装不能解决依赖关系,需要我们自己运行尝试,看看少啥安啥。目前已知的依赖是
ncures5(会报错:error while loading shared libraries: libncurses.so.5)和libncursesw5(报错:error while loading shared libraries: libncursesw.so.5: cannot open shared object file: No such file or directory),其需要安装sudo apt install libncurses5 libncursesw5(貌似应该是需要 32 位的,我们上面安装的 libncurses-dev 应该是 64 位的)。
至于需不需要其他的依赖,大家自行尝试,我这里是没有提示需要其他任何组件。
-
-
fatal error: openssl/evp.h:

这个错误主要是由于 U-Boot 代码使用了 openssl 中的相代码,而我们的环境中没有安装 openssl。Ubuntu 下直接使用命令:sudo apt install libssl-dev即可。
-
-
正常编译完成之后。我们需要的文件就有了。我们真正需要的是根目录下的
u-boot.bin和 spl 目录下的u-boot-spl.bin。

-
其他一些问题。我在试用
make CHANGELOG命令时出现错误 unrecognized command line option ‘-mno-unaligned-access’,不知道为啥,也没找到如何解决。最新的 U-Boot-v2022.07 版本已经没有该错误了

Image 镜像
成功编译之后,就会在 U-Boot 源码的根目录下产生多个可执行二进制文件以及编译过程文件,这些文件都是 u-boot.xxx 的命名方式。这些文件由一些列名为 .xxx.cmd 的文件生成,.xxx.cmd 这些文件都是由编译系统产生的用于处理最终的可执行程序的。注意,下面部分文件可能没有与自己的 make menuconfig 中的配置有关系。

- u-boot: 这个文件是编译后产生的 ELF 格式的最原始的 U-Boot 镜像文件,后续的文件都是由它产生的!
.u-boot.cmd这个命令脚本描述了如何产生。

- u-boot-nodtb.bin: 这文件是使用编译工具链的
objcopy工具从u-boot这个文件中提取来的,它只包含可执行的二进制代码。就是把u-boot这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由.u-boot-nodtb.bin.cmd这个命令脚本产生。

- u-boot-dtb.bin: 在
u-boot-nodtb.bin尾部拼接上设备树后形成的文件。由.u-boot-dtb.bin.cmd这个命令脚本产生。

- u-boot.bin: 就是把
u-boot-dtb.bin重命名得到的。由.u-boot.bin.cmd这个命令脚本产生。

- u-boot.img: 在
u-boot.bin开头拼接一些信息后形成的文件。由.u-boot.img.cmd这个命令脚本产生。 - u-boot-dtb.img: 在
u-boot.bin开头拼接一些信息后形成的文件。由.u-boot-dtb.img.cmd这个命令脚本产生。 - u-boot.srec: S-Record 格式的镜像文件。由
.u-boot.srec.cmd这个命令脚本产生。 - u-boot.sym: 这个是从
u-boot中导出的符号表文件。由.u-boot.sym.cmd这个命令脚本产生。 - u-boot.lds: 编译使用的链接脚本文件。由
.u-boot.lds.cmd这个命令脚本产生。 - u-boot.map: 编译的内存映射文件。该文件是有编译工具链的连接器输出的!
- System.map: 记录 U-Boot 中各个符号在内核中位置,但是这个文件是使用了
nm和grep工具来手动生成的

- u-boot.dtb: 这个是编译好的设备树二进制文件。就是
./dts/dt.dtb重命名得到的。./dts/dt.dtb来自于 arch/arm/dts/stm32f769-eval.dtb 重命名。
默认是开启了 SPL 的,因此,在编译 U-Boot 时会额外单独编译 SPL,编译产生的镜像文件就存放在 ./SPL 目录下。这下面的镜像生成方式与 U-Boot 基本是一模一样的。
- u-boot-spl: 这个文件是编译后产生的 ELF 格式的 SPL 镜像文件,后续的文件都是由它产生的!
.u-boot-spl.cmd这个命令脚本描述了如何产生。 - u-boot-spl-nodtb.bin: 这文件是使用编译工具链的
objcopy工具从u-boot-spl这个文件中提取来的,它只包含可执行的二进制代码。就是把u-boot-spl这个文件中对于执行不需要的节区删除后剩余的仅执行需要的部分。由.u-boot-spl-nodtb.bin.cmd这个命令脚本产生。 - u-boot-spl-dtb.bin: 在
u-boot-nodtb.bin尾部依次拼接上u-boot-spl-pad.bin和u-boot-spl.dtb后形成的文件。由.u-boot-spl-dtb.bin.cmd这个命令脚本产生。 - u-boot-spl.bin: 就是把
u-boot-dtb.bin重命名得到的。由.u-boot-spl.bin.cmd这个命令脚本产生。 - u-boot-spl.sym: 这个是从
u-boot中导出的符号表文件。由.u-boot-spl.sym.cmd这个命令脚本产生。 - u-boot-spl.lds: 编译使用的链接脚本文件。由
.u-boot-spl.lds.cmd这个命令脚本产生。 - u-boot-spl.map: 编译 SPL 的内存映射文件。
- u-boot-spl.dtb: 这个是编译好的设备树二进制文件。就是
./dts/dt.dtb重命名得到的。./dts/dt.dtb来自于 arch/arm/dts/stm32f769-eval.dtb 重命名。 - u-boot-spl-pad.bin: 应该是对齐使用的数据,具体为啥需要这个还没找到。
Image 使用
编译之后,源码的根目录下会生成一堆二进制的文件(.bin),其中,在默认的 U-Boot 配置下,我们实际需要的 U-Boot 实际包含两部分:spl/u-boot-spl.bin 和 u-boot.bin。而且,默认情况下,编译的这俩文件是可以直接在 MCU 内部的 Nor Flash 中运行的。

因此,我们可以直接使用 J-link 等调试器将这俩文件烧写到 Flash 中。至于烧写地址,在 U-Boot 源码中(具体见于 ./config/stm32f769-disco_defconfig 和 ./include/configs/stm32f746-disco.h)都是配置好了的。
设备树
设备树源文件被最终编译为二进制的 DTB 文件,原始的 DTB 文件位于 arch/arm/dts/xxx.dtb 下面,构建系统会复制到 ./dts/dt.dtb,进一步重命名为 ./u-boot.dtb。u-boot 支持两种形式将 dtb 编译到 u-boot 的镜像中:
-
dtb 和 u-boot 的 bin文件分离
- 需要打开
CONFIG_OF_SEPARATE宏来使能。 - 在这种方式下,u-boot 的编译和 dtb 的编译是分开的,先生成 u-boot 的 bin 文件,然后再另外生成dtb 文件。
- dtb 最终会自动追加到 u-boot 的 bin 文件的最后面。因此,可以通过 u-boot 的结束地址符号,也就是 _end 符号来获取 dtb 的地址。
- 需要打开
-
dtb 集成到 u-boot 的 bin 文件内部
- 需要打开
CONFIG_OF_EMBED宏来使能。 - 在这种方式下,在编译 u-boot 的过程中,也会编译 dtb。
- 最终 dtb 是包含到了u-boot 的 bin 文件内部的。dtb 会位于 u-boot 的 .dtb.init.rodata 段中,并且在代码中可以通过 __dtb_dt_begin 符号获取其符号。
官方不推荐这种方式,建议仅用于调试
- 需要打开
-
另外,也可以通过 fdtcontroladdr 环境变量来指定 dtb 的地址。可以通过直接把 dtb 加载到内存的某个位置,并在环境变量中设置 fdtcontroladdr 为这个地址,达到动态指定 dtb 的目的。
参考
- https://askubuntu.com/questions/1243252/how-to-install-arm-none-eabi-gdb-on-ubuntu-20-04-lts-focal-fossa
- https://james-hui.com/2021/07/02/building-a-small-uboot-linux-and-rootfs-for-arm-cortex-m7/
- opdenacker-understanding-u-boot-falcon-mode.pdf
- https://adrianalin.gitlab.io/popsblog.me/posts/build-linux-for-stm32f769i-disco-using-buildroot/
















