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 |