U-Boot分析【学习笔记】(2)
3. U-Boot 编译在上篇文章U-Boot分析 【学习笔记】(2)中给出了基于imx6ull裁剪后的U-Boot源码结构图但这并不意味着把其他文件进行删除而是通过配置.config的方式使编译时只选取所需要的文件进行操作。保持源码完整不要手动删除任何文件夹。生成默认配置执行 make mx6ull_14x14_evk_defconfig这是 i.MX6ULL 的默认“菜单”。微调裁剪执行 make menuconfig。在菜单里尝试关掉一些你用不到的功能比如 Networking support 下的各种不认识的网卡驱动。观察编译产物你会发现虽然 drivers/net 文件夹里有几十个驱动但编译完成后只有你选中的那几个驱动生成了 .o 文件。3.1 makefile 介绍既然我们已经知道 U-Boot 的裁剪是靠 逻辑开关.config实现的那么问题来了是谁在真正执行“看开关、选文件、调用编译器”这一系列动作的答案就是Makefile在 U-Boot 中Makefile 并不是一个孤立的文件而是一套高度自动化的工程构建系统。为了理解它的运作我们可以从以下三个维度进行拆解3.1.1 核心职责把控编译全流程Makefile 就像是一个严谨的指挥官它负责解决三个核心问题编译哪些文件它会读取 .config 中的变量如 CONFIG_SPIy决定将哪些 .c 文件编译成 .o 目标文件。以什么顺序编译它确保先编译架构相关的初始化代码如 start.S再编译通用代码最后才进行链接。怎么打包镜像对于 i.MX6ULL它会调用 tools/ 里的工具将编译出的二进制文件包装成板子能识别的 .imx 格式。3.1.2 结构分层从顶层到底层U-Boot 的 Makefile 采用了嵌套式结构这与你之前看到的目录结构是完美对应的顶层 Makefile位于根目录负责全局性的环境配置如交叉编译器 CROSS_COMPILE 的设定和子目录递归。子目录 Makefile几乎每个子文件夹如 drivers/spi/下都有一个自己的 Makefile。它不关心全局只负责管理自己地盘里的文件。3.1.3 核心变量obj-$(CONFIG_…) 模式这是 U-Boot Makefile 最精妙的地方。你会频繁在各个子目录的 Makefile 中看到类似的写法举例drivers/spi/Makefile 中的片段 obj-$(CONFIG_MXC_SPI)mxc_spi.o obj-$(CONFIG_ROCKCHIP_SPI)rk_spi.o背后的逻辑如果你在 menuconfig 中勾选了 i.MX 的 SPI 驱动.config 里就会出现 CONFIG_MXC_SPIy。Makefile 执行时变量会被替换为 obj-y mxc_spi.o意味着这个驱动会被编译进内核。如果你没选变量就是 obj-n 或为空编译器就会直接跳过它。3.2 makefile 使用创建两个文件Makefile与b_makefile以下是Makefile代码A1include b_makefile-include c_makefile all:echo A${A}echo B${B}echo C${C}在 Makefile 的语法中目标Target行如 all:必须顶格写。命令Recipe行如 echo…的前面必须由一个制表符(tab)开始而不能是空格。即使你的编辑器里看起来缩进距离一样但在 Makefile 的解析器眼里空格和 Tab 是完全不同的。A1解释定义一个名为 A 的变量并赋值为 1。注意在 Makefile 中等号两边通常不留空格虽然留了也能解析但规范写法建议紧凑。这个变量在当前 Makefile 及后续包含的文件中都有效。include b_makefile解释强制包含引用名为 b_makefile 的文件。行为Makefile 会暂停处理当前的脚本转去读取并执行 b_makefile 里的内容例如在里面定义了 B2。关键点如果磁盘上找不到 b_makefile 这个文件make 命令会直接报错并停止编译。这通常用于包含那些必须存在的底层架构配置或核心规则文件。-include c_makefile解释尝试包含 c_makefile 文件前面的横杠 - 表示“忽略错误”。行为如果 c_makefile 存在则读取它例如在里面定义了 C3。关键点如果该文件不存在make 会静默跳过不会报错也不会停止编译。应用场景这在 U-Boot 中极其常见通常用于包含可选的本地配置、用户自定义变量或者自动生成的依赖文件.d 文件。all:解释定义一个名为 all 的目标Target。行为这是默认目标。当你只输入 make 而不带参数时它会自动执行这个 all 下面的命令。echo A${A}解释在终端打印变量 A 的值。符号含义命令修饰符表示“静默执行”即在终端只显示命令的运行结果A1而不显示命令本身echo A1。${A}引用前面定义的变量 A。echo B${B}解释打印变量 B 的值。逻辑说明如果 b_makefile 存在且内部写了 B2这里将输出 B2。如果 b_makefile 里没写则输出 B空值。echo C${C}解释打印变量 C 的值。逻辑说明如果 -include 的 c_makefile 不存在变量 C 将为空且由于使用了 - 符号整个程序依然能运行到这一步并打印出空结果。以下是b_makefile代码B1放在同一个文件夹下并执行make执行make -p打印出 Make 在执行过程中所掌握的“全部信息”。在输出中最关键的部分是它标出了每个变量的来源makefile (from Makefile, line 1): A 1确认了变量 A 是在当前 Makefile 的第 1 行定义的。makefile (from b_makefile, line 1): B 1证实了 include b_makefile 执行成功了并且 B 的值是从那个文件里读出来的。执行make -p 1.txt将输出的内容保存下来执行vi 1.txt修改文件内容执行:g/^#/d这个命令通常在 vim 的末行模式下输入或者作为 sed 命令的核心逻辑g (Global)全局搜索。表示对整个文件中的每一行进行检查。/^#/ (Pattern)正则表达式匹配。^代表行首。#代表井号字符。合起来的意思是匹配所有以 # 开头的行即 Makefile 或 Shell 脚本中的典型注释行。d (Delete)执行动作。代表删除匹配到的行。结果 删除或隐藏所有注释行只留下有效的代码和变量定义。执行:wq保存修改的内容3.3 U-Boot 的默认配置每个板子都会对应一个默认的配置命令如imx6ull对应的配置命令为make mx6ull_14x14_evk_defconfig在U-Boot根目录下执行该命令mx6ull_14x14_evk_defconfig 实际上触发了一系列底层的自动化准备工作。准备阶段编译“工具的工具”在处理你为 i.MX6ULL 准备的配置单之前Makefile 需要先准备好一些专门负责解析配置的辅助工具。HOSTCC scripts/basic/fixdepHOSTCC 代表使用你电脑Host上的编译器通常是 gcc来编译代码。fixdep 是一个基础工具用来处理源代码之间的文件依赖关系比如哪个 .c 文件引用了哪个 .h 文件。HOSTCC scripts/kconfig/conf.o编译 Kconfig 系统的核心组件 conf 的目标文件。这个程序是 U-Boot 配置系统的“大脑”负责读取配置定义并生成最终结果。解析阶段处理配置语言U-Boot 的配置选项非常多它是用一种类似文本描述的语言Kconfig编写的。为了让 conf 程序能看懂需要进行格式转换。SHIPPED scripts/kconfig/zconf.tab.c等SHIPPED 表示这些文件是预先生成的“快照”文件由 Flex 和 Bison 这种词法/语法分析器生成。这几行代表系统正在准备用于解析 defconfig 文件的语法解析器代码。HOSTCC scripts/kconfig/zconf.tab.o和HOSTLD scripts/kconfig/confHOSTLD 代表链接Link动作。系统将刚才编译的所有 .o 文件链接在一起正式生成了 scripts/kconfig/conf 这个可执行程序。结果阶段写入配置文件configuration written to .config这是最关键的一步刚生成的 conf 工具读取了 configs/mx6ull_14x14_evk_defconfig你指定的“预设菜单”和源码中各处的 Kconfig 文件。它将所有的默认值、依赖关系和你的选择整合在一起并在根目录下生成了一个隐藏文件 .config。对conf进行分析“ * ”代表通配符当输入 ls scripts/kconfig/conf* 时Shell 会在 scripts/kconfig/ 目录下寻找所有以 conf 开头的文件。文件类型含义与作用conf.cC 源文件配置工具的源码。它定义了如何读取 Kconfig 语法如何检查选项之间的依赖关系。confdata.cC 源文件数据处理逻辑。专门负责如何将配置结果读入内存以及如何将其格式化输出到 .config 文件中。conf.o目标文件编译的中间产物。是由 conf.c 经过主机编译器gcc编译后生成的二进制目标文件。conf可执行程序最终的“指挥官”。由 conf.o 和其他相关目标文件如 zconf.tab.o链接而成。正是它刚才读取了你的 defconfig 并吐出了 .config。执行vi .config可以看到配置好的文件信息接下来利用3.2中的make -p来分析.config文件的配置信息是从哪里获取到的执行make mx6ull_14x14_evk_defconfig -p 1.txt vi 1.txt执行:g/^#/d对精简后的1.txt文件进行分析mx6ull_14x14_evk_defconfig:scripts/kconfig/conf $(Q)$$(silent)--defconfigarch/$(SRCARCH)/configs/$ $(Kconfig)mx6ull_14x14_evk_defconfig: scripts/kconfig/conf这条规则遵循 Makefile 的经典语法目标: 依赖 \n [Tab] 命令。目标 (mx6ull_14x14_evk_defconfig)这是你执行 make 时输入的参数。由于 Makefile 使用了模式匹配或显式定义当你输入这个名字时它就匹配到了这条规则。依赖 (scripts/kconfig/conf)这解释了为什么刚才看到系统先编译出了 conf 工具。这条规则告诉 Makefile“想要处理配置必须先确保 conf 这个可执行程序已经存在”。$(Q)$$(silent)--defconfigarch/$(SRCARCH)/configs/$$(Kconfig)$ (Q)这是一个控制变量。如果在执行 make 时没有开启 V1详细模式$(Q) 的值就是 作用是隐藏这条命令本身的打印只显示简洁的输出如 HOSTCC…。$自动化变量代表第一个依赖文件。在这里它指代的就是scripts/kconfig/conf。所以这条命令实际上是在运行那个配置工具。$(silent)静默模式开关。如果开启了静默编译它会向 conf 工具传递参数减少输出信息。–defconfigarch/( S R C A R C H ) / c o n f i g s / (SRCARCH)/configs/(SRCARCH)/configs/这是传递给 conf 工具的核心参数$(SRCARCH)源码架构。$自动化变量代表目标文件名。在这里就是 mx6ull_14x14_evk_defconfig。合起来的意思告诉 conf 程序去 arch/arm/configs/ 目录下寻找对应的预设文件。$(Kconfig)这是根目录下的 Kconfig 文件。它包含了整个 U-Boot 的配置框架菜单结构和依赖关系。conf 工具会结合这个框架和上面的 defconfig 文件来生成最终结果。变量/符号展开值含义说明$(Q)静默符。它会让命令执行时不显示命令本身只显示结果。$scripts/kconfig/conf自动化变量。指代规则中的第一个依赖文件。$(silent)(空)静默参数。除非开启了特殊静默模式否则一般为空。$(SRCARCH)…架构变量。$mx6ull_14x14_evk_defconfig自动化变量。指代当前规则的目标文件名。$(Kconfig)Kconfig根配置文件。通常指代 U-Boot 源码根目录下的 Kconfig 文件。$ (Q) $ $(silent) --defconfigarch/ $(SRCARCH)/configs/ $ $(Kconfig)根据表格可展开为下列代码scripts/kconfig/conf --defconfigarch/../configs/mx6ull_14x14_evk_defconfig Kconfig即当执行make mx6ull_14x14_evk_defconfig时会生成conf工具并使用该工具执行上述展开后的命令这里我们手动执行弹出警告U-Boot 版本号未定义所以需要手动设置坑设置U-Boot 版本号和执行命令不能分开操作虽然定义了变量但它还没有被“推”到子进程也就是 conf 程序中。方法一使用 export 导出先将变量提升为环境变量再运行命令export UBOOTVERSION2017.03 scripts/kconfig/conf --defconfigarch/../configs/mx6ull_14x14_evk_defconfig Kconfig方法 2命令行前置定义最快捷直接在运行命令的同一行定义变量这样变量会直接进入该命令的运行环境且不影响当前终端的其他操作UBOOTVERSION2017.03 scripts/kconfig/conf --defconfigarch/../configs/mx6ull_14x14_evk_defconfig Kconfig当你按下回车执行 make mx6ull_14x14_evk_defconfig 时内部发生了以下链式反应检查依赖Makefile 发现需要 scripts/kconfig/conf于是先去编译 conf.c 及其相关文件。执行命令调用生成的 conf 程序。读取输入conf 去找 arch/arm/configs/mx6ull_14x14_evk_defconfig 这个“原始菜单”。逻辑处理conf 扫描全局 Kconfig 树补全 defconfig 中没写的默认选项并检查你开启的功能是否有冲突。产出结果最后在根目录下吐出一个隐藏文件 .config这才是后续编译 drivers/、common/ 等目录时真正的“指挥手册”。同样的既然 mx6ull_14x14_evk_defconfig 依赖于 scripts/kconfig/conf那就继续看 scripts/kconfig/conf 依赖于谁搜索“ scripts/kconfig/conf: ”scripts/kconfig/conf:FORCE scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o $(call if_changed,host-cmulti)FORCE这是一个伪目标。在 Kbuild 系统中添加 FORCE 依赖是为了强制执行后续的命令检查。它并不意味着每次都重新编译而是让 if_changed 去判断文件或编译命令是否有变动。即scripts/kconfig/conf依赖于scripts/kconfig/conf.o和scripts/kconfig/zconf.tab.o按照这个思路即可分析完文件的依赖关系3.5 conf 介绍我们知道执行make mx6ull_14x14_evk_defconfig最终是生成一个conf工具来执行其他命令接下来就介绍这个 conf 工具conf 是 Linux 内核 Kconfig 系统的核心执行器。在 U-Boot 或 Linux 内核中所有的 make *config 命令如 menuconfig, oldconfig, defconfig最终几乎都是在调用这个工具只是传递的参数Action不同。3.6 conf 工具的“制造过程”执行make mx6ull_14x14_evk_defconfig V1V1是指输出更详细的内容输出make-f./scripts/Makefile.build objscripts/basic cc-Wp,-MD,scripts/basic/.fixdep.d-Wall-Wstrict-prototypes-O2-fomit-frame-pointer-o scripts/basic/fixdep scripts/basic/fixdep.c rm-f.tmp_quiet_recordmcount make-f./scripts/Makefile.build objscripts/kconfig mx6ull_14x14_evk_defconfig cc-Wp,-MD,scripts/kconfig/.conf.o.d-Wall-Wstrict-prototypes-O2-fomit-frame-pointer-D_GNU_SOURCE-D_DEFAULT_SOURCE-I/usr/include/ncursesw-DCURSES_LOCncurses.h-DNCURSES_WIDECHAR1-DLOCALE-c-o scripts/kconfig/conf.o scripts/kconfig/conf.c cat scripts/kconfig/zconf.tab.c_shippedscripts/kconfig/zconf.tab.c cat scripts/kconfig/zconf.lex.c_shippedscripts/kconfig/zconf.lex.c cat scripts/kconfig/zconf.hash.c_shippedscripts/kconfig/zconf.hash.c cc-Wp,-MD,scripts/kconfig/.zconf.tab.o.d-Wall-Wstrict-prototypes-O2-fomit-frame-pointer-D_GNU_SOURCE-D_DEFAULT_SOURCE-I/usr/include/ncursesw-DCURSES_LOCncurses.h-DNCURSES_WIDECHAR1-DLOCALE-Iscripts/kconfig-c-o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c cc-o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o scripts/kconfig/conf--defconfigarch/../configs/mx6ull_14x14_evk_defconfig Kconfig ##configurationwritten to.config#深入分析输出内容第一行make -f ./scripts/Makefile.build-f 参数表示“强制使用指定文件作为 Makefile”。这里不再使用顶层的 Makefile而是使用一个专门负责“通用编译逻辑”的脚本 Makefile.build。它就像是一个通用的加工模版。objscripts/basic这是一个变量赋值告诉 Makefile.build 脚本“请进入 scripts/basic 这个目录去干活”。合起来的含义“启动 make 程序加载通用的编译规则文件 Makefile.build并指定当前的任务目标是在 scripts/basic 目录下。”第二行把 .c 编译为“工具”cc … -oscripts/basic/fixdepscripts/basic/fixdep.c含义编译过程。它使用主机编译器cc通常就是 gcc将 fixdep.c 这个源代码编译并链接成一个名为 fixdep 的可执行二进制文件。作用fixdep 的全称是 Fix Dependency。在后续编译成千上万个内核文件时它负责“盯着”所有的头文件。如果你改动了一个 .h 文件它会精准地告诉 Makefile 哪些 .c 受影响了需要重新编译。它是保证编译“又快又准”的核心小工具。第三行rm -f .tmp_quiet_recordmcount含义执行删除操作。rm 是删除命令-f 表示强制删除且不报错误信息。作用清理临时文件。recordmcount 是内核用来记录函数调用图的工具这里在进入下一阶段配置前先清理掉之前可能残留在根目录下的临时记录文件确保编译环境的“无污染”。第四行make -f ./scripts/Makefile.build objscripts/kconfig mx6ull_14x14_evk_defconfig含义再次调用 Makefile.build 模板但这次将变量 obj 指向了 scripts/kconfig 目录并明确任务目标是执行 mx6ull_14x14_evk_defconfig 这个配置动作。作用指挥中心转移。这行代码告诉构建系统“现在基础准备做完了请进入配置工具所在的文件夹准备开始处理 i.MX6ULL 的板级预设文件”。第五行cc … -c -oscripts/kconfig/conf.oscripts/kconfig/conf.c含义编译配置工具的目标文件。使用主机编译器将 conf.c 编译-c成目标文件 conf.o。参数亮点你会看到 -I/usr/include/ncursesw。这表明配置工具链接了主机的终端图形库这就是为什么你以后运行 make menuconfig 时能看到蓝色菜单界面的原因。第六、七、八行cat scripts/kconfig/zconf.tab.c_shipped scripts/kconfig/zconf.tab.ccat scripts/kconfig/zconf.lex.c_shipped scripts/kconfig/zconf.lex.ccat scripts/kconfig/zconf.hash.c_shipped scripts/kconfig/zconf.hash.c含义使用 cat 命令进行重定向。将以 _shipped 结尾的预存文件内容复制到对应的 .c 文件中。作用源码还原。zconf 系列文件是复杂的语法解析器源码。为了防止用户电脑上没装 Bison 或 Flex 工具U-Boot 预先准备好了这些源码的“快照shipped”。这几行确保了即使在最简陋的环境下也能拥有解析 Kconfig 文件的源代码。第九行cc … -c -oscripts/kconfig/zconf.tab.oscripts/kconfig/zconf.tab.c含义编译语法解析器的核心。将刚才还原出来的 zconf.tab.c 编译成目标文件 zconf.tab.o。作用负责读懂各个目录下 Kconfig 文件里那些 depends on 和 select 的逻辑语法。第十行cc -oscripts/kconfig/confscripts/kconfig/conf.oscripts/kconfig/zconf.tab.o含义最终链接。将之前编译好的两个零件conf.o 和 zconf.tab.o链接在一起生成最终的可执行程序 conf。第十一行scripts/kconfig/conf--defconfigarch/…/configs/mx6ull_14x14_evk_defconfig Kconfig含义正式作业。运行刚才造好的 conf 工具。–defconfig指定参考的厂家“设计图”。Kconfig指定全局的“工艺准则”。作用逻辑校验与合并。工具会检查你的 defconfig 有没有写错并根据 Kconfig 里的依赖关系计算出成千上万个配置项的最终状态。