Linux内核构建系统之三

 顶层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。打开这个文件,果然能找到相关的规则:

Linux内核构建系统中defconfig系列目标的处理

在这里,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 来包含其他的选项配置文件:

Linux内核构建系统中所使用的配置选项文件

不光是 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系统中,这些配置结果由谁去用,又是怎么去用呢?我们在后面会给大家一一道来,其实关于由谁去用,我们前面已经稍微提到过一点了。这里我们还是先不去管这些,休息片刻后把注意力转移到我们的主框架上面来吧。

 

【原文请至这里浏览:http://yihect.juliantec.info/julblog//post/4/19】

 

你可能感兴趣的:(linux,linux,kernel,System,Build,makefile,Kbuild,内核构建系统)