从Linux内核2.6开始,Linux内核的编译采用Kbuild系统,这和过去的编译系统有很大的不同,尤其对于Linux内核模块的编译。在新的系统下,Linux编译系统会两次扫描Linux的Makefile:首先编译系统会读取Linux内核顶层的Makefile,然后根据读到的内容第二次读取Kbuild的Makefile来编译Linux内核。
Kbuild是建立在GUN make机制上的一种编译体系,理解KBuild体系,首先要理解GUN make,这也就是为什么要在前一节大谈特谈Makefile预备知识的原因。
那么Linux内核的KBuild体系中用到的Makefile分为5个部分:
(1)顶层Makefile
Kernel Makefile位于Linux内核源代码的顶层目录,也叫 Top Makefile。它主要用于指定编译Linux Kernel目标文件(vmlinux)和模块(module)。这编译内核或模块是,这个文件会被首先读取,并根据读到的内容配置编译环境变量。对于内核或驱动开发人员来说,这个文件几乎不用任何修改。
(2).config
内核配置文件,当配置完menuconfig以后,就会在主目录下生成一个.config文件,我们再来看看这个文件的部分内容:
……
CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
CONFIG_HAVE_CPUMASK_OF_CPU_MAP=y
……
(3)arch/$(ARCH)/Makefile
具体CPU架构的Makefile,位于ARCH/$(ARCH)/Makefile,是系统对应平台的Makefile。Kernel Top Makefile会包含这个文件来指定平台相关信息。只有平台开发人员会关心这个文件。
(4)scripts/Makefile.*
Kbuild使用到通用的规则等,面向所有的Kbuild Makefiles,包含了所有的定义、规则等信息。这些文件被用来编译基于kbuild Makefile的内核。
(5)kbuild Makefiles
内核源代码中大约有上百个这样的文件,一般是每个目录一个Makefile,同它对应的有一个Kconfig,其内容是一些默认的编译选项。每一个子目录都有一个Kbuild Makefile文件,用来执行从其上层目录传递下来的命令。注意,Kbuild Makefile并不直接被当做Makefile执行,而是从.config文件中提取信息,生成Kbuild完成内核编译所需的文件列表。
Kbuild Makefile的最核心的部分是内核目标的定义。主要是定义需要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。
例如,源代码某目录中有一个foo.c,要编译成一对象,那么该目录中的Kbuild Makefile其中必有一行形式如下:
obj-$(CONFIG_FOO) += foo.o
$(CONFIG_FOO)来自.config内核配置文件,可以为y(编译进内核) 或m(编译成模块)。如果CONFIG_FOO不是y和m,那么该文件就不会被编译联接了。
(1)编译进内核
Kbuild Makefile 规定所有编译进内核的目标文件都存在$(obj-y)列表中。而这些列表依赖内核的配置。
Kbuild编译所有的$(obj-y)文件。然后,调用"$(LD) -r"将它们合并到一个build-in.o文件中。稍后,该build-in.o会被顶层Makefile链接进vmlinux中。
注意,$(obj-y)中的文件是有顺序的。列表中有重复项是可以的,因为当第一个文件被联接到built-in.o中后,其余文件就被忽略了。
另外,链接也是有顺序的,那是因为有些函数(module_init()/__initcall)将会在启动时按照他们出现的顺序进行调用。所以,记住改变链接的顺序可能改变你系统顺序,从而导致你的硬件损害,这一点千万要注意。
(2)编译成模块
编译成模块和内核的区别不用我多说,因为模块是可装载,通过inmod等命令。$(obj-m) 列举出了哪些文件要编译成可装载模块。一个模块可以由一个文件或多个文件编译而成。如果是一个源文件,KbuildMakefile只需简单的将其加到$(obj-m)中去就可以了。
如果内核模块是由多个源文件编译而成,那你就要采用一个方法去声明你所要编译的模块:
obj-$(CONFIG_FOO) += isdn.o
解释一下这个方法,很简单,Kbuild需要知道你所编译的模块是基于哪些文件,所以你需要通过变量$(<module_name>-objs)来告诉它:
#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ FOO) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
上面的例子中,isdn.o就是模块名,Kbuild将编译在$(isdn-objs)中列出的所有文件,然后使用"$(LD) -r"生成isdn.o。
Kbuild能够识别用于组成目标文件的后缀-objs和后缀-y。这就让KbuildMakefile可以通过使用 CONFIG_ 符号来判断该对象是否是用来组合对象的:
#fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
在这个例子中,如果 $(CONFIG_EXT2_FS_XATTR) 是 'y',xattr.o将是复合对象 ext2.o的一部分。
注意:当然,当你要将其编译进内核时,上面的语法同样适用。所以,如果你的 CONFIG_EXT2_FS=y,那Kbuild会按你所期望的那样,生成 ext2.o文件,然后将其联接到 built-in.o中。
(3)编译成库文件
在 obj-* 中所列文件是用来编译成模块,或者是链接到特定目录中的 built-in.o以编译进内核。同样,也可以列出一些将被包含在lib.a库中的文件。在 lib-y 中所列出的文件用来组成该目录下的一个库文件。
在 obj-y 与 lib-y 中同时列出的文件,因为都是可以访问的,所以该文件是不会被包含在库文件中的。而同样的情况, lib-m 中的文件就要包含在 lib.a 库文件中。
注意,一个Kbuild makefile可以同时列出要编译进内核的文件与要编译成库的文件。所以,在一个目录里可以同时存在 built-in.o 与 lib.a 两个文件:
#arch/x86/lib/Makefile
lib-y := chechsum.o delay.o
这将由 checksum.o 和delay.o 两个文件创建一个库文件 lib.a。为了让Kbuild 真正认识到这里要有一个库文件 lib.a 要创建,其所在的目录要加到 libs-y 列表中。lib-y 使用一般限制在 lib/ 和 arch/*/lib 中。
(4)递归向下访问目录
一个Kbuild Makefile只对编译所在目录的对象负责。在子目录中的文件的编译要由其所在的子目录的Makefile来管理。只要你让Kbuild知道它应该递归操作,那么该系统就会在其子目录中自动的调用 make 递归操作。这就是 obj-y 和 obj-m 的作用。
例如,ext2 被放的一个单独的目录下,在fs目录下的Makefile会告诉Kbuild使用下面的赋值进行向下递归操作:
#fs/Makefile
obj-$(CONFIG_EXT2_FS) += ext2/
如果 CONFIG_EXT2_FS 被设置为 'y'(编译进内核)或是'm'(编译成模块),相应的 obj- 变量就会被设置,并且Kbuild就会递归向下访问 ext2 目录。Kbuild只是用这些信息来决定它是否需要访问该目录,而具体怎么编译由该目录中的Makefile来决定。
注意,将CONFIG_变量设置成目录名是一个好的编程习惯。这让Kbuild在完全忽略那些相应的CONFIG_值不是'y'和'm'的目录。