一、内核源码中makefile文件的分类
1. 主目录下的Makefile(不妨称为main-makefile)
它主要有两个作用:生成vmlinux(内核映像)和modules(模块)。
2. 主目录下的Rules.make
Rules.make中定义通用规则供main-makefile和subdir-makefiles调用。
变量subdir-y为用于构建vmlinux(内核)的目录,变量subdir-m为用于构建modules(模块)的目录;subdir-n和subdir-中的目录将不参加构建工作。
变量obj-y为内核的目标文件列表,在各subdir-y中会对其赋相应的值。如:lib/Makefile的第13-14行。变量obj-m为各模块 的目标文件列表,在各subdir-m中会对其赋相应的值。如在drivers/Makefile中,在第13行对subdir-y赋值,并在第14行将 此列表复制给subdir-m。从第17行开始对subdir-y和subdir-m进行补充,这两个列表的建立完成。obj-n和obj-中所有的目标 将被忽略。
3. arch/*/目录下的Makefile(不妨称为arch-makefiles)
arch-makefiles是与特定的体系结构相关的,它们分别是针对特定CPU的makefiles。
4. 除arch外的子目录下的Makefile(不妨称为subdir-makefiles)
subdir-makefiles只负责在自己的目录中生成目标文件。它们接受来自上层Make传递下来的信息,并根据这些信息来构造一个需要编译的文件 列表,并交由Rules.make处理。subdir-makefiles都比较简短,因为通用的规则都已在Rules.make定义了。一般 subdir-makefiles的主要工作是对obj-*或subdir-*变量赋值。
二、内核和模块构建的过程
第一步:make ARCH=arm menuconfig
目标menuconfig的定义在main-makefile的第306行。main-makefile的第308行调用Menuconfig 工具(scripts/Menuconfig),让它根据arch/$(ARCH)/config.in文件的内容在主目录下生成.config文件供以后调用(main-makefile的第37行)。
第二步:make ARCH=arm CROSS_COMPILE=arm-linux- dep
include/linux/autoconf.h根据.config文件生成,其内容是若干#define和#undef宏。这些宏通知编译器哪些组件需要编译而哪些不需要,是编译进内核还是编译成模块。这两个文件中的变量是相互对应的。例如.config的第4行是:
CONFIG_ARM=y
这是编译进内核的情况。相应地,include/linux/autoconf.h的第5行是:
#define CONFIG_ARM 1
CONFIG_BINFMT_AOUT=m
#undef
#define CONFIG_BINFMT_AOUT_MODULE 1
接下来main-makefile调用split-include工具(scripts/split-include.c)(main-makefile 的第314行),分割解析autoconf.h并在include/config目录下生成相应的头文件。autoconf.h的路径通过第一个参数传递 给split-include(scripts/split-include.c的第71行)根据刚才提到的autoconf.h的第5行的内 容,split-include工具会在include/config/目录下生成arm.h文件,其内容就是一行:
#define CONFIG_ARM 1
include/linux/config.h的第4行嵌入了autoconf.h,而内核源码中很多c文件又嵌入这个config.h(例如 kernel/printk.c的第28行),于是间接地嵌入了autoconf.h,可见config.h起到的是一个桥梁的作用。
在这一步骤中,makefile会调用mkdep工具(scripts/mkdep.c)生成两种包含依赖关系的文件:.hdepend和.depend。
.hdepend只有一个,在主目录下,它表示相应的头文件又依赖于哪些其它的头文件。.hdepend是在main-makefile的第496行执行后生成的。如果存在.hdepend文件,Rules.make会嵌入它(Rules.make第295-297行)。
.depend文件有很多,它表示相应的目标文件依赖哪些源文件(*.c)和头文件(*.h)。主目录下的.depend文件是在main- makefile的第497行执行后生成的。mkdep读取*.c文件,分析出其所依赖的文件。下面以mm/page_alloc.c举例。 page_alloc.c的第15-23行为(“…”表示省略):
#include <linux/config.h>
#include <linux/mm.h>
…
#include <linux/module.h>
mkdep就会在mm/.depend中的第110-118行中写入:
page_alloc.o: page_alloc.c /
但接下来的第119行和上面的写法有些不同:
$(wildcard /usr/src/arm-linux/linux-roy/include/config/discontigmem.h) /
#ifndef CONFIG_DISCONTIGMEM
这需要判断discontigmem.h的存在性,而mm目录下的.depend文件的第119行正是起到了这样的作用(若存在则返回文件名,若不存在则返回空)。
第三步:make ARCH=arm CROSS_COMPILE=arm-linux- clean
目标clean在main-makefile的第444-449行定义,它所清除文件的文件和目录列表(CLEAN_FILES和CLEAN_DIRS)在203-229行被赋值。
第四步:make ARCH=arm CROSS_COMPILE=arm-linux- zImage
make zImage执行后会生成一个用gzip压缩过的内核。对于arm类型的CPU,目标zImage是在arch/arm/boot/Makefile的第 146行定义的。它依赖于compressed/vmlinux。而在第152行,又说明了compressed/vmlinux依赖于主目录下的 vmlinux。它的生成规则在main-makefile的第281-289行:
vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o /
init/do_mounts.o linuxsubdirs
ZLDFLAGS是在arch/arm/boot/Makefile的第14行定义的。如果我们指定CROSS_COMPILE 为arm-linux-,则LD为arm-linux-ld(main-makefile的第29行)。LINKFLAGS在arch/arm/Makefile的第10行定义:
LINKFLAGS
make从主目录Makefile开始执行,从中获得与体系结构无关的变量和依赖关系,并同时从arch-makefiles中获得体系特定的变量等信 息,这些信息继承并扩展了主目录Makefile提供的变量。先是所有通过“:=”赋值的变量在这个过程中被赋值,然后是所有通过“=”赋值的变量被赋值 (实际上类似于宏替换)。然后,main-makefile向下递归调用子目录中的Makefile,把部分变量传递给子目录Makefile。此时构建 内核需要的子目录Makefile根据配置信息决定编译哪些源文件,从而构建出一个需要编译的文件列表。在make zImage的时候make会递归进入列表subdir-y里的目录,根据其定义的编译规则决定这些文件的编译方式。然后,make会根据依赖关系树执行 命令,编译生成一系列的目标文件,最后根据vmlinux的生成规则(main-makefile的第281-289行),通过链接脚本把这些目标文件链 接成vmlinux(例如arch/arm/Makefile第267行)。
第五步:make ARCH=arm CROSS_COMPILE=arm-linux- modules
vmlinux:
三、几点说明
main-makefile的第37行是:
MAKEFILES
makemrproper用于重新构建内核,它做的清除工作比make clean更加彻底:除了做makeclean的工作外,还要删除.config,.depend等文件,把核心源码恢复到最原始的状态。它在main- makefile的第451-455行定义,它所清除文件的文件和目录列表(MRPROPER_FILES和MRPROPER_DIRS)在 232-256行被赋值。
递归的make 必须使用MAKE变量,而不是直接用make 命令。如main-makefile的第279行:
@$(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C arch/$(ARCH)/boot
当使用“-C”参数来make下层Makefile时,“-w”参数就会被自动打开,即在处理makefile
之前和之后,显示工作目录,所以在make的时候会看到屏幕上反复出现Entering directory[some dir]和Leaving directory [some dir]。
如果在编译的第一步用的是xconfig,则可以参见第302行的定义。在xconfig中,会使用wish程序调用scripts目录下的tk脚本形成图形界面供用户配置。
在编译的第二步中,为什么要通过mkdep和split-include工具,而不是通过gcc加上-MM参数来建立依赖关系呢?上文提到过,很多c源文 件都会通过linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依赖关系(.depend),只要更新过 autoconf.h,就会造成所有源代码的重新编译。为了优化make过程,减少不必要的重新编译,linux开发了专用的mkdep工具,用它来生 成.depend文件。配置内核时,split-include会检查旧的子文件的内容,确定是不是要更新它们。这样,不管autoconf.h修改日期 如何,只要其配置不变,make就不会重新编译内核。
Rules.make把当前目录下所有的目标文件称为O_TARGET,它的生成规则在第96-102行,最终生成一系列的.o文件;把当前目录下所有的 库文件称为L_TARGET,它的生成规则在第114-116行,最终生成一系列的.a文件。注意第62-65行、第104-107行、118-121 行、279-282行和304-317行实现了一种增量编译的机制。make每编译一个源文件时会生成一个.flags文件。例如编译mm/swap.c 时,会在相同的目录下生成.swap.o.flags文件。它本质上也是Makefile文件,其代码的功能是测试当前的编译选项与上次相比是否作过改 动,若改动过就将自己对应的目标文件加入FILES_FLAGS_UP_TO_DATE列表。生成这个文件是供下一次编译用的。下次编译时,当make进 入某个子目录,会搜索其中的.flags文件,如果存在则将它们嵌入(第309-312行)。Make会得到 FILES_FLAGS_UP_TO_DATE列表,它的内容是不需要更新的目标。于是,系统从编译对象表中删除它们,从而得到 FILES_FLAGS_CHANGED列表(第314-317行),这个列表的内容就是需要更新的目标。