zoukankan      html  css  js  c++  java
  • 内核顶层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

  • 相关阅读:
    Zabbix实现企业微信(WeChat)告警
    Zabbix使用腾讯企业邮箱发送警报邮件,在微信查看警报邮件
    centos7安装pip
    zabbix源码安装3.4.11客户端和yum安装4.2.1客户端
    zabbix中文乱码解决
    centos7配置阿里yum源替换centos自带的yum源(其他镜像yum源配置相似)
    CentOS7.5下Redis5.0.5安装与配置
    LVS+Keepalived搭建高可用负载均衡
    centos7配置双ip(内外网均可访问)
    NSMutableAttributedString 的使用
  • 原文地址:https://www.cnblogs.com/baiyw/p/3316238.html
Copyright © 2011-2022 走看看