uboot编译脚本分析

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

332.3.5uboot编译脚本分析

#!/bin/bash
# ↑ 指定由 Bash 解释器执行这个厂商提供的构建脚本。

export ARCH=arm
# ↑ 把目标体系结构设为 ARM,并导出给后续 make 及其子进程。

#export CROSS_COMPILE=/usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-none-linux-gnueabi-
# ↑ 这是原脚本中已被“#”注释的旧工具链前缀,不会执行。

export CROSS_COMPILE=arm-linux-gnueabihf-
# ↑ 指定当前使用的交叉工具链前缀;末尾的连字符属于前缀本身。

make distclean
# ↑ 执行彻底清理,删除旧配置以及此前生成的多数构建文件。

#make mx6ul_topeet_defconfig
# ↑ 这是原脚本中已停用的旧默认配置命令,只保留作参考。
if   [ "$1" = "nand" ]
# ↑ 判断脚本收到的第一个位置参数是否严格等于 nand。
then
# ↑ nand 条件成立时进入该分支。
        make mx6ul_topeet_nand_defconfig
# ↑ 以迅为 i.MX6UL/ULL 开发板的 NAND 模板生成完整 .config。
else
# ↑ 未传 nand(包括不传参数)时进入默认分支。
        make mx6ul_topeet_emmc_defconfig
# ↑ 以同一开发板的 eMMC 模板生成完整 .config。
fi
# ↑ 结束 if 条件结构。

make 2>&1
# ↑ 执行默认构建,并把标准错误 fd 2 合并到标准输出 fd 1。

# configs/mx6ul_topeet_emmc_defconfig:视频打开的 eMMC 默认配置。
CONFIG_ARM=y
# ↑ 选择 ARM 体系结构。
CONFIG_ARCH_MX6=y
# ↑ 选择 NXP/Freescale i.MX6 SoC 系列。
CONFIG_TARGET_MX6UL_TOPEET=y
# ↑ 选择迅为 mx6ul_topeet 板级目标。
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ul_topeet/imximage.cfg,MX6UL_EVK_EMMC_REWORK"
# ↑ 指定 i.MX 启动镜像配置,并启用该源码中的 eMMC 接线/板级选项。
CONFIG_CMD_GPIO=y
# ↑ 把 GPIO 命令功能设为启用。
CONFIG_CMD_DHCP=y
# ↑ 把 DHCP 网络命令设为启用。
CONFIG_CMD_PING=y
# ↑ 把 ping 网络命令设为启用。

# configs/mx6ul_topeet_nand_defconfig:视频对照的 NAND 默认配置。
CONFIG_ARM=y
# ↑ NAND 配置同样选择 ARM 体系结构。
CONFIG_ARCH_MX6=y
# ↑ NAND 配置同样选择 i.MX6 SoC 系列。
CONFIG_TARGET_MX6UL_TOPEET=y
# ↑ NAND 配置仍然使用同一个迅为板级目标。
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ul_topeet/imximage.cfg,SYS_BOOT_NAND"
# ↑ 与 eMMC 文件的关键差异:这里启用 SYS_BOOT_NAND。
CONFIG_CMD_GPIO=y
# ↑ 把 GPIO 命令功能设为启用。
CONFIG_CMD_DHCP=y
# ↑ 把 DHCP 网络命令设为启用。
CONFIG_CMD_PING=y
# ↑ 把 ping 网络命令设为启用。

# .config:视频实际框选的两个生成结果示例。
CONFIG_CMD_PING=y
# ↑ y 表示 CONFIG_CMD_PING 这个布尔配置被启用。
# CONFIG_CMD_CDP is not set
# ↑ 这是 Kconfig 表示布尔配置关闭的标准形式,不是“把变量赋值为 #”。

# net/Makefile:视频用于说明配置开关如何控制目标文件的规则。
obj-$(CONFIG_CMD_PING) += ping.o
# ↑ CONFIG_CMD_PING=y 时展开为 obj-y += ping.o,ping.c 会被编译成 ping.o 并纳入构建。

# 顶层 Kconfig:视频最后滚动展示的一个真实菜单项片段。
menuconfig EXPERT
# ↑ 定义名为 EXPERT 的菜单型配置项。
	bool "Configure standard U-Boot features (expert users)"
# ↑ 声明它是布尔项,并给出 menuconfig 界面中显示的文字。
	default y
# ↑ 未被其他条件覆盖时,其默认值为 y。

本节分析的是课程配套源码根目录中的 create.sh,以及它串联起来的 defconfig、.config、Makefile 和 Kconfig。课程源码顶层 Makefile 标明版本为 U-Boot 2016.03;本文中的文件名与规则均以该版本、该厂商分支为准。

整体构建链可以先记为:

create.sh → 设置 ARCH/CROSS_COMPILE → make distclean → 选择 eMMC 或 NAND defconfig → 生成 .config → make → Makefile/Kbuild 按配置选择源码 → 编译 .o → 链接并生成 U-Boot 镜像

这里有五类容易混淆的文件:

文件来源作用是否应直接维护
create.sh迅为在源码根目录增加的 Bash 包装脚本固定构建环境并串起清理、配置、编译可以阅读或按项目需求维护
configs/*_defconfig板级默认配置模板保存少量非默认选择,作为生成 .config 的输入板级默认需求变化时维护
.configKconfig 工具生成保存本次构建解析后的完整配置通常通过 menuconfig 修改,不建议随意手改
Kconfig源码树中的配置描述文件定义选项类型、提示、依赖、默认值和菜单结构新增或重构功能配置时维护
Makefile源码树中的构建规则根据配置决定目录、源码、目标和链接方式新增源文件或改变构建关系时维护

一、任务资源导图

上一节沿着函数调用追踪 U-Boot 的运行流程;本节换到构建视角,回答“这么多源文件为什么只编译其中一部分”。学习时不要试图一次读完整个顶层 Makefile,而应抓住一条可验证的小链路:

CONFIG_CMD_PING=y → obj-$(CONFIG_CMD_PING) += ping.o → obj-y += ping.o → ping.c 被编译并纳入 U-Boot

围绕这条链路,再分别理解四个问题:

  1. create.sh 如何选择开发板配置?
  2. defconfig 如何生成隐藏文件 .config?
  3. .config 如何影响子目录 Makefile?
  4. menuconfig 的菜单又由哪些 Kconfig 文件定义?

源码很多时,重复走调用或构建链路是有效方法,但应同时观察准确的文件路径、变量名、条件和注释,不能只记住口述简称。

二、Uboot编译脚本解析

2.1 脚本分析流程

课程中的思维导图把构建过程压缩成五个作用点:设置架构、设置交叉编译器、清理、生成配置、编译。这是便于记忆的流程摘要,不是说 create.sh 字面上只有五条有效语句;真实脚本还包含 eMMC/NAND 条件分支、两个已注释的旧命令和输出重定向。

脚本阶段实际内容结果
选择目标架构export ARCH=arm后续构建按 ARM 目录和规则工作
选择工具链export CROSS_COMPILE=arm-linux-gnueabihf-make 可组合出 arm-linux-gnueabihf-gcc、ld、objcopy 等工具名
清理旧状态make distclean清除对象文件、生成文件和 .config 等旧配置
选择板级模板根据 $1 调用 NAND 或 eMMC defconfigKconfig 解析模板、默认值和依赖,生成新的 .config
开始构建make 2>&1执行默认构建,并让错误与普通输出显示在同一输出流中

create.sh 没有使用 set -e,各步之间也没有用 && 连接。因此某一步失败时,Bash 不一定立即停止整个脚本;学习和排错时要从输出中确认 distclean、defconfig 和最终 make 是否分别成功,不能只看脚本是否跑到最后。

2.2 Uboot源码分析

2.2.1 create.sh脚本解析

脚本名是 create.sh,不是转录中的 “Crit.sh” 或 “create 点 sh”。它是厂商为了这块开发板封装的便捷入口,不是所有 U-Boot 源码都必须具有的上游标准脚本。

  1. ARCH 不是编译器

    ARCH=arm 告诉构建系统目标体系结构是 ARM。它与运行构建命令的 Ubuntu 主机体系结构不是一回事。

  2. CROSS_COMPILE 是工具名前缀

    arm-linux-gnueabihf- 末尾的 - 不能漏掉。构建规则会在此前缀后拼接 gcc、ld、ar、objcopy 等名称。视频终端中输入该前缀后按 Tab,列出的正是这组交叉工具。

    那条以 #export CROSS_COMPILE=/usr/local/... 开头的长路径已被 Shell 注释,不会与当前设置同时生效。

  3. make distclean 不只删除配置文件

    课程为便于理解只强调了“清除旧配置”。在本版本顶层 Makefile 中,distclean 先依赖 mrproper,而 mrproper 又依赖 clean;它会删除大部分编译产物、所有生成配置以及编辑器备份、补丁残留等文件。因此,未保存到 defconfig 的 .config 定制会被下一次 create.sh 清掉。

  4. 第一个参数决定启动介质配置

    ./create.sh
    ./create.sh nand
    

    不传参数时 $1 为空,条件不成立,默认选择 mx6ul_topeet_emmc_defconfig;第一个参数恰好为 nand 时,选择 mx6ul_topeet_nand_defconfig。传入其他字符串也会落入 eMMC 分支。

    两份模板共有 7 行,真正的关键差异位于第 4 行:eMMC 版本包含 MX6UL_EVK_EMMC_REWORK,NAND 版本包含 SYS_BOOT_NAND。文件名必须按课程源码写成 mx6ul_topeet_*_defconfig,不能凭开发板芯片名称擅自改成其他目标名。

  5. 2>&1 不是编译选项

    Linux Shell 中,文件描述符 1 是标准输出,2 是标准错误;2>&1 让错误输出跟随标准输出当前的去向。脚本没有再接 tee 或重定向到文件,所以它只是让两类信息统一显示,并不会自动保存编译日志。

U-Boot 官方 GCC 构建文档同样说明:<board>_defconfig 用于生成 .config,交叉编译时 CROSS_COMPILE 可以预先导出,也可以在 make 命令行传入。

2.2.2 Makefile分析

执行裸命令 make 时,GNU Make 首先读取源码根目录的顶层 Makefile,再按照其中的规则进入各子目录、读取子目录 Makefile 并构建目标。更准确的说法是“Make 根据 Makefile 描述的依赖和规则执行命令”,而不是“运行 Makefile 文件”或“执行所有 .c 文件”。

顶层与子目录 Makefile 的分工如下:

  • 顶层 Makefile:处理配置、工具链参数、公共编译选项、递归构建、链接和最终镜像目标。
  • 子目录 Makefile:声明本目录哪些 .o 无条件或按 CONFIG_* 条件加入构建。
  • 源文件:只有被相应规则选中的 .c、.S 等文件才会被编译。
  • 目标文件:.c 通常先编译成 .o,再汇总到目录目标并参与最终链接,不是单独的可执行程序。

U-Boot 采用源自 Linux Kbuild 的 obj-y 风格。关于 obj-$(CONFIG_FOO) += foo.o 如何按配置展开,可对照 Linux 官方 Kbuild Makefile 文档;课程源码中的 net/Makefile 正是在使用这一机制。

2.2.2.1 例题: 配置文件内容

make mx6ul_topeet_emmc_defconfig 的作用不是把 7 行模板逐字复制成 7 行 .config。Kconfig 配置工具会读取该模板、整个 Kconfig 依赖树和各项默认值,最终生成包含数百个已启用或未启用状态的完整 .config。这也解释了为什么视频中的 .config 远长于 configs/mx6ul_topeet_emmc_defconfig。

.config 位于源码或指定输出目录的根部,文件名以 . 开头,所以普通 ls 默认不显示,使用 ls -a 才能看到。两种常见布尔状态是:

CONFIG_CMD_PING=y
# CONFIG_CMD_CDP is not set

第一行表示启用;第二行整行是 Kconfig 生成的注释形式,表示该配置为 n。因此原笔记所说的“赋值为 #”不准确,# 只是注释起始符。

正式构建时,顶层 Makefile 还会根据 .config 生成并包含 include/config/auto.conf、自动配置头文件等中间结果,使同一批配置既能影响 Makefile 选择,又能通过 CONFIG_* 宏影响 C 代码条件编译。.config 是配置链的核心输入,但不是唯一被编译器直接读取的文件。

.config 可以手动编辑,却可能制造不满足依赖的组合;课程使用 make menuconfig 修改更安全。U-Boot 的 Kconfig 迁移和配置目标说明可参见 U-Boot 官方 Kconfig 文档。

2.2.2.2 例题: 网络命令配置

课程用 ping 展示从配置到目标文件的完整对应关系:

  1. eMMC 与 NAND 两份板级 defconfig 都包含 CONFIG_CMD_PING=y。
  2. Kconfig 生成的 .config 中也出现 CONFIG_CMD_PING=y。
  3. net/Makefile 包含 obj-$(CONFIG_CMD_PING) += ping.o。
  4. 变量展开后成为 obj-y += ping.o,于是 net/ping.c 被编译为 net/ping.o,再纳入目录及最终链接目标。

如果该选项未启用,$(CONFIG_CMD_PING) 为空,该行不会把 ping.o 加入 obj-y。这才是“配置开关控制源码是否进入固件”的准确含义。

还要区分视频里出现的三个网络概念:

界面或规则对应配置实际含义
Networking supportCONFIG_NET整体网络支持及其子菜单的总开关
Control TFTP timeout and count through environmentCONFIG_NET_TFTP_VARS只控制能否用环境变量调整 TFTP 超时与重试次数
obj-$(CONFIG_CMD_NET) += tftp.oCONFIG_CMD_NET课程版本 net/Makefile 中把 tftp.o 纳入构建的规则

所以,视频按空格切换的是 Networking support 总开关,不能简单等同为“单独打开或关闭 TFTP 命令”。CONFIG_CMD_PING 的示例则是另一个更直接的命令级开关。

2.2.3 make menuconfig命令

正确命令是:

make menuconfig

它启动的是基于终端的字符界面,不是桌面图形窗口。课程环境中的旧版 U-Boot 使用 ncurses 构建并显示该界面,因此需要安装与系统匹配的 ncurses 开发包;若界面无法生成,还应同时检查构建工具、终端尺寸和报错信息,不能断言所有失败都只由 ncurses 缺失引起。

建议先执行正确的板级 defconfig,再进入菜单:

make mx6ul_topeet_emmc_defconfig
make menuconfig

视频界面中的基本操作如下:

操作含义
上、下方向键移动当前选项
Enter进入带 ---> 的子菜单,或执行底部按钮
Space / Y / N对可修改的布尔选项启用或禁用
左、右方向键在底部的 Select、Exit、Help、Save、Load 等操作间移动
/搜索配置符号或提示文字
[*]当前布尔选项启用,通常生成 CONFIG_xxx=y
[ ]当前布尔选项关闭

视频进入 Networking support,先用空格切换状态,再恢复原状,最后退出且不保存;因此演示的目的只是说明界面操作,没有证据表明它永久改变了课程编译配置。

保存会更新当前构建目录中的 .config。但再次运行 create.sh 时,开头的 make distclean 会删除它,随后又按厂商 defconfig 重建。若要保留定制配置,应先明确是只为本次构建保留 .config,还是要将经过审核的最小差异写回并版本管理到板级 defconfig,不能把临时生成文件当作唯一配置备份。

2.2.4 Kconfig文件

文件名应写作 Kconfig,大小写不能随意。顶层 Kconfig 只是一棵配置树的入口,它还通过 source 读取 arch/Kconfig、common/Kconfig、cmd/Kconfig、net/Kconfig、drivers/Kconfig 等子文件。因此,menuconfig 中的全部选项并非都写在根目录那一个文件里。

常见 Kconfig 语法作用如下:

关键字作用
config NAME定义普通配置符号,生成时通常对应 CONFIG_NAME
menuconfig NAME定义一个配置符号,并提示前端把相关子项组织成独立菜单
bool、string、hex、int定义值的类型;引号内文字可成为界面提示
default指定默认值,但仍受依赖和其他规则约束
depends on限制选项何时可见、可选
select当前选项启用时选择另一个符号,应谨慎使用
help为界面中的 Help 提供说明
menu / endmenu显式组织菜单层次
source继续读取另一个 Kconfig 文件

原笔记中“在 Kconfig 加一个选项,界面就一定多一个选项”过于绝对。只有带可见提示、依赖条件成立且被配置树读取到的条目,才会以可操作项出现在菜单中;无提示符号可能只由默认值或其他选项间接赋值。具体语法和依赖规则可查阅 Linux 官方 Kconfig Language,U-Boot 正是在此语言基础上构建自己的配置系统。

可以把四者的职责记成一句话:

defconfig 给出板级初始选择,Kconfig 定义“有哪些选择以及约束”,.config 保存“本次实际选择”,Makefile 决定“这些选择如何变成目标文件和镜像”。

三、课程总结

本节从厂商 create.sh 串起了 U-Boot 的配置与构建链:

  • ARCH=arm 选择目标架构,CROSS_COMPILE 选择交叉工具链前缀;
  • make distclean 会清除旧产物和配置,不只是删除几个配置文件;
  • ./create.sh nand 选择 NAND 模板,不传 nand 则默认选择 eMMC 模板;
  • defconfig 经 Kconfig 解析后生成完整 .config,不是简单逐行复制;
  • .config 中的 CONFIG_CMD_PING=y 使 net/Makefile 的规则展开为 obj-y += ping.o;
  • make menuconfig 是 ncurses 终端配置界面,保存结果写入 .config;
  • Kconfig 负责配置项、菜单和依赖,Makefile/Kbuild 负责把最终配置转换成实际构建目标。

真正需要掌握的不是背下顶层 Makefile,而是能从一个配置符号出发,依次找到它的 Kconfig 定义、defconfig 初值、.config 结果、Makefile 规则和最终源文件。以后新增驱动或命令时,这条反查路径仍然适用。

校核依据:课程视频与配套 U-Boot 2016.03 源码;U-Boot Building with GCC、U-Boot Kconfig、Linux Kbuild Makefiles及 Linux Kconfig Language。