尊天命,尽人事
分类: LINUX
2012-01-05 15:54:40
顶层Makefile的总体框架结构
既然前面我们说过顶层Makefile最为重要,那么我们就先来研究一下这个文件。在你用VI编辑器打开这个文件时,千万别被它的复杂吓倒。这个文件虽然行数颇多,但其实里面也是有道道可寻的,我们可以抽出其中最重要的框架结构出来,列出如下(稍做整理和缩进):
从上面的框架中可以看出,影响内核构建过程动作的有数个变量,分别是:KBUILD_SRC, KBUILD_OUTPUT, skip-makefile, mixed-targets, config-targets 和 dot-config。我们将它们分成两组,前三为一组,后三个为一组。显然,前一组影响着框架中最外面的两个ifeq-endif块,而后一组则决定了第二个ifeq-endif块内的逻辑。
对于第一组变量,其实是为了支持"make O=1 [Targets]", 也就是为了支持将输出文件放到另外目录(不同于内核源代码目录的其他目录)的功能而准备的。那到底是如何支持的呢?我们说其实这是两次调用顶层Makefile的过程。
首先,当我们输入"make O=dir [Targets]"命令的时候,会第一次调用顶层Makefile。而此时变量KBUILD_SRC没有定义,所以会进入到第一个ifeq-endif块。进去之后,条件判断ifeq ("$(origin O)", "command line")会发现命令行里有变量O的定义,并且其值为dir。所以接下来,make会把变量O的值,也就是dir赋给变量KBUILD_OUTPUT。
既然KBUILD_OUTPUT的值为dir,所以条件判断ifneq ($(KBUILD_OUTPUT),)就必定成立,所以make会去处理上面的A部分。处理完A部分后,会将变量skip-makefile设置为1。因此显然,在退出第一个ifeq-endif块后,进入第二个ifeq-endif块的条件判断就得不到满足。所以我们说,其实到这里,第一次调用Makefile的过程就提前结束了。
那第二次调用顶层Makefile的过程又是在哪里呢?答案是在对A部分的处理上。我们列出A部分中关键的代码:
PHONY += $(MAKECMDGOALS) sub-make $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make $(Q)@: sub-make: FORCE $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \ KBUILD_SRC=$(CURDIR) \ KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile \ $(filter-out _all sub-make,$(MAKECMDGOALS))从这里,我们可以看出,不管命令"make O=dir [Targets]"中的Targets个数有多少个,它们都是要依赖于 sub-make。所以,对A部分的处理,其实就是执行sub-make规则的命令。在这个命令中,KBUILD_OUTPUT值为dir,CURDIR值为当前的内核源码目录。因为make命令中没有设置M变量或者SUBDIRS变量,所以KBUILD_EXTMOD值为空。所以这个命令就简化为:
make -C dir KBUILD_SRC=`pwd` KBUILD_EXTMOD="" -f `pwd`/Makefile [Targets]
这样一个命令的执行就和执行没带O=dir的"make [Targets]"命令差不多了。其差别只在于将输出文件存到dir目录,而非内核源码树目录而已。
从上面的框架中看出来,如果只是简单的"make [Targets]",那么就不会进去到第一个ifeq-endif块,而直接进到第二个ifeq-endif块中去处理了。这就要涉及到上面说到的第二组变量了。我们说,设置第二组变量的目的,就是为了适应make命令后面所跟Targets数目和类型上的多样性而已。
在详细分析第二个ifeq-endif块之前,让我们看看第二组变量的含义以及它们的初始值设置情况。由于我们之前已经对几乎所有Targets的作用及分类做了说明,所以理解它们不应该很难。如上面框架图中所示的那样,让我们先抽出B2部分中和这个相关的代码如下:
no-dot-config-targets := clean mrproper distclean \ cscope TAGS tags help %docs check% \ include/linux/version.h headers_% \ kernelrelease kernelversion config-targets := 0 mixed-targets := 0 dot-config := 1 ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),) ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),) dot-config := 0 endif endif ifeq ($(KBUILD_EXTMOD),) ifneq ($(filter config %config,$(MAKECMDGOALS)),) config-targets := 1 ifneq ($(filter-out config %config,$(MAKECMDGOALS)),) mixed-targets := 1 endif endif endif上面的代码一开始就设置了变量no-dot-config-targets,它指代的是那些和.config没有关系的目标,这已经在前面对目标进行分类的时候说过了。接下来,它将三个变量分别赋初值0,0和1。再接下来,它会判断make命令中的[Targets]部分是否有且仅有变量no-dot-config-targets所指代的那些目标,如果是的话它就将变量dot-config的值设置为0。这表明本次make命令所make的目标都是和.config文件没有关联的,既不是那些会产生.config的配置目标,也不是那些需要使用.config文件内容的构建目标。
好,上面的条件判断ifeq ($(KBUILD_EXTMOD),)指示了如果我们不是编译外部模块的话就进去到这最后一个ifeq-endif块里面。进去后,构建系统如果发现make命令中[Targets]部分包含有config,或者有%config目标的话,它就将变量config-targets设置为1,这表明本次make需要处理配置目标。在此基础之上,如果它还发现有其他目标的话,它就将mixed-targets变量设置为1。注意mixed-targets变量的确切含义,其确切含义是指配置目标和其他目标相混合,而不是仅仅指配置目标和构建目标相混合。换句话讲,对于"make s3c2410_defconfig kernelversion"这样的命令来说,配置目标s3c2410_defconfig和与.config文件不相关的目标kernelversion相混合,此时变量mixed-targets也会被设置为1。
讨论完B2部分对三个变量的初始化,让我们再回到主框架结构上面来。框架结构中接下来就是针对三变量不同的取值搭配进行处理了。
首先,如果mixed-targets取值为1,则表明是混合目标的情况,构建系统要处理框架中的C部分。我们取出其中代码如下:
# =========================================================================== # We're called with mixed targets (*config and build targets). # Handle them one by one. %:: FORCE $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@从代码中可以看出,这里使用了一个双冒号的模式匹配规则。百分号代表任何目标都使用这个规则,其中$(srctree)为内核代码树所在目录,KBUILD_SRC定义为空。所以如果make命令为:make s3c2410_defconfig all,那么构建系统就会分别执行下面两条命令:
make -C $(srctree) KBUILD_SRC= s3c2410_defconfig make -C $(srctree) KBUILD_SRC= all这其实和简单的用手动的输入两条连续命令(make s3c2410_defconfig 和 make all)是一样效果的。
回到主框架,如果make命令的[Targets]部分不是混合目标,而是单个目标。那么构建系统会先用ifeq ($(config-targets),1)判断是否是配置目标,如果是,则处理框架中的D部分,我们也抽出D部分的代码如下:
# =========================================================================== # *config targets only - make sure prerequisites are updated, and descend # in scripts/kconfig to make the *config target # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed. # KBUILD_DEFCONFIG may point out an alternative default configuration # used for 'make defconfig' include $(srctree)/arch/$(SRCARCH)/Makefile export KBUILD_DEFCONFIG KBUILD_KCONFIG config: scripts_basic outputmakefile FORCE $(Q)mkdir -p include/linux include/config $(Q)$(MAKE) $(build)=scripts/kconfig $@ %config: scripts_basic outputmakefile FORCE $(Q)mkdir -p include/linux include/config $(Q)$(MAKE) $(build)=scripts/kconfig $@观察上面D部分的代码,无论配置目标是config,还是%config形式的,都依赖于 script_basic 和 outputmakefile两个目标。我们可以找到这两个依赖是定义在框架B1部分中的,抽出代码如下:
# =========================================================================== # Rules shared between *config targets and build targets # Basic helpers built in scripts/ PHONY += scripts_basic scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic # To avoid any implicit rule to kick in, define an empty command. scripts/basic/%: scripts_basic ; PHONY += outputmakefile # outputmakefile generates a Makefile in the output directory, if using a # separate output directory. This allows convenient use of make in the # output directory. outputmakefile: ifneq ($(KBUILD_SRC),) $(Q)ln -fsn $(srctree) source $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \ $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL) endif正如上面代码中的注释所说的一样,这两个目标对应的规则其实是Kconfig和Kbuild都要用到的。scripts_basic规则的命令 $(Q)$(MAKE) $(build)=scripts/basic 做的工作就是:make -f scripts/Makefile.build obj=scripts/basic ,为什么?回过头去看看我们前面说的构建系统内各makefile的关联就知道了。
make -f scripts/Makefile.build obj=scripts/basic 命令由于没有指定目标,所以会在 script/Makefile.build 中处理默认目标__build,如下:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @:同时,别忘记在scripts/Makefile.build中会包含进 scripts/basic 目录下的 Kbuild/Makefile,所以该make命令的实际效果是去编译出 scripts/basic 目录下的三个 host program,也就是 fixdep docproc和hash。什么是host program?一般认为是和内核无关,但是要在编译过程中使用的工具程序。关于这些程序的编译,请参见 scripts/Makefile.host 文件,以及Documentation/kbuild/makefile.txt 文件中关于 host program的这一节。请你留意这里的变量$(always)。
对于目标outputmakefile,其实只在 make 命令带有O 变量时才有用,因为这个时候变量 KBUILD_SRC 不会为空。这个时候中间文件不会存在内核源码树目录中,而是存在另外的一个目录。这个没什么困难的,所以我们这里不再做过多说明。
好,回到我们对配置目标的处理上面来。在处理完两个依赖后,构建系统将会创建两个目录: include/linux 和 include/config。接着执行 $(Q)$(MAKE) $(build)=scripts/kconfig $@ 。如果我们的make 命令是 "make s3c2410_defconfig" 的话,这个时候执行的就是:
make -f scripts/Makefile.build obj=scripts/kconfig s3c2410_defconfig
又牵涉到文件 scripts/Makefile.build 了,我们先搜索一下这个文件里面有没有 s3c2410_defconfig 或 类似于 %config 之类的目标。没有,怎么办,该不会是弄错了吧?呵呵,你忘记了么?文件 scripts/Makefile.build 会包含obj变量所指代目录内的 Makefile的,在这里就是 script/kconfig/Makefile。打开这个文件,果然能找到相关的规则:
在这里,s3c2410_defconfig 需要依赖于同目录下的conf程序。这其实就是Linux内核进行Kconfig操作的主程序之一了,类似的还有mconf,qconf和gconf等。他们其实都是host program。关于它们是如何被编译出来的,还请参见 scripts/kconfig/Makefile 文件,主要是借助于bison,flex和gperf三个工具来生成c源程序文件,之后再编译出来的。由于这部分和我们Linux内核的构建主题关系不大,所以我在这里不再赘述。
由于变量 KBUILD_KCONFIG 在arm架构Makefile中没有被定义,所以 Kconfig 被定义成 arch/arm/kconfig,所以这个目标的规则就简化成:
$(obj)/conf -D arch/arm/configs/s3c2410_defconfig arch/arm/Kconfig
这个命令就是读取并解析以 arch/arm/Kconfig 为首的内核功能选项配置文件,并将文件 arch/arm/configs/s3c2410_defconfig 所设置的默认值分配给对应的所有选项,最终生成隐藏配置文件 .config 。你可以打开看一下 .config 文件的内容,都是这样的格式:
# # Automatically generated make config: don't edit # Linux kernel version: 2.6.31 # Tue Nov 30 21:03:12 2010 # CONFIG_ARM=y CONFIG_HAVE_PWM=y CONFIG_SYS_SUPPORTS_APM_EMULATION=y CONFIG_GENERIC_GPIO=y CONFIG_MMU=y CONFIG_NO_IOPORT=y CONFIG_GENERIC_HARDIRQS=y CONFIG_STACKTRACE_SUPPORT=y CONFIG_HAVE_LATENCYTOP_SUPPORT=y CONFIG_LOCKDEP_SUPPORT=y .....里面貌似都是一些变量的定义。稍后我们会看到在内核开始真正编译之前,构建系统会以 .config 文件为蓝本生成 include/config/auto.conf 文件,这个文件的格式和 .config类似,这个文件会在顶层 以及 scripts/Makefile.build 文件中被直接包含进来,所以这些变量其实就成了 GNU Make 的变量。而内核各子目录中的 Kbuild/Makefile 就可以使用这些变量的定义,来决定是否将该目录下对应的代码功能直接编译到内核里面(这些变量取值为"y")、编译成模块(取值为"m")或者干脆不进行编译(取值为"空")。可以想见,如果选择不编译,那出来的Linux内核就不会有对应的功能。
前面之所以说是以 arch/arm/Kconfig 为首的,那就说明功能配置选项文件可能有多个。的确如此,你如果打开文件 arch/arm/Kconfig 就会看到它通过 source 来包含其他的选项配置文件:
不光是 arm 架构这样,其他所有的架构也都这样。在配置的时候,配置工具首先会解析架构平台目录下的 Kconfig,这就是所谓和平台相关的主Kconfig。主Kconfig文件会包含其他目录的Kcofnig文件,而其他目录的Kconfig又会包含其他各子目录的 Kconfig。如此形成一个树型结构。
另外需要说一下的,如果你的make 命令是 "make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig",那用到的配置工具将会是 mconf,它同样会读取那些配置选项文件,只不过不同的是,它会show除一个菜单界面,并将这些可设置的选项一一呈现出来,如此我们就可以手动进行设置,而不是让mconf去读取 arch/arm/configs/s3c2410_defconfig 之类的文件进行默认设置了。关于gconf等其他配置工具以及Kconfig文件中具体的选项配置语法,我这里就不再详细叙述了,你可以参考目录 Documentation/kbuild/ 下的文件 kconfig.txt以及 kconfig-language.txt 。关于配置目标,除了这里的 menuconfig 和 s3c2410_defconfig,我们后面还会说到另外一个很重要的配置目标: silentoldconfig 。
好,做个阶段性总结吧。作为内核构建系统对 kconfig 的支持,到这步就算是结束了,其根本目标是产生 .config 隐藏文件,用以记录我们所需要的配置结果。但是在Linux内核里面,仅仅把配置结果保存在像 .config 这样一个文件中是不够的。为什么这么说?我们说辛苦辛苦保存起来的东西在关键时候总是需要派上用场的。那么在Linux系统中,这些配置结果由谁去用,又是怎么去用呢?我们在后面会给大家一一道来,其实关于由谁去用,我们前面已经稍微提到过一点了。这里我们还是先不去管这些,休息片刻后把注意力转移到我们的主框架上面来吧。