1 版本号#

Top Makefile的开头会有版本描述,VERSION 是主版本号,PATCHLEVEL 是补丁版本号,SUBLEVEL 是次版本号,这三个一 起构成了 uboot 的版本号,比如当前的 uboot 版本号就是2016.03。EXTRAVERSION 是附加 版本信息,NAME 是和名字有关的,一般不使用这两个。
2 MAKEFLAGS#

有两个特殊的变量:SHELL和MAKEFLAGS,这两个变量除非使用unexport声明, 否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。MAKEFLAGS 追加了一些值,-rR表示禁止使用内置的隐 含规则和变量定义,--include-dir指明搜索路径,$(CURDIR)表示当前目录。
3 设置命令输出详细程度#
①uboot 默认编译使用短命令。
②设置变量V=1来实现完整的命令输出。

③make -s设置成静默输出,将会silent_输出,不打印任何提示信息。如下:
MAKE_VERSION就是make版本号,我这里是4.2.1。因此filter 4.%,$(MAKE_VERSION)得到的过滤结果不为空。
当使用make -s编译的时候,-s会作为 MAKEFLAGS 变量的一部分传递给 Makefile。因此$(firstword x$(MAKEFLAGS))得到xrRs,最后quiet=silent_,否则不使用silent_输出。最后使用 export 导出变量 quiet、Q 和 KBUILD_VERBOSE。

4 设置编译结果输出位置#
在 make 的时候使用O来指定 输出目录,这么做是为了将源文件 和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在 同一个目录内。
判断O是否来自于命令行,如果来自命令行的话,KBUILD_OUTPUT 就为$(O),也就是输出目录。
一开始判断KBUILD_OUTPUT 是否为空。 如果指定了输出目录就调用 mkdir 命令创建目录。
5 代码检查#
命令make C=1使能代码检查,检查那些需要重新编译的文 件。make C=2用于检查所有的源码文件。
6 子模块编译#
使用命令make M=dir即可,旧语法make SUBDIRS=dir也是支持的。
如果定 义 了 SUBDIRS或者M,那么KBUILD_EXTMOD就会被赋值。然后进入目标_all 依赖 modules,要先编译出 modules,也就是编译模块。
否则KBUILD_EXTMOD为空,进入all编译。
判断KBUILD_SRC是否为空,如果为空的话就设置变量srctree为当前目录,否则srctree就是KBUILD_SRC。一般不设置 KBUILD_SRC。
设置变量src和 obj,都为当前目录,设置VPATH,导出量 scrtree、objtree 和 VPATH。
7 获取主机架构系统信息#

最终开发服务器主机架构和操作系统信息如下:
8 设置目标架构、工具链和配置文件#
HOSTARCH是x86_64,我们编译make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-就是用于设置目标 ARCH 和 CROSS_COMPILE。
KCONFIG_CONFIG,这里设置配置文件为.config,.config 默认是没有的,需要使用命令make xxx_defconfig 对 uboot 进行配置,配置完成以后就会在 uboot 根目录下生成.config。
设置主机编译器HOSTCC,HOSTCXX等。
9 调用 scripts/Kbuild.include#
在整个Kbuild系统中,scripts/Kbuild.include 提供了大量通用函数以及变量的定义,这些定义将被 Makefile.build 、Makefile.lib 和 top Makefile频繁调用,以实现相应的功能,scripts/Kbuild.include 参与整个内核编译的过程,是编译的核心脚本之一。

Kbuild.include定义了很多变量:
里面定了build变量,后面产生配置文件时会用到:
9.1 build变量#
build := -f $(srctree)/scripts/Makefile.build obj
例如:
1 | %config: scripts_basic outputmakefile FORCE |
当我们执行make menuconfig时:
1 | menuconfig: scripts_basic outputmakefile FORCE |
9.1.1 调用scripts/Makefile.build#
参见后面12.3.1.1节再详细介绍。
与build变量相类似的,还有以下的编译指令:
$(modbuiltin):
1 | modbuiltin := -f $(srctree)/scripts/Makefile.modbuiltin obj |
$(dtbinst):
1 | dtbinst := -f $(srctree)/scripts/Makefile.dtbinst obj |
$(clean):
1 | clean := -f $(srctree)/scripts/Makefile.clean obj |
$(hdr-inst):
1 | hdr-inst := -f $(srctree)/scripts/Makefile.headersinst obj |
这四条指令,分别对应 內建模块编译、dtb文件安装、目标清除和头文件安装.
9.2
9.2 filechk变量#
1 | define filechk |
mkdir -p $(dir $@):如果$@目录不存在,就创建目录,$@是编译规则中的目标部分。($@ 在 Makefile 表目标文件)- 执行
filechk_$(1),然后将执行结果保存到$@.tmp中 - 对比
$@.tmp和$@是否有更新,有更新就使用$@.tmp,否则删除$@.tmp。
9.3 if_changed变量#
下一节有详细解析介绍:
uboot顶层makefile-2编译过程 - fuzidage - 博客园 (cnblogs.com)
uboot-编译过程Make分析 | Hexo (fuzidage.github.io)
9.4 scripts/Makefile.lib文件作用#
9.4.1 定义编译选项#
1 | asflags-y += $(EXTRA_AFLAGS) |
9.4.2 去重#
如果某个模块已经被定义在obj-y中,就没必要再编译了。
1 | # 去除obj-m中已经定义在obj-y中的部分 |
9.4.3 modules.order#
1 | #将obj-y中的目录 dir 修改为 dir/modules.order赋值给modorder, |
内核将编译的外部模块全部记录在 modules.order 文件中,以便 modprobe 命令在加载卸载时查询使用。
1 | kernel/drivers/input/mouse/psmouse.ko |
9.4.4 目录的处理#
1 | #挑选出obj-y 和 obj-m 中的纯目录部分,然后添加到subdir-y和subdir-m中。 |
obj-y 和 obj-m 的定义中同时夹杂着目标文件和目标文件夹,文件夹当然是不能直接参与编译的,所以需要将文件夹提取出来。
将 obj-y or obj-m中以"/"结尾的纯目录部分提取出来,并赋值给 subdir-ym.
9.4.5 设备树相关#
1 | extra-y += $(dtb-y) |
将所有的dtb-y赋值给extra-y。
9.4.6 添加路径#
1 | extra-y := $(addprefix $(obj)/,$(extra-y)) |
文件的处理最后,给所有的变量加上相应的路径,以便编译的时候进行索引。
Makefile.lib 通常都被包含在于 Makefile.build中,这个变量继承了Makefile.build的 obj 变量。而 Makefile.build 的obj变量则是通过调用 $(build) 时进行赋值的。
9.5 scripts/Makefile.build文件作用#
9.5.1 包含 include/config/auto.conf#
包含include/config/auto.conf文件,这个文件的内容是这样的:
1 | CONFIG_RING_BUFFER=y |
9.5.2 包含 scripts/Kbuild.include#
9.5.3 处理编译目标#
1 | #主机程序,在前期的准备过程中可能需要用到,比如make menuconfig时需要准备命令行的图形配置。 |
modorder-target将被赋值为$(obj)/modules.order, module.order这个文件记录了可加载模块在Makefile中出现的顺序,主要是提供给modprobe程序在匹配时使用。
9.5.4 默认编译目标_build#
例子见12.3.1.1.1。
9.5.4.1builtin-target#
1 | $(builtin-target): $(real-obj-y) FORCE |
1 | real-prereqs = $(filter-out $(PHONY), $^) |
ar_builtin就是执行cmd_ar_builtin, 就是使用ar指令打包成 $(builtin-target),也就是$(obj)/built-in.a。它的依赖文件被保存在 $(real-obj-y) 中。
$(real-obj-y) 在scritps/Makefile.lib被处理出来的变量, 对应目录下的所有目标文件,不包含文件夹。
9.5.4.2 lib-target#
1 | $(lib-target): $(lib-y) FORCE |
同理,最终将调用cmd_ar命令:
1 | real-prereqs = $(filter-out $(PHONY), $^) |
将本模块中的目标全部打包成$(lib-target),也就是 $(obj)/lib.a
9.5.4.3 extra-y#
$(extra-y) 在Makefile.lib中被确定,主要负责dtb相关的编译:
1 | extra-y += $(dtb-y) |
Makefile.lib 中可以找到对应的实现:
1 | $(obj)/%.dtb: $(src)/%.dts $(DTC) FORCE |
调用了 cmd_dtc:
1 | cmd_dtc = mkdir -p $(dir ${dtc-tmp}) ; \ |
$(2) 为 dtb,-O dtb 表示输出文件格式为 dtb 。 -o $@, $@ 为目标文件,表示输出目标文件,输入文件则是对应的 $<。
9.5.4.4 obj-m#
由于它是一系列的 .o 文件,所以它的编译是通过模式规则的匹配完成的。
1 | $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE |
执行rule_cc_o_c:
1 | define rule_cc_o_c |
cmd 函数其实就是执行 cmd_$1,那么也就是上述命令中分别执行 cmd_checksrc,cmd_gen_ksymdeps等等。
其中最重要的指令就是 : $(call cmd_and_fixdep,cc_o_c),它对目标文件执行了 fixdep,生成依赖文件,然后执行了 cmd_cc_o_c, 这个命令就是真正的编译指令:
1 | cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< |
9.5.4.5 modorder-target#
modorder-target值为 $(obj)/modules.order,也就是大多数目录下都存在这么一个 modules.orders `文件,来提供一个该目录下编译模块的列表。
1 | $(modorder-target): $(subdir-ym) FORCE |
1 | modorder-cmds = \ |
该操作的目的就是将需要编译的.ko的模块以kernel/$(dir)/*.ko为名记录到 obj-m 指定的目录下。
9.5.4.6 subdir-ym#
1 | $(subdir-ym): |
这就是Kbuild递归遍历子目录编译的策略,对于每个需要递归进入编译的目录,都调用:
1 | $(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1) |
10 设置交叉编译器#

11 核心变量导出#
在顶层 Makefile 会导出很多变量:
1 | export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION |

打印出export的这些变量:
①这些变量来自config.mk,里面定义了ARCH,CPU,BOARD,VENDOR,SOC,BOARDDIR等变量。变 量 ARCH , 值 为 $(CONFIG_SYS_ARCH:"%"=%) , 也 就 是 提 取 CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“arm”的话, ARCH=arm。经过展开确定了CPUDIR=arch/arm/cpu/armv7。
②这里有一个sinclude指令,sinclude 和 include 的功能类似,在 Makefile 中都是读取指定文件内容,这里读取 文件$(srctree)/arch/$(ARCH)/config.mk 的内容。sinclude 读取的文件如果不存在的话不会报错。
③依次包含arch,cpu,soc,vendor,board相关的config.mk
.config如下:
12 make xxx_defconfig 配置过程#
输入命令进行配置:
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig |

12.1 产生版本日期信息#
先产生version_h和timestamp_h。version_h记录uboot版本和编译器版本。timestamp_h记录uboot的日期时间戳信息。打开这两个头文件:

12.2 配置变量#
MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指 定的终极目标列表,比如执行make mx6ull_alientek_emmc_defconfig,那么 MAKECMDGOALS 就为 mx6ull_alientek_emmc_defconfig。filter函数将 MAKECMDGOALS 中符合no-dot-config-targets的部分过滤出来,可以看到为空,所以dot-config还是等于1.
KBUILD_EXTMOD前面提到没有编译子模块,KBUILD_EXTMOD为空,进入,在判断
1 | ifneq ($(filter config %config,$(MAKECMDGOALS),) |
当make mx6ull_alientek_emmc_defconfig, 明显过滤出来不为空,因此config-targets=1.
words $(MAKECMDGOALS)等于1,最后得到:
1 | config-targets = 1 |
12.3 执行3个依赖#

KBUILD_DEFCONFIG等于mx6ull_14x14_ddr512_emmc_defconfig, KBUILD_KCONFIG等于空,导出。
匹配到%config, 执行scripts_basic、outputmakefile 和 FORCE 3个依赖.
12.3.1 scripts_basic#

scripts/Kbuild.include定义了build变量。$Q$(MAKE) $(build)=scripts/basic展开build变量后得:
1 | make -f ./scripts/Makefile.build obj=scripts/basic |
12.3.1.1 scripts/Makefile.build#
scripts_basic 会调用文件./scripts/Makefile.build,打开这个文件:
这里$(obj)=scripts/basic, patsubst 是替换函数在scripts/basic中查找符合tpl/%的部分,然后将tpl/取消掉,但是 scripts/basic没有tpl/,所以 src= scripts/basic。两个ifeq ($(obj),$(src))都满足条件,最终prefix等于.。
kbuild-dir 展开后为:
1 | $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic) |
因为没有以/为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuilddir=./scripts/basic。kbuild-file 展开后为:
1 | $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile) |
scrpts/basic 目录中没有Kbuild这个文件,所以 kbuild-file= ./scripts/basic/Makefile.scripts/Makefile.build文件包含/scripts/basic/Makefile
12.3.1.1.1 找到默认目标_build#
再继续分析scripts/Makefile.build:
__build 是默认目标,因为命令@make -f ./scripts/Makefile.build obj=scripts/basic没有指定目标,所以会使用到默认目标:__build。
顶层 Makefile 中,KBUILD_BUILTIN 为 1, KBUILD_MODULES 为 0,因此展开后目标__build 为:
1 | __build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always) |
直接打印出这5个目标:所以最终只有一个依赖scripts/basic/fixdep要执行,产生fixdep.
执行打印如下:
至此第一个依赖scripts_basic就结束了。
总结:scripts_basic就是利用scripts/Makefile.build去找到_build目标,然后去scripts/basic目录编译出fixdep.
12.3.2 outputmakefile#
由于KBUILD_SRC为空,不执行。否则会为源码路径创建source这个符号链接,执行mkmakefile。
12.3.3 FORCE#
Makefile最底下定了FORCE目标,可以看到什么都不执行。
FORCE的作用:
可以看到它也是一个目标,没有依赖文件且没有命令部分,由于它没有命令生成FORCE,所以每次都会被更新。
所以它的作用就是:FORCE作为依赖时,就导致依赖列表中每次都有FORCE依赖被更新,导致目标每次被重新编译生成。
12.4 产生.config#
回到%config 处:$(Q)$(MAKE) $(build)=scripts/kconfig $@展开后
1 | make -f ./scripts/Makefile.build obj=scripts/kconfig mx6ull_14x14_ddr512_emmc_defconfig |
再次进入scripts/Makefile.build, 用echo得到一些变量值:
1 | kbuild-dir = ./scripts/kconfig |
打开./scripts/kconfig/Makefile:
%_defconfig: $(obj)/conf匹配到我们的mx6ull_14x14_ddr512_emmc_defconfig,先编译依赖scripts/kconfig/conf,编译生成scripts/kconfig/conf如下:
最终利用conf工具从configs目录找到mx6ull_14x14_ddr512_emmc_defconfig,将配置写成.config文件。
总结defconfig配置过程:
12.5 制作defconfig#
如果没有对应的defconfig可以找一个与自己板级信息类似的defconfig生成一个.config,再通过menuconfig来完成自己board的配置,并最后通过savedefconfig保存为自己board的defconfig:
1 | make CROSS_COMPILE=aarch64-linux-gnu- evb-rk3399_defconfig |