从0开始理解 U-Boot嵌入式系统的第一道门以及我们为什么终于告别了硬编码时代写在前面嘿这里是CharlieChen114514笔者的一位朋友准备接触嵌入式Linux了刚好笔者准备重新试试IMX6ULL更加进一步的重新学习嵌入式Linux。笔者手头的系列文章看了一下感觉不够扎实。所以打算好好的做一下专门的嵌入式Linux系列的开发博客尝试分享给各位入门其实我也算入门的嵌入式Linux爱好者不敢碰瓷真驱动开发和系统裁剪工程师大佬们一些我的踩坑经验虽然我的本职工作跟这个半毛钱关系没有但是我仍然会尝试保证这些内容的可靠性。这个系列的仓库笔者专门维护在一个IMX加油仓库上 Link:Awesome-Embedded-Learning-Studio/imx-forge: This is a repo with templated contains imx6ull-chips, uboot, kernel, rootfs patches to make fast applyURL:https://github.com/Awesome-Embedded-Learning-Studio/imx-forge各位感兴趣的话可以提Issue或者任何问题PS: 挖了不少坑不过没事该填会填嵌入式C开发大致快到C部分的尾声了剩下的可能是笔者象到了可能有帮助的Feature进行补充了笔者会在最后的时候添加这个仓库的下一部分的建设专门发一篇文章说明为什么要写这篇文章笔者是2025年开始的嵌入式Linux的研究的好在之前Linux的使用经验也稍微有一些开发经验多少有那么一点所以还好对我而言学习曲线尚可。但是这个尚可对我还是有点难度的——当我第一次拿到 i.MX6ULL 这块板子的时候我是真的懵了。完全就只会插个电源拨个开关然后对着日志发呆。UBoot的日志就是我们日志的起点相信你可以猜到UBoot就是板子交接的第一个棒。是的——引导加载程序。你可能会说不就是个引导加载程序嘛网上教程一堆照着敲命令不就行了问题就在这里——网上的教程确实不少但要么是几年前的老版本要么就是只给你一堆命令却不告诉你为什么这么敲。我当时折腾了整整一天踩了各种坑工具链版本不对、设备树选错了、编译产物验证不过最离谱的是有一次因为 .config 残留导致改了配置完全不生效排查到半夜看到我靠怎么还是你发现.config的时间戳不对想把地球炸了这件事情告诉我们勤快的清理是必要的是的都 2026 年了我们还在用一套诞生于 20 世纪末的引导加载程序。这倒不是说它过时恰恰相反U-Boot 之所以能活到现在是因为它在不断地进化。但问题在于它的文档太分散了官方文档写得像技术规范书新手看了直摇头社区教程又良莠不齐很多还是基于 2010 年代的经验写的。所以我决定写这篇文章不是为了给你一个复制粘贴就能用的脚本——那个东西我最后会给你——而是想带你完整地走一遍 U-Boot 的来龙去脉。当你理解了它是什么、为什么存在、它是如何从硬编码时代进化到设备树时代的你会发现所谓的黑魔法其实都有清晰的逻辑。U-Boot 到底是什么如果非要用一个类比来解释 U-Boot我会说它就像是电脑的 BIOS但比 BIOS 强大得多。在一台 x86 电脑上当你按下电源键首先运行的是主板上的 BIOS/UEFI它负责初始化硬件、加载操作系统。嵌入式系统的启动流程类似但更加复杂芯片内部的 ROM Code 会先运行一段厂商写死的代码嗯。。。一些厂商会在里面加点小料的这段代码会从外部存储介质eMMC、SD 卡、SPI Flash中加载 U-Boot然后 U-Boot 接管控制权完成更复杂的硬件初始化最后加载 Linux 内核。所以 U-Boot 在启动链条中的位置是这样的ROM Code → U-Boot → Linux Kernel → Rootfs那为什么不能让 ROM Code 直接加载 Linux 内核呢原因很简单ROM 代码的空间极其有限而且它是厂商写死的你改不了。Linux 内核需要知道板子上有哪些硬件、内存地址是什么、外设怎么初始化这些东西 ROM 代码搞不定必须由一个可编程的中间层来完成——这就是 U-Boot 存在的意义。具体来说U-Boot 要做这几件事初始化 DDR 内存、初始化存储设备eMMC、SD 卡、从存储设备中加载设备树和内核镜像、把设备树地址和内核入口地址告诉 CPU、最后跳转到内核启动。它还提供了一个交互式命令行你可以在启动前手动修改环境变量、加载文件、网络启动等等。这部分功能在实际调试中简直救命。从 8xxROM 到 Das U-Boot二十多年的进化史U-Boot 的历史比很多人想象的要长得多。它的前身可以追溯到 1999 年之前的 8xxROM那是 Magnus Damm 为 PowerPC 8xx 系列处理器写的一个个人项目。当时的嵌入式引导加载程序基本是一板一代码换一块板子就要重写一套。1999 年 10 月德国 DENX 软件工程中心的 Wolfgang Denk 接手了这个项目把它搬到了 SourceForge 上。但 SourceForge 不允许用 “8xxROM” 这个名字所以改名叫 PPCBoot——你看得出来这是 PowerPC Boot Loader 的缩写。这个时期 PPCBoot 主要是为 PowerPC 处理器服务的架构支持相对单一。事情在 2002 年发生了转折。当时有一个叫 ARMBoot 的项目是 PPCBoot 的 ARM 架构移植版本。大家觉得维护两个重复的项目没意义于是决定合并。2002 年 7 月开始合并工作2002 年 11 月发布了 PPCBoot 的最后一个版本 2.0.0代号Halloween Release然后同年 12 月 17 日第一个名为 “U-Boot” 的版本 0.2.0 诞生了。“Das U-Boot” 是德语“Das” 是定冠词“U-Boot” 是 “Universal Boot Loader” 的缩写——通用引导加载程序。从那以后PPCBoot 和 ARMBoot 项目停止维护统一到 U-Boot。版本命名也经历了变化。2008 年之前U-Boot 用的是传统数字版本号比如 1.1.3、1.3.4。但从 2008 年 10 月开始版本号改成了基于时间的格式类似 Ubuntuv2009.11 表示 2009 年 11 月发布v2009.11.1 是这个版本的 bug 修复版。这个命名规则一直沿用到现在最新版本是 v2026.01每三个月发布一次。设备树革命为什么我们终于告别了硬编码说到 U-Boot 的进化最彻底的改变莫过于设备树的引入。在设备树出现之前嵌入式系统是怎么描述硬件的呢答案是硬编码在代码里。你可能会问这有什么问题问题大了。想象一下你的板子上有三个 I2C 设备地址分别是 0x50、0x51、0x52。在硬编码时代这些地址要写在板级初始化代码里。如果你想改其中一个地址或者你想给同一款芯片换一个不同容量的 eMMC对不起你得重新编译整个内核。更惨的是如果你要维护同一款芯片的十种不同板型你就得维护十份几乎完全重复的代码只有几个参数不同。ARM Linux 早期使用的是 ATAG 机制——参数标签列表。这是一种在启动时向内核传递参数的方式但功能非常有限基本上只能传内存大小、命令行参数这些基础信息。复杂的硬件描述根本搞不定。2008 年PowerPC 架构已经成功迁移到了设备树机制效果很好。2010 年PowerPC 维护者 Grant Likely 提议 ARM 也跟进从 ATAG 转向设备树描述硬件。这在当时引起了不小的争议因为这意味着要改动大量的板级代码。但历史证明这个决定是正确的。2011 到 2012 年间ARM Linux 开始大规模迁移到设备树ATAG 机制逐步被废弃。设备树的核心思想很简单把硬件描述从代码中分离出来用一种专门的格式DTS 文件来描述。硬件不变设备树就不变硬件变了只需要改设备树不需要改代码。同一个内核镜像配上不同的设备树就可以运行在不同的板子上——这在以前是不可想象的。U-Boot 对设备树的支持是从 v1.1.3 开始的通过 CONFIG_OF_LIBFDT 选项启用。但有趣的是U-Boot 和 Linux 在设备树方面并不是完全同步的。某些板级绑定在两个项目中仍然存在差异为了解决这个问题U-Boot 维护了一个从 Linux 内核同步的设备树子目录通过 devicetree-rebasing 机制保持更新。你在 U-Boot 源码里看到的*-u-boot.dtsi文件就是专门用来添加 U-Boot 特定配置的不会和 Linux 内核的设备树冲突。U-Boot 项目目录结构代码都放在哪现在我们来看看 U-Boot 的源码结构。当你第一次解压 U-Boot 源码包你会发现目录挺多的不知道从哪下手。其实核心目录就那么几个其他的都是辅助性的。arch/目录是架构相关的代码。对于 ARM 平台我们主要看arch/arm/。这里面cpu/存放 CPU 相关的初始化代码mach-imx/是 NXP i.MX 系列处理器的专用代码里面按代数分了mx3/、mx5/、mx6/、mx7/以及更新的imx8/、imx8m/、imx8ulp/、imx9/。dts/存放设备树源文件include/是架构相关的头文件。board/目录是板级支持包里面按厂商和板型分类。比如freescale/下是 NXP 官方开发板的代码boundary/、engicam/、phytec/则是其他厂商的板子。这个目录在设备树普及之前非常重要现在的作用已经弱化了。cmd/目录实现了 U-Boot 的命令行命令。你可以在 U-Boot 命令行里敲boot、bdinfo、bootm、bootefi这些命令它们的实现代码就在这里。如果你需要添加自定义命令也要在这个目录下添加。common/目录是各架构通用的功能代码包括环境变量处理、内存管理、设备初始化等。drivers/目录按设备类型组织驱动clk/是时钟驱动gpio/是 GPIOmmc/是 eMMC/SD 卡spi/、i2c/、uart/顾名思义net/在这里是以太网驱动display/是显示相关。configs/目录可能对你来说最实用——里面存放了 500 多个 defconfig 文件。命名规则一般是平台_板型_功能_defconfig比如imx8mm_evk_defconfig就是 i.MX8MM EVK 开发板的配置。android_gbl这种带特殊前缀的是 Android 相关配置还有带secure的是安全启动相关。dts/目录存放设备树源文件。fs/是文件系统支持包括 FAT、EXT4、UBIFS 等。include/是全局头文件lib/是通用库函数net/是网络协议栈以太网、DHCP、TFTP、DNS。scripts/是构建脚本和配置工具tools/则是各种辅助工具比如我们后面会用到的mkimage。2025-2026 年的 U-Boot老项目的新活力你可能会想U-Boot 这么老的项目现在还有人维护吗答案是肯定的而且很活跃。U-Boot 采用固定的三个月发布周期非常可预测。发布后约 21 天是合并窗口之后进入 RC 阶段主要修复 bug。最新版本是 2026 年 1 月 5 日发布的 v2026.01往前是 v2025.10、v2025.07、v2025.04——我们的项目就是基于 v2025.04 的 NXP 分支。2025 年的版本带来了不少新特性。v2025.07 增加了 Apple M1/M2 SoC 支持改进了 NXP i.MX8/i.MX9 处理器的支持还增强了 AMD Versal 平台和 Raspberry Pi 的支持。Python 工具也得到了改进代码清理和功能增强一直在进行。对于 NXP i.MX 系列的用户来说NXP 维护了一个专门的仓库nxp-imx/uboot-imx针对 i.MX 处理器做了专门优化。i.MX6 系列相对成熟SPL 支持和 HABHigh Assurance Boot安全启动都有。i.MX8/i.MX8X 系列启动架构更复杂需要 SCUSystem Controller Unit和 SCFW 固件参与。i.MX8M 和 i.MX9 系列支持 Falcon Mode 快速启动可以直接跳转到内核或通过 ATF 跳转到 U-Boot。写在最后到这里你应该对 U-Boot 有了一个整体的认识。它不是一个简单的启动程序而是一个经历了二十多年进化的复杂系统。从早期的一板一代码到设备树带来的硬件描述分离从单一的架构支持到现在的多平台覆盖U-Boot 的进化史其实就是嵌入式 Linux 发展史的一个缩影。下一篇文章我们来深入到 U-Boot 的编译过程中。你会看到依赖包为什么必须装、交叉编译是怎么回事、defconfig 和 .config 有什么区别、编译出来的各种产物都是干什么用的。更重要的是你会明白每一行命令背后的原因而不是机械地复制粘贴。准备好了吗我们开始动手编译。