内核顶层Makefile相关4

http://www.groad.net/bbs/simple/?f104.html

make 的递归执行与 MAKEFLAGS 变量

make 的递归调用是指:在 Makefile 中使用 make 作为一个命令来执行本身或者其它 makefile 文件。递归调用在一个有多级子目录的项目中非常有用。比如,当前目录下有一个 "subdir" 的子目录,这个子目录中又有描述这个目录编译规则的 makefile 文件,在执行 make 时,需要从上层目录开始并完成它所有子目录的编译。

在当前目录下可以使用如下规则对子目录的编译:

subsystem:
    cd subdir && $(MAKE)

其等价于:

subsystem:
    $(MAKE) -C subdir

或者还可以通过 -f 参数直接指定 subdir 下的 Makefile 文件:

make -f $(CURDIR)/subdir/Makefile

上面,$(MAKE) 是对变量 "MAKE" 的引用;$(CURDIR) 是环境变量,表示当前目录;"-C" 选项后接要进入编译的子目录。

在 make 递归执行的过程中,最上层的 make 称为 主控make ,它的命令行选项,如 "-k", "-s" 等会通过环境变量 "MAKEFLAGS" 传递给子 make 进程。变量 "MAKEFLAGS" 的值会被主控 make 自动的设置为包含所执行 make 时的命令行选项的字符串。比如主控执行 make 时使用 "-k" 和 "-s" 选项,那么 "MAKEFLAGS" 的值就为 ks 。子 make 进程处理时,会把此环境变量的值作为执行的命令行选项,因此子 make 进程就使用 "-k" 和 "-s" 这两个命令行选项。

下面看一个具体的示例:
在某个目录下有:

$ ll
total 12
-rw-rw-r--. 1 beyes beyes 73 May 17 00:15 hello.c
-rw-rw-r--. 1 beyes beyes 169 May 17 13:47 Makefile
drwxrwxr-x. 2 beyes beyes 4096 May 17 13:27 submake

$ ll submake/
total 4
-rw-rw-r--. 1 beyes beyes 47 May 17 13:27 Makefile

其中,主控 Makefile 内容为:

MAKEFLAGS += -rR

hello : hello.o
#make -f $(CURDIR)/submake/Makefile
    cd $(CURDIR)/submake && make
    gcc -o hello hello.o

hello.o : hello.c
    gcc -c hello.c -o hello.o

submake 子目录下的 Makefile 的内容为:

all:
    @echo $(MAKEFLAGS)

接下来我们在顶层目录下 make 一下:

[beyes@SLinux temp8]$ make
gcc -c hello.c -o hello.o
cd /home/beyes/Makefile/temp8/submake && make
make[1]: Entering directory `/home/beyes/Makefile/temp8/submake'
wRr
make[1]: Leaving directory `/home/beyes/Makefile/temp8/submake'
gcc -o hello hello.o

从输出中可以看到,submask 目录下的 make 输出了 $(MAKEFLAGS) 变量的值为 wRr ,其中 rR 这两个选项是我们在主控 Makefile 里设定的。而 w 选项的出现是因为 make 使用了 -C 选项的缘故 --- 只要使用 -C 选项来指定下层 Makefile 时,w 选项会被自动打开。如果参数中指定了 -s 选项或 "“--no-print-directory" 选项,那么 w 选项就会失效。正是由于 w 选项的打开,所以在 make 时才会输出目录信息,如:make[1]: Entering directory `/home/beyes/Makefile/temp8/submake'。

make选项

选项说明:
-b -m :为了与其它 make 版本的兼容性,这两个选项忽略。

-B :相当于 --always-make ,强制重建所有规则的目标,不根据规则的依赖描述决定是否重建目标文件。

-C DIR :相当于 --directory=dir ,在读取Makefile之前,进入目录"DIR”,就是切换工作目录到 "DIR” 之后执行 make。存在多个 "-C” 选项时,make的最终工作目录是第一个目录的相对路径。如:"make –C / -C etc" 等价于"make –C /etc" 。一般此选项被用在递归地make调用中。

-d :make在执行过程中打印出所有的调试信息。包括:make 认为那些文件需要重建;那些文件需要比较它们的最后修改时间、比较的结果;重建目标所要执行的命令;使用的隐含规则等。使用 "-d" 选项我们可以看到make构造依赖关系链、重建目标过程的所有信息,它等效于"--debug=a" .

--debug[=FLAGS] :make执行时输出调试信息。可以使用 "OPTIONS" 控制调试信息级别。默认是 "OPTIONS=b" ,"OPTIONS" 的可能值为以下这些,首字母有效(all 和 aw等效):

a (all) 
    输出所有类型的调试信息,等效于 "-d" 选项。
b (basic)
    输出基本调试信息。包括:那些目标过期、是否重建成功过期目标文件。
v (verbose)
    “basic”级别之上的输出信息。包括:解析的makefile文件名,不需要重建文件        等。此选项目默认打开 "basic" 级别的调试信息。
i (implicit)
    输出所有使用到的隐含规则描述。此选项目默认打开 "basic" 级别的调试信息。
j (jobs)
    输出所有执行命令的子进程,包括命令执行的PID等。
m (makefile)
    也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

-e :相当于 --environment-overrides ,使用系统环境变量的定义覆盖Makefile中的同名变量定义。

-f=file :相当于 --file= FILE 或 --makefile= FILE 。指定作为 makefile 的文件的名称。 如果不用该选项,那么make程序首先在当前目录查找名为makefile的文件,如果没有找到,它就会转而查找名为Makefile的文件。如果您在 Linux 下使用 GNU Make 的话,它会首先查找 GNUmakefile,之后再搜索 makefile 和 Makefile 。按照惯例,许多 Linux 程序员使用 Makefile,因为这样能使 Makefile 出现在目录中所有以小写字母命名的文件的前面。所以,最好不要使用GNUmakefile这一名称,因为它只适用于make程序的GNU版本。

-h :相当于 --help ,打印帮助信息。

-i :相当于 --ignore-errors,执行过程中忽略规则命令执行的错误。

-I DIR : 相当于 --include-dir=DIR ,指定被包含 makefile 文件的搜索目录。在Makefile中出现 "include" 另外一个文件时,将在"DIR" 目录下搜索。多个"-I" 指定目录时,搜索目录按照指定顺序进行。

j [JOBS] :相当于 --jobs[=JOBS] ,指定可同时执行的命令数目。在没有指定 "-j" 参数的情况下,执行的命令数目将是系统允许的最大可能数目。存在多个"-j" 参数时,尽最后一个"-j" 指定的数目(“JOBS”)有效。

-k :相当于 --keep-going ,如果使用该选项,即使make程序遇到错误也会继续向下运行;如果没有该选项,在遇到第一个错误时make程序马上就会停止,那么后面的错误情况就不得而知了。我们可以利用这个选项来查出所有有编译问题的源文件。

-l LOAD :相当于 --load-average[=LOAD] 或 --max-load[=LOAD] ,告诉make当存在其它任务在执行时,如果系统负荷超过“LOAD”(浮点数表示的),不再启动新任务。没有指定 "LOAD” 的“-I”选项将取消之前 "-I” 指定的限制。

-n :相当于 --just-print 或 --dry-run 或 --recon ,该选项使make程序进入非执行模式,也就是说将原来应该执行的命令输出,而不是执行。

-o FILE :相当于 --old-file= FILE 或 --assume-old= FILE ,指定文件 "FILE" 不需要重建,即使相对于它的依赖已经过期;同时也不重建依赖于此文件任何文件(目标文件)。注意:此参数不会通过变量 "MAKEFLAGS" 传递给子make进程。

-p :相当于 --print-data-base ,命令执行之前,打印出make读取的Makefile的所有数据(包括规则和变量的值),同时打印出make的版本信息。如果只需要打印这些数据信息(不执行命令)可以使用“make -qp”命令。查看make执行前的预设规则和变量,可使用命令“make –p -f /dev/null”。

-q :相当于 --question ,称为“询问模式”;不运行任何命令,并且无输出。make只是返回一个查询状态。返回状态为0表示没有目标需要重建,1表示存在需要重建的目标,2表示有错误发生。

-r :相当于 --no-builtin-rules ,取消所有内嵌的隐含规则,不过你可以在 Makefile 中使用模式规则来定义规则。同时选项 "-r" 会取消所有支持后追规则的隐含后缀列表,同样我们也可以在Makefile中使用 ".SUFFIXES" 定义我们自己的后缀规则。"-r" 选项不会取消 make 内嵌的隐含变量。

-R :相当于 --no-builtin-variabes ,取消make内嵌的隐含变量,不过我们可以在 Makefile 中明确定义某些变量。注意,"-R” 选项同时打开 "-r” 选项。因为没有了隐含变量,隐含规则将失去意义(隐含规则是以内嵌的隐含变量为基础的)。

-s :相当于 --silent 或 --quiet ,取消命令执行过程的打印。

-S :相当于 --no-keep-going 或 --stop ,取消 "-k" 选项。在递归的 make 过程中子 make 通过 "MAKEFLAGS" 变量继承了上层的命令行选项。我们可以在子 make 中使用 "-S” 选项取消上层传递的 "-k” 选项,或者取消系统环境变量 "MAKEFLAGS” 中的 "-k” 选项。

-t :相当于 --touch ,和 Linux 的 touch 命令实现功能相同,更新所有目标文件的时间戳到当前系统时间。防止 make 对所有过时目标文件的重建。

-v : 相当于 --version ,查看make版本信息。

-w :相当于 --print-directory ,在 make 进入一个目录读取 Makefile 之前打印工作目录。这个选项可以帮助我们调试 Makefile,跟踪定位错误。使用 "-C” 选项时默认打开这个选项。参考本节前半部分 "-C” 选项的描述。

--no-print-directory :取消 "-w” 选项。可以是用在递归的 make 调用过程中,取消 "-C” 参数的默认打开 "-w" 功能。

-W FILE : 相当于 --what-if= FILE 或 --new-file= FILE 或 --assume-file= FILE ,设定文件 "FILE” 的时间戳为当前时间,但不改变文件实际的最后修改时间。此选项主要是为实现了对所有依赖于文件 "FILE” 的目标的强制重建。

--warn-undefined-variables :在发现Makefile中存在对没有定义的变量进行引用时给出告警信息。此功能可以帮助我们调试一个存在多级套嵌变量引用的复杂Makefile。但是:我们建议在书写Makefile时尽量避免超过三级以上的变量套嵌引用。

内核 Makefile O 选项分析

在编译内核时,可以将输出的各种 *.o 文件以及目标文件(如 vmlinux)输出到别的目录中,方法是通过在 make 时使用 O 选项指定输出的目标路径,比如:

[beyes@beyes linux-2.6.35.13]$ make -j8 O=/home/beyes/kerstore/ vmlinux

这样,编译的输出都会放在 /home/beyes/kerstore/ 这个目录下。这里需要注意两点:

1. O 所指定的目录下面要先放置 .config 文件,否则会有如下类似的错误提示:

***
*** You have not yet configured your kernel!
*** (missing kernel config file ".config")
***
*** Please run some configurator (e.g. "make oldconfig" or
*** "make menuconfig" or "make xconfig").
***
make[3]: *** [silentoldconfig] 错误 1
make[2]: *** [silentoldconfig] 错误 2
make[1]: *** 没有规则可以创建“include/config/kernel.release”需要的目标“include/config/auto.conf”。 停止。
make: *** [sub-make] 错误 2

2. 执行 make 命令必须在内核源代码的根目录下执行。这里在顶层 Makefile 中的注释有说明:

# kbuild supports saving output files in a separate directory.
# To locate output files in a separate directory two syntaxes are supported.
# In both cases the working directory must be the root of the kernel src.
# 1) O=
# Use "make O=dir/to/store/output/files/"

# 2) Set KBUILD_OUTPUT
# Set the environment variable KBUILD_OUTPUT to point to the directory
# where the output files shall be placed.
# export KBUILD_OUTPUT=dir/to/store/output/files/
# make
#
# The O= assignment takes precedence over the KBUILD_OUTPUT environment
# variable.

由注释可以看到,在命令行中直接在 O 选项后指定路径和设置一个环境变量 KBUILD_OUTPUT 的效果是一样的。

下面分析执行带有 O 选项 make 命令时的前半部内容,所谓的前半部是指不涉及具体构建目标文件的过程:

来到第 97 行:

ifeq ($(KBUILD_SRC),)

由于这里 KBUILD_SRC 变量还未定义,所以必然为空,所以程序继续往下走。

来到 101-103 行:

ifeq ("$(origin O)", "command line")
    KBUILD_OUTPUT := $(O)
endif

这里判断 O 选项是否来自命令行,是的话,就将目录信息 $(O) 赋值到变量 KBUILD_OUTPUT 中。

来到 106-107 行:

PHONY := _all
_all:

如果只是简单的执行 make 命令时,_all 就是默认的目标了。

来到 110 行:

$(CURDIR)/Makefile Makefile: ;

这只是为了避免顶层 Makefile 规则冲突。

来到 112-118 行:

ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)
$(if $(KBUILD_OUTPUT),, 
    $(error output directory "$(saved-output)" does not exist))

因为之前已经用 O 选项所带的目录信息赋值到 KBUILD_OUTPUT 变量中,所以第 1 条判断语句不为空,接着又将这个路径信息保存到 saved-output 变量中。然后通过 shell 函数执行 shell 命令,主要是测试所给的目录是否存在,不存在的话通过 error 函数报出错误信息,然后停止 make 的继续执行。如果指定的目录是存在的,那么程序继续往下走。

来到 12 行:

PHONY += $(MAKECMDGOALS) sub-make

MAKECMDGOALS 变量是 make 的一个内置变量,它表示的是所要构建目标的一个终极列表。这里是表示,将 MAKECMDGOALS 所表示的所有目标以及 sub-make 都定义为伪目标。

来到 122-123 行:

$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
    $(Q)@:

这里使用 filter-out 函数将 $(MAKECMDGOALS) 表示的所有目标中去掉 _all sub-make $(CURDIR)/Makefile 这 3 个目标,此后剩下的目标和 _all 这两个目标都将依赖于 sub-make ,而 $(Q)@: 表示什么都不做,它的作用仅仅是提示要先去实现 sub-make 这个依赖,而实现了 sub-make 这个依赖后也就相当于实现了 $(MAKECMDGOALS) 和 _all 这两个目标了。

来到 125-129 行:

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))

在这里,实际上是对 sub-make 目标的实现。

这里我们并不指定 $(KBUILD_EXTMOD) ,也就是我们不是在编译外部模块,而假定是在编译 vmlinx ,所以 KBUILD_EXTMOD 变量为空。

底下 $(filter-out _all sub-make,$(MAKECMDGOALS)) 这一行再次用 filter-out 函数将 _all 和 sub-make 这两个目标去掉,剩下的只有 $(MAKECMDGOALS) 这个目标列表里的目标了。如果我们只是 make vmlinux 那么 $(MAKECMDGOALS) 就表示 vmlinux 。$(CURDIR) 是内置变量,表示当前目录。

所以,上面的几条指令可以写成:

make -C [输出的外部目录] KBUILD_SRC=`pwd` KBUILD_EXTMOD=“” -f `pwd`/Makefile [要生成的目标]

从上面看到,在此我们重新调用了顶层 Makefile ,这是递归调用。当我们再次调用顶层 Makefile 时,由于我们在上面的命令中的 KBUILD_SRC 变量已经被赋值,所以当再次来到第 97 行中的 ifeq ($(KBUILD_SRC),) 判断时,是不会再进去到它的代码块中(从 97-134 行) 。这样,程序就会直接跳到 137 行的 ifeq ($(skip-makefile),) ,因为此时 skip-makefile 还未定义,故为空,所以程序得以继续往下执行。后面的执行就是上面所说的 “下半部”,这部分涉及到了具体目标文件的构建过程。当 “下半部” 执行完毕后会返回到 131-134 行,即:

# Leave processing to above invocation of make

skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

这时,skip-makefile 变量被赋值为 1 。此时,程序会再次来到 137 行:

# We process the rest of the Makefile if this is the final invocation of make
ifeq ($(skip-makefile),)

上面的注释意思是:如果这时最终的一次调用 make,那么我们处理 Makefile 余下的部分。所谓的 “最终一次调用” 就是说 “递归到最底层” 的那一次。如上面用 O 指定输出路径,会进行一次递归,也就是在进入第 2 次调用 Makefile 时,我们会往下执行;当不指定 O 时,如只是 make vmlinux 时,make 会直接往下走。所以,当上面的递归返回到第 1 层 Makefile 时,这里由于 skip-makefile 值为 1,程序直接跳到 Makefile 最底部,从而结束了 Makefile 这个过程。

从上面的分析可以看到,O 选项所对应的代码块从 97-134 这里,如果带 O 选项就分析这一块代码,如果不带 O,则略过此段的分析。

no-dot-config-targets/dot-config/mixed-targets变量

no-dot-config-targets 定义在 414 行:

no-dot-config-targets := clean mrproper distclean 
                         cscope TAGS tags help %docs check% 
                         include/linux/version.h headers_% 
                         kernelrelease kernelversion

no-dot-config-targets 的意思是这些目标和 .config 完全无关的。使用 make help 也可以看到这些目标,如:help, kernelversion, kernelrelease, headers_install 等等。

而与 .config 有关的目标(dog-config)则有两种,一种是产生 .config 文件,如 menuconfig, oldconfig, defconfig 等,这些目标也称为配置目标(config targets);另外一种是需要使用 .config 文件的目标,它们必须以 .config 文件中的配置作为构建自己的依据,比如 all, vmlinux, modules, bzImage 等,这些目标也称为构建目标(build targets)。

还有一种目标是混合目标(mixed-targets),它表示在执行 make 命令时,同时指定了 配置目标 和 其它的目标,而不仅仅是指 配置目标 和 构建目标 相混合。比如 make defconfig vmlinux 命令是 配置目标和构建目标的混合;make defconfig kernelversion 中的 kernelversion 则不是构建目标。

顶层 Makefile 文件中与上面所述几个目标的相关变量:no-dot-config-targets, dot-config, mixed-targets 。相关代码定义在 414-436 行:

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-targets, mixed-targets, dot-config 这 3 个变量分别定义为 0, 0, 1 。接下来在在

ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)

中判断 make 命令的目标中是否含有 no-dot-config-targets 这些目标,如果有,那么接着执行下面

ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)

的判断,这里将 no-dot-config-targets 这种目标从 make 命令中的所有目标都去掉,然后看结果是否为空,若为空的话,则说明 make 命令中仅含有变量 no-dot-config-targets 中指出的目标,这时就将 dot-config 变量值设为 0 ,所以这次所要 make 的目标是和 .config 文件无关的,也就是说,它们既不会产生 .config配置文件,也不需要用 .config 文件来构建其它相关的目标。

继续往下,在

ifeq ($(KBUILD_EXTMOD),)

中判断是否要编译模块,如果 $(KBUILD_EXTMOD) 为空,则说明不是编译外部模块,那么程序来到下面的判断:

ifneq ($(filter config %config,$(MAKECMDGOALS)),)

这里判断 make 命令中指定的目标中是否含有 config 或者 %config 这样的目标,如果有的话,则将 config-targets 变量设置为 1 。

接着继续来到下面的判断语句:

ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)

这里判断除了 config 或 %config 这些目标外,是否还要构建其它目标,如果还有的话,那么就是在 make 一个混合目标,此时将 mixed-targets 变量设置为 1 。比如 make defconfig kernelversion 就是构建混合目标的例子。

auto.conf, auto.conf.cmd, autoconf.h

在编译构建性目标时(如 make vmlinux),顶层 Makefile 的 $(dot-config) 变量值为 1 。

在顶层 Makefile 的 497-504 行看到:

ifeq ($(dot-config),1)
# Read in config
-include include/config/auto.conf

ifeq ($(KBUILD_EXTMOD),)
# Read in dependencies to all Kconfig* files, make sure to run
# oldconfig if changes are detected.
-include include/config/auto.conf.cmd

# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;

# If .config is newer than include/config/auto.conf, someone tinkered
# with it and forgot to run make oldconfig.
# if auto.conf.cmd is missing then we are probably in a cleaned tree so
# we execute the config step to be sure to catch updated Kconfig files
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

其中,

-include include/config/auto.conf
-include include/config/auto.conf.cmd

这两行尝试包含 auto.conf 和 auto.conf.cmd 这两个文件。由于使用 -include 进行声明,所以即使这两个文件不存在也不会报错退出,比如在第 1 次编译内核,且已经配置好 .config 文件时,这两个文件还未生成。

假如我们已经编译好了 vmlinux 这个目标,那么我们会在 include/config 这个目录下看到 auto.conf 和 auto.conf.cmd 这两个文件。

从 include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd 这条语句可以知道,auto.conf 文件依赖于 $(KCONFIG_CONFIG) 和 include/config/auto.conf.cmd 。其中 $(KCONFIG_CONFIG) 变量的值就是 .config 这个配置文件。那么 include/config/auto.conf.cmd 这个文件应该在什么时候生成?

现在仍然假设 auto.conf 和 auto.conf.cmd 还没有生成,那么由上面的 $(KCONFIG_CONFIG) include/config/auto.conf.cmd: ; 这条语句知道,该语句中的目标没有依赖,也没有生成它的规则命令,所以可想 GNU Make 本身无法生成 auto.conf.cmd 的。然后该条语句后面的一个分号表明,这两个目标被强制是最新的,所以下面这条命令得以执行:

$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig

这里我们看到要生成一个目标 silentoldconfig ,这个目标定义在 scripts/kconfig/Makefile 中。因为这里使用 -f 选项重新指定了顶层 Makefile,而目标又是 silentoldconfig ,所以该命令最终会在顶层 Makefile 的 462-464 这里执行:

%config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

这时,我们来到 scripts/kconfig/Makefile 文件里。在该文件的 32-34 行看到:

silentoldconfig: $(obj)/conf
    $(Q)mkdir -p include/generated
    $< -s $(Kconfig)

从上面看到,silentoldconfig 目标需要依赖 conf 这个程序,该程序也在 scripts/kconfig 目录下生成。

$< -s $(Kconfig) 该条命令相当于 conf -s $(Kconfig) ,这里 $(Kconfig) 是位于不同平台目录下的 Kconfig 文件,比如在 x86 平台就是 arch/x86/Kconfig 。

conf 程序的源代码的主函数在同目录的 conf.c 文件中,在 main() 函数中看到:

while ((opt = getopt(ac, av, "osdD:nmyrh")) != -1) {
    switch (opt) {
    case 'o':
        input_mode = ask_silent;
        break;
    case 's':
        input_mode = ask_silent;
        sync_kconfig = 1;
    break;
... ...

所以,在使用 s 参数时,sync_kconfig 这个变量会为 1 。同样在 main() 函数还看到:

if (sync_kconfig) {
    name = conf_get_configname();
    if (stat(name, &tmpstat)) {
        fprintf(stderr, _("***
"
        "*** You have not yet configured your kernel!
"
        "*** (missing kernel config file "%s")
"
        "***
"
        "*** Please run some configurator (e.g. "make oldconfig" or
"
        "*** "make menuconfig" or "make xconfig").
"
        "***
"), name);
        exit(1);
    }
}    

上面代码中,如果我们从未配置过内核,那么就会打印出错误信息,然后退出。这里假设已经配置过内核,并生成了 .config 文件,那么在 main() 函数中会来到:

switch (input_mode) {
case set_default:
    if (!defconfig_file)
        defconfig_file = conf_get_default_confname();
    if (conf_read(defconfig_file)) {
        printf(_("***
"
        "*** Can't find default configuration "%s"!
"
        "***
"), defconfig_file);
        exit(1);
    }
    break;
case ask_silent:
case ask_all:
case ask_new:
    conf_read(NULL);
    break;
... ...

由于使用 s 选项,则 input_mode 为 ask_silent,所以这里会执行 conf_read(NULL); 函数。

conf_read(NULL); 函数用来读取 .config 文件。读取的各种相关内容主要存放在一个 struct symbol 结构链表里,而各个结构的相关指针则放在一个 symbol_hash[] 的数组中,对于数组中元素的寻找通过 fnv32 哈希算法进行定位。

最后会来到 conf.c 中的底部:

if (sync_kconfig) {
    /* silentoldconfig is used during the build so we shall update autoconf.
    * All other commands are only used to generate a config.
    */
    if (conf_get_changed() && conf_write(NULL)) {
        fprintf(stderr, _("
*** Error during writing of the kernel configuration.

"));
        exit(1);
    }
    if (conf_write_autoconf()) {
        fprintf(stderr, _("
*** Error during update of the kernel configuration.

"));
        return 1;
    }
} else {
    if (conf_write(NULL)) {
    fprintf(stderr, _("
*** Error during writing of the kernel configuration.

"));
    exit(1);
    }
}

实际上也只有当处理 silentoldconfig 目标是 sync_kconfig 变量才会为 1 。上面代码中的 conf_write_autoconf() 函数就用来生成 auto.conf, auto.conf.cmd 以及 autoconf.h 这 3 个文件。

在 if (conf_get_changed() && conf_write(NULL)) 这个判断里,conf_get_changed() 函数判断 .config 文件是否做过变动,如果是,那么会调用 conf_write(NULL) 来重新写 .config 文件。实际上,对于 defconfig, oldconfig, menuconfig 等目标来说,conf 程序最终也是调用 conf_write() 函数将配置结果写入 .config 文件中(最后那个 else 里的内容便是)。

确保了 .config 已经最新后,那么调用 conf_write_autoconf() 生成 auto.conf,auto.conf.cmd 以及 autoconf.h 这 3 个文件。

来到 conf_write_autoconf() 函数。

在 conf_write_autoconf() 里,调用 file_write_dep("include/config/auto.conf.cmd"); 函数将相关内容写入 auto.conf.cmd 文件。在生成的 auto.conf.cmd 文件中可以看到:

include/config/auto.conf: 
    $(deps_config)

可以看到 auto.conf 文件中的内容依赖于 $(deps_config) 变量里定义的东西,这些东西基本上是各个目录下的 Kconfig 以及其它一些相关文件。

auto.config 和 .config 的差别是在 auto.config 里去掉了 .config 中的注释项目以及空格行,其它的都一样。

仍然在 conf_write_autoconf() 里,分别建立了 .tmpconfig,.tmpconfig_tristate 和 .tmpconfig.h 这 3 个临时文件:

out = fopen(".tmpconfig", "w");
if (!out)
    return 1;

tristate = fopen(".tmpconfig_tristate", "w");
if (!tristate) {
    fclose(out);
    return 1;
}

out_h = fopen(".tmpconfig.h", "w");
if (!out_h) {
    fclose(out);
    fclose(tristate);
    return 1;
}

然后将文件头的注释部分分别写入到这几个临时文件中:

sym = sym_lookup("KERNELVERSION", 0);
sym_calc_value(sym);
time(&now);
fprintf(out, "#
"
    "# Automatically generated make config: don't edit
"
    "# Linux kernel version: %s
"
    "# %s"
    "#
",
    sym_get_string_value(sym), ctime(&now));
fprintf(tristate, "#
"
    "# Automatically generated - do not edit
"
    "
");
fprintf(out_h, "/*
"
    " * Automatically generated C config: don't edit
"
    " * Linux kernel version: %s
"
    " * %s"
    " */
"
    "#define AUTOCONF_INCLUDED
",
    sym_get_string_value(sym), ctime(&now));

接着在 for_all_symbols(i, sym) 这个循环里(是一个宏)里将相关内容分别写入到这几个文件中。

在最后一段代码中,将这几个临时文件进行改名:

name = getenv("KCONFIG_AUTOHEADER");
if (!name)
    name = "include/generated/autoconf.h";
if (rename(".tmpconfig.h", name))
    return 1;
name = getenv("KCONFIG_TRISTATE");
if (!name)
    name = "include/config/tristate.conf";
if (rename(".tmpconfig_tristate", name))
    return 1;
name = conf_get_autoconfig_name();
/*
* This must be the last step, kbuild has a dependency on auto.conf
* and this marks the successful completion of the previous steps.
*/
if (rename(".tmpconfig", name))
    return 1;

上面代码中的 conf_get_autoconfig_name() 实现为:

const char *conf_get_autoconfig_name(void)
{
    char *name = getenv("KCONFIG_AUTOCONFIG");

    return name ? name : "include/config/auto.conf"; 
}

从上面可以看到,分别生成了以下几个文件:

include/generated/autoconf.h
include/config/tristate.conf
include/config/auto.conf

其中 include/generated/autoconf.h 头文件由内核本身使用,主要用来预处理 C 代码。比如在 .config 或 auto.conf 中定义要编译为模块的项,如:

CONFIG_DEBUG_NX_TEST=m

在 autoconf.h 中会被定义为:

#define CONFIG_DEBUG_NX_TEST_MODULE 1

在 .config 或 auto.conf 后接字符串值的项,如:

CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"

在 autoconfig.h 中会被定义为:

#define CONFIG_DEFCONFIG_LIST "/lib/modules/$UNAME_RELEASE/.config"

同样对应于 int 型的项如 CONFIG_HZ=1000 在 autoconf.h 中被定义为 #define CONFIG_HZ 1000 。

Linux内核Makefile分类

KBuild MakeFile 

  从Linux内核2.6开始,Linux内核的编译采用 Kbuild 系统,这同过去的编译系统有很大的不同,尤其对于 Linux 内核模块的编译。在新的系统下,Linux 编译系统会两次扫描 Linux 的 Makefile :首先编译系统会读取 Linux 内核顶层的 Makefile,然后根据读到的内容第二次读取 Kbuild 的 Makefile 来编译Linux内核。


Linux内核Makefile分类


Kernel Makefile

  Kernel Makefile位于Linux内核源代码的顶层目录,也叫 Top Makefile。它主要用于指定编译 Linux Kernel 目标文件(vmlinux)和模块(module)。这编译内核或模块是,这个文件会被首先读取,并根据读到的内容配置编译环境变量。对于内核或驱动开发人员来说,这个文件几乎不用任何修改。

Kbuild Makefile

  Kbuild 系统使用 Kbuild Makefile 来编译内核或模块。当 Kernel Makefile 被解析完成后,Kbuild 会读取相关的 Kbuild Makefile 进行内核或模块的编译。Kbuild Makefile 有特定的语法指定哪些编译进内核中、哪些编译为模块、及对应的源文件是什么等。内核及驱动开发人员需要编写这个Kbuild Makefile 文件。

ARCH Makefile

  ARCH Makefile位于ARCH/$(ARCH)/Makefile,是系统对应平台的Makefile。Kernel Top Makefile会包含这个文件来指定平台相关信息。只有平台开发人员会关心这个文件。


Kbuild Makefile

Kbuild Makefile 的文件名不一定是 Makefile,尽管推荐使用 Makefile 这个名字。大多的 Kbuild文件 的名字都是 Makefile。为了与其他Makefile文件相区别,你也可以指定Kbuild Makefile的名字为 Kbuild。而且如果 “Makefile” 和 “Kbuild” 文件同时存在,则 Kbuild 系统会使用 “Kbuild” 文件。


目标定义

  Kbuild Makefile的一个最主要功能就是指定编译什么,这个功能是通过下面两个对象指定的 obj-? 和 xxx-objs :

obj-?
  obj-? 指定编译什么,怎么编译?其中的 “?” 可能是 “y” 或 “m”,“y”指定把对象编译进内核中,“m”指定把对象编译为模块。语法如下:

obj-? = $(target).o

target为编译对象的名字。如果没有指定xxx-objs,这编译这个对象需要的源文件就是$(target).c或$(target).s。如果指定了$(target)-objs,则编译这个对象需要的源文件由$(target)-objs指定,并且不能有$(target).c或$(target).s文件。

xxx-objs
  xxx-objs指定了编译对象需要的文件,一般只有在源文件是多个时才需要它。
只要包含了这两行,Kbuild Makefile就应该可以工作了。
嵌套编译

  有时一个对象可能嵌入到另一个对象的目录下,那个如何编译子目录下的对象呢?其实很简单,只要指定 obj-? 的对象为子目录的名字就可以了:

obj-? = $(sub_target)/

其中 “?” 可以是 “y” 或 “m”,$(sub_target) 是子目录名字。
编译器选项

  尽管在大多数情况下不需要指定编译器选项,有时我们还是需要指定一些编译选项的:

ccflags-y, asflags-y and ldflags-y

这些编译选项用于指定cc、as和ld的编译选项。

 http://blog.csdn.net/it1988888/article/details/8040605

原文地址:https://www.cnblogs.com/baiyw/p/3316238.html