Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1306881
  • 博文数量: 860
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-20 19:57
个人简介

对技术执着

文章分类

全部博文(860)

文章存档

2019年(16)

2018年(12)

2015年(732)

2013年(85)

2012年(15)

我的朋友

分类: LINUX

2015-03-14 17:24:31

原文地址:Linux内核构建系统之四 作者:kyok520


Linux内核构建系统之四


回到我们的主框架上面来,讨论完配置目标的处理后,就轮到框架中 "ifeq ($(config-targets),1)-endif" 块的 else 部分了。这部分是为了处理那些构建目标以及和.config无关的目标,其对这些目标处理的代码都位于框架中的E部分中。在E部分之前,有一个不小的 "ifeq ($(dot-config),1)-endif" 块,我们暂先不去理会,且看这个 ifeq-endif 块之前有一小段注释:



# ===========================================================================
# Build targets only - this includes vmlinux, arch specific targets, clean
# targets and others. In general all targets except *config targets.


这段注释字面上的意思是说 "ifeq ($(config-targets),1)-endif" 块的 else 部分处理的都是 Build targets,也就是除了配置目标之外的其他目标。注意他这里对 Build targets 分类方法,其实和我们之前的分类方法是有差异的,他这里所谓的 Build targets ,除了包括我们之前分类中所说的构建目标外,还包括之前我们说的和 .config 文件无关的那些目标。这其实是对同个东西的两种不同分类罢了,不影响我们的分析。其实不管哪种分类,都改变不了在本 else 部分既处理真正的构建目标,又处理那些和 .config 文件无关目标的事实。

好,鉴于我们已有这样的事实认同。那接下来理解前面说的那个不小的 "ifeq ($(dot-config),1)-endif" 块就比较容易了。很显然这个时候如果变量 dot-config 等于 1,那说明针对的是那些真正的构建目标,因为他们需要文件 .config 来完成真正的构建。而如果这个变量不为1,那么针对的就是那些和 .config 完全无关的目标了。

当 dot-config 等于 1 时,构建系统首先会尝试性的包含 include/config/auto.conf 文件。为什么说是尝试性的?这是 GNU make 做 "-include" 的特性。其意思和是否存在所包含的文件以及是否能根据所存在的规则去重新创建所包含文件有关系。

GNU Make 是这样一个大致的读取Makefile的流程:首先它读入主Makefile,在读的过程中,如果碰到 "include" 或 "-include",它就会包含对应的文件。如果对应的文件不存在,则暂时跳过做包含的地方,继续读入。待所有makefie都读完后。GNU Make会考虑将每个makefile作为目标,在全局范围内查找是否有能生成这些目标的规则,如果发现有一个makefile可以被一条规则生成,那么GNU Make就会先生成这个makefile。生成后,GNU Make又会从零开始读入主Makefile以及所有被包含的makefile,然后再检查是否有makefile可以被remade….这样一次又一次,直到所有的makefile都不需要再次生成了,它才处理依赖规则链。它之这样做,是为了保证所有 makefile 都是 update-to-date 的。

那如果你的子makefile是被 "include" 所包含的,但是这个makefile本身不存在,且无法用一条规则去Remake出来,那么 GNU Make就会报错并退出。相反,如果你用的是 "-include",那么 GNU Make就什么都不做,就好象什么也没发生过那样继续处理后面的事情。所以,我们说这里是尝试性的,通俗点就是”有则包含,没有也罢了“:)。

接下来回到主框架,假如你的 make 命令是 "make ARCH=arm CROSS_COMPILE=arm-linux- zImage",那么dot-config 等于 1,并且变量 KBUILD_EXTMOD 会等于空。构建系统又会先尝试性的包含文件 include/config/auto.conf.cmd,然后继续处理主框架中的 G1部分。我们先看看G1部分的代码:


# 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/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig


:),你看到这里也许会露出些许微笑,因为你还记得那个 "-include include/config/auto.conf"。没错,针对你的make命令,GNU Make在处理任何目标之前,它一定会努力的为我们生成文件 include/cofig/auto.conf ,用的就是这里的这条对应规则。注意这里的细节,GNU Make同样也会努力重新用上面这条规则去生成 include/config/auto.conf.cmd 文件,可是无奈上面这条规则既没有依赖,又没有命令,所以GNU Make 是怎么也没办法生成 auto.conf.cmd。那么 auto.conf.cmd 文件又是从什么地方生成呢?答案是在生成 include/config/auto.conf 的时候。稍后,我们会看到,它同样在这个时候生成了另外一个文件 include/linux/autoconf.h。

既然已经说到这几个文件了,那我就预先来回答一下上面提出来的问题。什么问题?就是配置过程最后产生了用于记载配置结果的 .config ,那么其中的配置结果由谁使用,又是如何使用的问题。我们说在整个Linux内核系统中,有两方面的用户需要关注所记载的配置结果。一个自然是内核构建系统,它需要根据配置结果产生具有指定功能的内核映像;另外一个就是大部分代码为C语言代码的Linux内核本身,它也需要用户的配置结果,主要用来预处理C代码。前者使用配置结果,并不是直接通过 .config 文件来的,而是将其转换成两个文件:include/config/auto.conf 和 include/config/auto.conf.cmd。后者也没办法直接通过 .config 文件来使用配置结果,它需要将其转换成C语言头文件的形式使用,在这里就是文件 include/linux/autoconf.h 。关于这三个文件的内容,我们稍后叙述。

还是回到上面处理 auto.conf 的规则中来,变量 KCONFIG_CONFIG 指代的就是配置文件 .config 。目标 auto.conf 依赖于 .config 和 auto.conf.cmd 。当GNU Make使用这条规则来remake auto.conf的时候,这两个文件即使不存在也没有关系。因为这样的话,GNU Make 会因为有上面这条没有依赖也没有命令的规则而认为这两个依赖是最新的,所以此时auto.conf规则的命令总是会被得到执行。

这是有意思的地方,因为这是不是意味着我可以在一个刚刚解压出来的Linux内核目录中直接用命令 "make ARCH=arm CROSS_COMPILE=arm-linux- zImage"来完成构建呢?咱们试一下:

在解压缩后的内核代码目录中直接做 make zImage

从结果看前面都没问题,直到用conf进行最后一步的配置时出现了错误。至于错误原因呢,是因为找不到文件 .config 。我们可以找出出现错误的代码,就在文件scripts/kconfig/conf.c 的main函数中,如下所示的代码片段:


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


就像前面讲的,这个时候GNU Make会执行auto.conf规则的对应命令"$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig"。而针对这个命令,配置程序 conf 最后会在main函数上面这个代码片段的前面,将上面这个代码片段中的 sync_kconfig 设置为1。接下来取得配置文件的名称放在 name 变量里面,再使用 stat 函数查询这个文件的状态。结果该函数返回非0值,使得GNU Make认为配置文件.config不存在而报错。

回到处理 auto.conf 的那条规则上来。考虑一下如果这个时候 auto.conf 文件和 .config 文件都存在,但是 .config 比较新的话会怎么样呢?想想,这会是一个怎么样的 Scenario呢?如果你刚刚编译过内核并已生成映像文件,这个时候你从你朋友那里取来一个 .config 文件,并且将其放到了你的内核源码树下面,这个时候就产生了这种情况。这个时候你需要根据你的内核的版本来做处理。如果你的版本和你的朋友一样,那没问题,你大可以直接 "make ... zImage"。但是,如果你们的版本不一样,你最好做下 "make ... oldconfig"。这个命令的作用是针对原来版本内核的配置文件设置来配置新版本的内核。新版内核中没变的配置选项可以设置成原来的值,但是新添加的配置项就需要你自己手动设置了。

再次回到处理 auto.conf 的那条规则上来,我们看到它的命令 "$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig",这个命令最终会导致GNU Make执行文件 scripts/kconfig/Makefile 中针对目标 silentoldconfig 的命令:

$(obj)/conf -s arch/arm/Kconfig

conf配置程序在前面已经有所提及,其对应的代码都在目录 scripts/kconfig/ 中。conf 的主函数main即定义在 conf.c 文件中。其实,目标silentoldconfig 和 目标oldconfig类似,只不过它多了生成auto.conf、auto.conf.cmd以及autoconf.h等三个文件的任务。这是怎么做到的?答案就在conf.c文件中 main 函数最后的一段代码:


int main(int ac, char **av) { int opt; const char *name; struct stat tmpstat;
 
        .....
        ..... 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, _("\n*** Error during writing of the kernel configuration.\n\n"));
                        exit(1); } if (conf_write_autoconf()) { fprintf(stderr, _("\n*** Error during update of the kernel configuration.\n\n")); return 1; } } else { if (conf_write(NULL)) { fprintf(stderr, _("\n*** Error during writing of the kernel configuration.\n\n"));
                        exit(1); } } return 0; }


前面我们已经介绍过,当用 conf 处理 silentoldconfig 时,变量sync_kconfig会被设置为1。实际上,也只有处理此目标时,它才会被设置成1,其他的目标都不会。对于oldconfig、menuconfig等目标来说,conf程序最后会直接调用函数 conf_write 将配置结果写到配置文件 .config 中去。该函数的定义在同目录的另外一个文件 confdata.c 中,这里我们不再细究下去了,你可以自己探究。而对于 silentoldconfig 目标来说,conf 程序除了调用 conf_write 来写 .config 文件外,它还会调用 conf_write_autoconf 函数来完成 auto.conf、auto.conf.cmd 和 autoconf.h 三个文件的生成。我们且来看看同样定义在 confdata.c 文件中的 conf_write_autoconf 函数:

confdata.c 文件中的 conf_write_autoconf 函数

该函数一开始就写 include/config/auto.conf.cmd 文件,然后将对应的内容写入到两个临时文件 .tmpconfig 和 .tmpconfig.h 中,并在最后将这两个文件分别重新命名为include/config/auto.conf 和 include/linux/autoconf.h。注意上面代码中对 conf_split_config 函数的调用,其目的是在目录 include/config 中产生一系列的头文件,至于这些头文件如何产生的、以及它们的作用,我们这里先留下一个伏笔,回头再来探究。

知道了这几个文件是如何产生的。我们再来注意一下它们的内容。文件 auto.conf.cmd 里面记录的是 auto.conf 目标的相关依赖,而文件 auto.conf 和文件 autoconf.h 的内容和 .config 文件的内容直接相关。举例来说,如果你找到有个定义在 .config 文件里的变量形式:CONFIG_MMU=y 表示要将虚拟内存管理的功能编译进内核,那么在 auto.conf 里面 也会有完全相同的定义形式:CONFIG_MMU=y。而在文件 autoconf.h 文件中,则会有一个这样形式的宏定义:#define CONFIG_MMU 1。假如.config中的形式是 CONFIG_IKCONFIG=m,那么在 auto.conf 中的形式也会是:CONFIG_IKCONFIG=m ,而在文件 autoconf.h 中的定义形式则变成:#define CONFIG_IKCONFIG_MODULE 1 。

前面说的是当 KBUILD_EXTMOD 为空的时候,那么当这个变量取值不为空(也就是我们尝试在用 "make ... -M=..." 之类的命令来编译外部模块)时,GNU Make 只是简单的处理上面框架中的G2部分。编译外部模块时并不需要关心 auto.conf/autoconf.h 是不是最新的。它只是检查他们是否存在,如果不存在,它就报错并退出。

之前我们说过构建目标以及和.config无关的目标是混在一块处理的。那当 dot-config 等于 1 时,处理的是构建目标,而在 dot-config 等于 0 时,构建系统处理的是那些和文件 .config没有关系的目标。注意看我们的框架,处理和.config无关目标的时候,构建系统只是简单的针对 auto.conf 目标定义了一个既没有依赖又没有命令的规则:


# Dummy target needed, because used as prerequisite
include/config/auto.conf: ;


这样做的意思是因为 auto.conf 在后面会为多个其他目标所依赖。我们在这里只是登记一下说:“嘿,兄弟我(auto.conf)已经是最新的了,你们别再管我了,只管继续做你们自己的事情就好。”

关于对上面框架代码E部分中和 .config 文件无关目标的处理,这里限于篇幅,我们就不再多讲,您可自己研究。

阅读(767) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~