从零构建:Uboot源码获取、配置与编译实战指南
1. 环境准备搭建你的Ubuntu工作站如果你刚接触嵌入式开发或者正准备捣鼓一块新的开发板那么从Uboot开始绝对是个明智的选择。Uboot全称Universal Boot Loader是嵌入式世界的“开机第一棒”负责初始化硬件、加载操作系统内核。没有它你的开发板就是一块“砖头”。今天我就带你从零开始在Ubuntu 20.04系统上把Uboot的源码拿到手配置好并成功编译出一个能用的镜像。整个过程我会尽量讲得细一点把我自己踩过的坑和总结的经验都分享给你保证你跟着做就能成功。首先你得有一台运行Ubuntu 20.04 LTS的电脑或虚拟机。为什么推荐20.04因为它是一个长期支持版本社区资源丰富软件包稳定遇到问题也容易找到解决方案。我用的是实体机当然你用虚拟机比如VirtualBox或VMware也完全没问题分配个20GB以上的磁盘空间和4GB内存就够用了。接下来我们需要安装一些基础的编译工具和依赖库。打开你的终端CtrlAltT我们先更新一下软件包列表确保获取到最新的软件信息。这个操作就像去超市购物前先看看货架清单一样。sudo apt update更新完成后我们安装一系列必要的软件包。别被这一长串命令吓到它们都是构建Uboot乃至后续嵌入式开发的基础“砖瓦”。build-essential包含了GCC、make等核心编译工具bison和flex是语法分析器生成器Uboot的配置系统会用到libssl-dev提供加密相关的库支持device-tree-compiler简称dtc超级重要它是编译设备树文件的工具现代嵌入式Linux都离不开设备树来描述硬件。sudo apt install build-essential bison flex libssl-dev device-tree-compiler安装过程可能会花几分钟取决于你的网速。完成后我们可以先验证一下最基础的gcc和make是否就位。gcc --version make --version如果终端能正确打印出版本信息比如gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0那说明本地编译环境的基础已经打好了。不过我们这次的目标是给ARM架构的开发板比如树莓派、全志H3/H5系列、瑞芯微RK系列等编译Uboot这就需要用到交叉编译工具链。简单来说就是在你的x86电脑上生成能在ARM芯片上运行的代码。这个工具我们稍后再安装因为它的选择和获取本身也有点讲究。提示在整个过程中请确保你的网络连接顺畅因为我们需要从网上下载源码和工具。如果遇到下载慢的问题可以考虑为apt和git配置国内镜像源这会大大提升效率。2. 源码获取从官方仓库“克隆”宝藏环境准备好了现在我们来获取Uboot的源代码。我强烈建议直接从官方仓库获取而不是随便在网上下载一个压缩包。官方仓库不仅代码最新而且包含了完整的提交历史方便你追溯问题或者切换到任何一个历史版本。Uboot的官方主仓库托管在source.denx.de这是由Uboot的维护方DENX直接管理的。此外在GitHub上也有一个实时同步的镜像仓库。对于国内开发者来说从GitHub克隆速度可能会更快一些。我们打开终端找一个你喜欢的目录比如在用户主目录下创建一个work文件夹来存放我们的项目。cd ~ mkdir -p work/u-boot cd work/u-boot现在使用git命令克隆仓库。这里我以GitHub镜像为例git clone https://github.com/u-boot/u-boot.git这个命令会创建一个名为u-boot的目录并把所有的源代码和历史记录都拉取到本地。仓库大小大约几百MB需要一点时间下载你可以先去泡杯茶。克隆完成后进入源码目录cd u-boot此时你位于默认的master分支这里是最新的开发代码可能包含一些尚未稳定的特性。对于学习和实验用master分支没问题。但如果你要为某个具体的硬件做产品开发我建议切换到某个稳定的发布版本标签Tag。你可以用下面的命令查看所有标签git tag -l | grep -E ^v20[0-9]{2}\.[0-9]{2}$ | sort -V | tail -10这个命令会列出最近10个稳定版标签命名规则是v年份.月份比如v2023.10、v2024.01。假设我们选择一个比较稳定且兼容性广的版本v2023.04可以这样切换git checkout v2023.04切换后终端会提示HEAD is now at ...。这样就确保了我们工作在一个已知的、相对稳定的代码基础上能避免很多新版本可能引入的奇怪问题。完成这一步Uboot这座“金矿”的源代码就已经完整地躺在你的硬盘里了。注意有些教程会教你去官网找FTP下载链接下压缩包我不是很推荐。git的方式灵活太多了你可以随时git pull更新随时切换版本还能用git log查看某行代码是谁、为什么修改的这对深入学习非常有帮助。3. 交叉编译工具链打造ARM代码的“翻译官”源码有了接下来我们需要一个关键的“翻译官”——交叉编译工具链。还记得吗我们的电脑宿主机通常是x86_64架构而目标开发板是ARM架构可能是32位的arm也可能是64位的aarch64。直接在我们电脑上编译出来的程序ARM芯片是看不懂也执行不了的。交叉编译工具链就是一套运行在x86上但专门生成ARM指令集程序的编译器、链接器等工具的集合。如何获取这个工具链呢有几个主流途径使用发行版提供的包最简单。对于Ubuntu我们可以直接用apt安装。从芯片厂商的SDK中获取如果你用的是特定厂商的开发板如NXP、瑞芯微他们提供的SDK里通常包含了优化过的工具链兼容性最好。从Linaro或ARM官方下载这是通用的、性能优化的工具链。对于初学者我强烈推荐第一种方法简单快捷足以应对大多数情况。假设你的开发板是64位的ARMv8架构也就是aarch64安装命令如下sudo apt install gcc-aarch64-linux-gnu如果你的开发板是32位的ARMv7架构则安装sudo apt install gcc-arm-linux-gnueabihf这里的gnueabihf后缀表示支持硬件浮点单元性能更好。安装完成后务必验证工具链是否可用。我们以aarch64为例aarch64-linux-gnu-gcc --version如果终端成功输出类似aarch64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0的信息并且没有报“命令未找到”那么恭喜你工具链安装成功了。这个验证步骤很重要我见过不少朋友后面编译出错折腾半天才发现是工具链根本没装对或者没生效。为了让后续的make命令知道该使用哪个工具链我们需要设置两个环境变量。你可以直接在终端里临时设置关闭终端就失效也可以写入到你的shell配置文件如~/.bashrc中永久生效。我们先采用临时设置的方式方便调试export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu-请特别注意CROSS_COMPILE变量值最后的那个短横线-它非常重要。这个变量告诉make系统所有编译工具如gcc、ld、objcopy的前缀都是aarch64-linux-gnu-。比如当make需要调用编译器时它会去寻找aarch64-linux-gnu-gcc这个命令。如果你不小心漏掉了短横线编译时会报一堆找不到命令的错误。4. 配置与定制为你的开发板“量体裁衣”现在进入最核心也最容易让人困惑的一步配置。Uboot支持成百上千种开发板我们下载的源码是一个“大杂烩”。编译前我们必须告诉系统“我要为哪块板子编译”。这就是配置的作用。进入Uboot源码的根目录你会发现一个叫configs/的文件夹。这里面存放了所有官方支持的开发板的配置模板文件名格式统一为board_name_defconfig。比如树莓派3的配置模板是rpi_3_defconfig树莓派4的是rpi_4_defconfig。你的任务就是找到最适合你手头开发板的那个模板。如何找呢首先确定你的开发板型号和主控芯片。然后可以到configs/目录下用grep命令搜索关键词。例如如果你用的是瑞芯微的RK3566芯片可以这样搜索grep -i rk3566 configs/*.config或者直接列出所有RK相关的配置看看ls configs/ | grep -i rk假设我们找到的配置模板是rock-3a-rk3568_defconfig对应Radxa ROCK 3A开发板。那么生成实际配置文件.config的命令非常简单make rock-3a-rk3568_defconfig执行这个命令后屏幕上会滚过一系列“Checking...”、“Setting...”、“# configuration written to .config”的信息。如果最后没有报错并且在源码根目录生成了一个名为.config的隐藏文件那么配置就成功了。这个.config文件包含了成千上万个配置项决定了最终Uboot镜像包含哪些功能、驱动和参数。有时候默认的配置模板可能不完全符合你的需求。比如你可能想启用USB网络功能或者改变内核的加载地址。这时你可以使用一个图形化的菜单配置工具来进行微调make menuconfig这个命令会打开一个基于ncurses的文本图形界面。你可以通过方向键导航空格键选中/取消选中功能[*]表示编译进镜像[M]表示编译为模块[ ]表示不编译回车键进入子菜单。对于初学者我建议先不要在这里做太多改动使用默认的defconfig生成的配置就好。等你熟悉了各个配置项的含义再进来探索。修改完成后选择Save保存然后Exit退出。注意make menuconfig需要libncurses库的支持。如果你运行命令时提示找不到ncurses.h需要先安装libncurses5-dev或libncursesw5-dev包sudo apt install libncurses5-dev。5. 编译实战生成最终的U-Boot镜像配置搞定工具链就绪终于到了激动人心的编译环节编译命令本身非常简单就是在源码根目录执行make -j$(nproc)让我解释一下这个命令。make是构建程序的指挥官它会读取Makefile和.config文件调用我们之前设置好的交叉编译工具链将C源码、汇编代码一步步编译、链接成可执行文件。后面的-j$(nproc)是一个性能优化选项。$(nproc)这个命令会获取你电脑CPU的核心数-j参数则表示使用多线程并行编译。比如你的CPU是8核那么-j8就会启动8个编译任务同时进行能极大缩短编译时间。如果你不确定核心数直接用-j4或-j8也是常见的做法。按下回车后终端会开始疯狂输出编译信息。你会看到很多以CC编译、AS汇编、LD链接开头的行。这是一个正常的、健康的编译过程。第一次编译可能会花费几分钟到十几分钟取决于你的电脑性能和Uboot配置的复杂程度。期间CPU风扇可能会狂转这是正常的。如果一切顺利编译完成后你会在终端看到类似这样的总结信息并且最后没有报错。此时在源码根目录下你会找到几个非常重要的输出文件u-boot这是U-Boot的ELF格式可执行文件包含调试符号主要用于调试。u-boot.bin这是我们最需要的、可以直接烧写到开发板存储设备如eMMC、SPI Flash的纯二进制镜像文件。它是由u-boot文件经过objcopy工具剥离掉调试信息后生成的。u-boot.img在某些平台如Rockchip可能需要这个格式的镜像它是在.bin文件基础上加了一个文件头。u-boot.srecMotorola S-Record格式用于通过串口等方式下载。对于大多数情况你只需要关注u-boot.bin。你可以用ls -lh u-boot.bin命令查看它的大小。一个典型的、功能齐全的U-Boot镜像大小可能在几百KB到1MB左右。6. 常见问题与排坑指南编译过程很少一帆风顺尤其是第一次。下面我总结几个我遇到过并且网上提问率很高的典型问题及其解决方案。### 6.1 编译工具链未找到或未设置这是最常见的问题。症状是编译一开始就报错提示aarch64-linux-gnu-gcc: command not found或者Makefile:xxx: *** No rule to make target ...。检查首先运行echo $CROSS_COMPILE看看输出是不是aarch64-linux-gnu-注意结尾的短横线。如果不是请重新设置环境变量。验证运行which aarch64-linux-gnu-gcc看系统是否能找到编译器。如果找不到说明工具链没安装成功请用apt list --installed | grep gcc-aarch64确认安装状态并重新安装。注意如果你打开了多个终端窗口环境变量只在设置它的那个窗口生效。确保你在同一个终端窗口里执行了export命令和make命令。### 6.2 Python或swig相关错误错误信息可能类似unable to execute swig: No such file or directory或者ModuleNotFoundError: No module named libfdt。这是因为新版本的Uboot构建系统部分工具是用Python编写的并且依赖swig等工具。解决方法就是安装这些依赖sudo apt install swig python3-dev python3-pip libpython3-dev有时候还需要通过pip安装一些Python包但通常apt安装的python3-dev已经包含了构建所需的核心库。安装完这些依赖后最好先执行一下make clean然后再重新开始配置和编译以清除之前可能生成的有问题的中间文件。### 6.3 特定版本源码的兼容性问题就像原始文章里提到的有时最新的Uboot源码比如当时的2022.04版本可能会存在一些暂时的bug或与某些工具链不兼容。如果你在编译最新master分支或某个新版本时遇到莫名其妙的错误而错误信息又很模糊一个非常有效的策略就是回退到一个稍旧的稳定版本。例如你正在用v2024.07编译出错可以尝试切换到v2023.10git checkout v2023.10 make distclean # 彻底清理比clean更干净 # 然后重新执行配置和编译步骤make distclean会删除所有编译生成的文件以及配置文件让你从一个绝对干净的状态开始。在嵌入式开发中“换个版本试试”是一个简单粗暴但往往很管用的招数。### 6.4 配置文件.config导致的编译错误如果你通过make menuconfig修改了大量配置编译时可能会遇到某些功能依赖未满足而报错。这时一个稳妥的方法是回到起点make mrproper这个命令会删除.config文件以及几乎所有编译产物但会保留你下载的源码。执行后重新从make board_defconfig开始。在不确定的情况下尽量少改动默认配置。7. 进阶深入理解配置与定制当你成功完成第一次编译后可能会想更进一步这个.config文件里到底有什么我该如何为自己的定制硬件调整配置.config文件是一个文本文件里面每一行都是一个配置项格式如CONFIG_CMD_BOOTMy。y表示该功能被编译进镜像m表示编译为模块U-Boot动态加载# CONFIG_... is not set表示禁用。你可以用文本编辑器打开它看看但内容非常多。更有效的方式是利用scripts/kconfig/下的工具来查询。比如你想知道所有关于USB的配置项grep -i usb .config或者你想知道CONFIG_CMD_BOOTM这个配置项是在哪个菜单里它的帮助信息是什么make menuconfig然后在菜单界面中按下/键输入CMD_BOOTM进行搜索界面会显示该配置项的路径和说明。对于自定义开发板通常的做法是在configs/目录下找一个硬件最接近的_defconfig文件作为基础复制一份重命名为你自己的myboard_defconfig。然后根据你的硬件差异比如DDR容量、网络PHY型号、串口引脚等用文本编辑器修改这个文件里的配置项。最后用make myboard_defconfig来生成你的专属配置。这需要你对硬件和Uboot的驱动模型有一定的了解是嵌入式工程师进阶的必经之路。整个流程走下来从环境搭建到镜像生成你可能需要一两个小时。但这个过程的价值远不止得到一个u-boot.bin文件。你熟悉了交叉编译的概念掌握了git管理源码的基本操作理解了基于defconfig和menuconfig的配置系统并亲手解决了几个典型的编译错误。这些经验在你后续移植Linux内核、构建根文件系统时都会反复用到。嵌入式开发就是这样动手趟过一次坑路就通了。下次当你需要为另一块板子编译Uboot时你会发现速度大大加快因为大部分步骤已经成了肌肉记忆。