uboot编译脚本分析
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 的输入 | 板级默认需求变化时维护 |
.config | Kconfig 工具生成 | 保存本次构建解析后的完整配置 | 通常通过 menuconfig 修改,不建议随意手改 |
Kconfig | 源码树中的配置描述文件 | 定义选项类型、提示、依赖、默认值和菜单结构 | 新增或重构功能配置时维护 |
Makefile | 源码树中的构建规则 | 根据配置决定目录、源码、目标和链接方式 | 新增源文件或改变构建关系时维护 |
一、任务资源导图
上一节沿着函数调用追踪 U-Boot 的运行流程;本节换到构建视角,回答“这么多源文件为什么只编译其中一部分”。学习时不要试图一次读完整个顶层 Makefile,而应抓住一条可验证的小链路:
CONFIG_CMD_PING=y → obj-$(CONFIG_CMD_PING) += ping.o → obj-y += ping.o → ping.c 被编译并纳入 U-Boot
围绕这条链路,再分别理解四个问题:
create.sh如何选择开发板配置?defconfig如何生成隐藏文件.config?.config如何影响子目录 Makefile?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 defconfig | Kconfig 解析模板、默认值和依赖,生成新的 .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 源码都必须具有的上游标准脚本。
-
ARCH不是编译器ARCH=arm告诉构建系统目标体系结构是 ARM。它与运行构建命令的 Ubuntu 主机体系结构不是一回事。 -
CROSS_COMPILE是工具名前缀arm-linux-gnueabihf-末尾的-不能漏掉。构建规则会在此前缀后拼接gcc、ld、ar、objcopy等名称。视频终端中输入该前缀后按 Tab,列出的正是这组交叉工具。那条以
#export CROSS_COMPILE=/usr/local/...开头的长路径已被 Shell 注释,不会与当前设置同时生效。 -
make distclean不只删除配置文件课程为便于理解只强调了“清除旧配置”。在本版本顶层
Makefile中,distclean先依赖mrproper,而mrproper又依赖clean;它会删除大部分编译产物、所有生成配置以及编辑器备份、补丁残留等文件。因此,未保存到defconfig的.config定制会被下一次create.sh清掉。 -
第一个参数决定启动介质配置
./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,不能凭开发板芯片名称擅自改成其他目标名。 -
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 展示从配置到目标文件的完整对应关系:
- eMMC 与 NAND 两份板级
defconfig都包含CONFIG_CMD_PING=y。 - Kconfig 生成的
.config中也出现CONFIG_CMD_PING=y。 net/Makefile包含obj-$(CONFIG_CMD_PING) += ping.o。- 变量展开后成为
obj-y += ping.o,于是net/ping.c被编译为net/ping.o,再纳入目录及最终链接目标。
如果该选项未启用,$(CONFIG_CMD_PING) 为空,该行不会把 ping.o 加入 obj-y。这才是“配置开关控制源码是否进入固件”的准确含义。
还要区分视频里出现的三个网络概念:
| 界面或规则 | 对应配置 | 实际含义 |
|---|---|---|
Networking support | CONFIG_NET | 整体网络支持及其子菜单的总开关 |
Control TFTP timeout and count through environment | CONFIG_NET_TFTP_VARS | 只控制能否用环境变量调整 TFTP 超时与重试次数 |
obj-$(CONFIG_CMD_NET) += tftp.o | CONFIG_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。