1. 配置LDSCRIPT,确定 U-Boot 链接脚本(Linker Script)的绝对路径
    1. BOARDDIR:在根目录下config.mk中定义。

    2. CONFIG_SYS_LDSCRIPT是老的uboot遗留下来的,这段现在不是对LDSCRIPT设置



  2. 配置LDSCRIPT,确定 U-Boot 链接脚本(Linker Script)的绝对路径
    1. $(wildcard ...):这是 GNU make 的内置函数。它会检查参数中匹配的文件是否存在,并返回存在的文件的路径。如果文件不存在,则返回空字符串。

  3. 根目录下的 config.mk 文件被称为“全局编译规则编译器”“变量大本营”。


  4. U-Boot 编译出来的固件体积与运行速度之间的关键权衡


  5. 都是一些编译参数设置
    1. -fno-stack-protector:剥离堆栈保护器
    2. -fno-delete-null-pointer-checks:拦截空指针优化陷阱
    3. -g:向编译器和汇编器统一注入 -g 参数。这意味着编译出来的 ELF 固件里会包含完整的调试符号表(保留了原始的 C 语言行号、变量名映射)
    4. -Wno-format-nonliteral:过滤无意义的报警


  6. 一个针对特定古董硬件架构(Motorola 68000,简称 m68k)以及特定老版本 GCC 编译器(GCC 3.4)的“陈年陈条”和 Bug 修复兜底机制

    1. include scripts/Makefile.extrawarn:编译命令后面加上 W=1,这个脚本就会彻底觉醒,用最严苛的洁癖视角审视所有的驱动 C 代码,帮你抓出隐藏的隐患。
    2. make KCFLAGS="-O3 -DDEBUG_MY_DRIVER":通过加算 $(KCFLAGS),向开发者递出了全系统的最高指挥棒。

    1. UBOOTINCLUDE:该变量使用 GCC 的 -I 参数,按优先级从上到下显式指定了头文件的搜寻目录:

    2. NOSTDINC_FLAGS:切断宿主机依赖,目的在于使编译环境与 PC 操作系统脱钩

    3. cpp_flags:组合了预处理器所需的全部资源(全局预处理标志、平台预处理标志、刚才计算出的 U-Boot 头文件路径、以及屏蔽系统头文件的 -nostdinc 标志)。

    4. c_flags:将 C 编译器的专属参数(如之前计算出的 -O2 优化级别、-g 调试符号等)与预处理器标志 cpp_flags 进行叠加。



  7. 构建 U-Boot 编译阶段的核心目标库列表(libs-y 变量)。它决定了接下来的 Kbuild 编译系统需要深入到源码树的哪些子目录中去编译驱动、文件系统、网络协议栈等模块,并最终将它们打包成 .a 静态库文件以供链接。
  8. scripts/Makefile.build:在 U-Boot 中,顶层 Makefile 本身不负责直接编译具体的 .c 文件。当顶层 Makefile 决定要编译某个子目录(例如你刚才看到的 drivers/gpio/)时,它会通过 make -f scripts/Makefile.build obj=drivers/gpio 命令,把实际的编译重任委派给 scripts/Makefile.build

    1. u-boot-dirs:清洗目录尾部斜杠并追加工具目录 tools。这个变量主要用于控制接下来 make 递归进入子目录编译的循环序列。去掉了斜杠,Make 引擎才能结合 scripts/Makefile.build 精准定位到物理文件夹。
    2. u-boot-alldirs:收集全量目录用于清理。这个变量包含了 U-Boot 工程下几乎所有的子目录。它最核心的用途是提供给 make cleanmake mrpropper 命令。当执行清理工程时,系统直接遍历 u-boot-alldirs,就能把所有目录(不管有没有被编译)底下的垃圾文件一次性全部扫除干净。
    3. u-boot-init:来自于 $(head-y)。在 arch/arm/Makefile 中,它被赋值为 arch/arm/cpu/armv7/start.o。这是 U-Boot 的第一行汇编代码所在的位置。由于它被拎出来作为 u-boot-init,链接脚本 u-boot.lds 会在最后组装时,强行把它放在二进制镜像的最前端(0x0 偏移处)。只有这样,i.MX6ULL 芯片上电时,Boot ROM 才能第一眼看到它并正确执行。
    4. u-boot-main:就是刚才拼装完毕的、包含了各个驱动目录下 built-in.olibs-y 列表。


  9. 为 U-Boot 链接阶段引入底层数学与位运算支持库(libgcc.a。动态链接交叉编译器自带的官方库(正点原子默认)


  10. 构建链接脚本预处理器标志(LDPPFLAGS 变量)

    1. BOARD_SIZE_CHECK:定义一个用于检测编译输出固件体积是否超标的 Shell 脚本变量。在嵌入式开发中,特定硬件平台分配给 U-Boot 的存储空间(如片内高速度 SRAM、内部 Flash 或特定的分区表边界)是存在物理上限的。如果编译出的固件超出了这个限制,强行烧录会导致覆盖其他数据区或硬件无法启动。


  11. 定义一个对编译出的二进制固件进行“静态链接重定位修复”的 Shell 脚本宏变量(DO_STATIC_RELA)。

    在现代 64 位 CPU 架构(如 ARM64)中,U-Boot 为了支持位置无关代码(PIC/PIE),即允许 U-Boot 镜像在运行期间动态地把自己从一个内存地址拷贝到另一个内存地址(重定位),会在 ELF 文件中生成一个动态重定位段(.rel.dyn.rela.dyn)。

    但是,在 U-Boot 刚刚上电、DDR 内存还没初始化、重定位代码还没运行的极早期裸机阶段,这些位置无关的绝对地址指针是无效的。为了让 U-Boot 在最早期能正常读取全局变量,必须在编译阶段通过外部工具,把这些动态重定位关联的物理地址硬编码写入二进制文件中。这就是“静态重定位修复”。



  12. 构建 U-Boot 编译结束时最终需要生成的“终极目标文件列表”(ALL-y 变量)。在 Makefile 中,一个庞大的工程最终会输出很多种格式的文件(如纯二进制、符号表、配置文件等)。Kbuild 通过 ALL-y 变量把这些需要输出的目标全部打包收集起来,作为默认编译规则(all: 伪目标)的依赖项。
    1. ALL-y:无条件收集的核心基础目标
    2. CONFIG_ONENAND_U_BOOT:如果板子采用的是三星的 OneNAND 闪存芯片作为启动介质
    3. CONFIG_SPL_FSL_PBL:FSL(飞思卡尔/NXP)处理器引导加载构造分支

    1. spl/u-boot-spl.bin:如果开启了二级启动(CONFIG_SPL=y),则追加生成 SPL 的二进制镜像。

    2. u-boot.img:如果使用了标准 SPL 架构外壳(CONFIG_SPL_FRAMEWORK=y),SPL 需要识别特定格式的 U-Boot 镜像。u-boot.img 是在纯二进制的 u-boot.bin 前面加上了 64 字节的 MKIMAGE 头部信息(包含镜像类型、加载地址、校验和等),专供 SPL 识别并引导。

    3. tpl/u-boot-tpl.bin:三级引导目标。在某些片内 SRAM 空间极度严苛的芯片上,先跑 SPL,再跑 TPL,最后才进 U-Boot。对于 i.MX6ULL,此项未定义。

    4. u-boot.dtb:如果开启了设备树分离模式(CONFIG_OF_SEPARATE=y,这是正点原子 i.MX6ULL 的默认配置),系统会将设备树源码(.dts)编译为独立的二进制设备树文件(.dtb),并将 u-boot.dtb 加入生成目标列表。

    5. u-boot-dtb.img:当同时满足“使用 SPL 框架”且“设备树分离”时追加。该目标在后续规则中,会把 u-boot.bin 和编译好的 u-boot.dtb 物理上拼接到一起,然后再用 mkimage 工具加上 64 字节头部,生成一个复合镜像,供 SPL 一次性加载。

    6. CONFIG_OF_HOSTFILE:这是 U-Boot 在 Sandbox(沙盒模拟)模式下,直接读取宿主机 Linux 里的设备树文件时使用的配置。

    7. 有些 SoC 原厂(例如三星、TI 或部分 NXP 芯片)的底层 Boot ROM 不认标准的 u-boot-spl.bin,需要特定格式(如 u-boot-spl.imxMLO)。原厂会在 Kconfig 中将这个特定的文件名写入 CONFIG_SPL_TARGET 字符串。

    8. u-boot.elf:重新生成带完整符号表的 ELF 格式可执行文件。

    9. u-boot-app.efi / u-boot-payload.efi:当把 U-Boot 配置为作为 UEFI 应用程序运行,或者作为 UEFI 的 Stub(存根)载荷时,追加生成符合 UEFI 规范的可执行文件。

    10. 追加生成 u-boot.rom。这是专门针对 X86 物理主板 BIOS 闪存颗粒的大小经过精密填充和对齐后生成的完整镜像,可以直接用烧录器刷入主板的 Flash 芯片中。对于你的 ARM 架构平台,此判断不成立。



  13. 为核心目标文件 u-boot 的链接过程初始化链接器参数(LDFLAGS
    1.  ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy):Tegra 平台的专属镜像拼装规则
    2. CONFIG_BUILD_TARGET:释放原厂/板级定制目标的接口
    3. LDFLAGS_u-boot:构建核心链接器参数



  14. 定义了一组底层的核心转换与拼装命令工具箱(Kbuild 体系中的 cmd_xxx 规则)。在编译的最后阶段,链接器(ld)输出的其实是一个很大的、带符号表的 u-boot ELF 格式文件。而我们在正点原子开发板上烧录需要的 .bin.imx 固件,就是通过这段代码里定义的 objcopymkimagecat 等命令对该 ELF 文件进行脱壳、填充、加头和拼接而来的。

    Kbuild 费尽心机搞这一套 cmd_xxx 抽象的目的:除了前面提到的让控制台输出更干净(V=0 清爽,V=1 详细)之外,这种抽象带来了一个嵌入式开发不可或缺的超强功能:检测编译参数变化。再次编译时,Kbuild 会拿当前的 cmd_xxx 字符串和隐藏文件里的旧字符串进行碰撞。一旦发现任何一个编译参数(如基地址、优化级、甚至多了一个字母)发生了变化,即便源码没动,系统也会立刻强制重新编译该目标!
  15. continues
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐