内核映像的形成 —— KBuild体系(一)

引用:内核映像的形成 —— KBuild体系(一)


2.1 KBuild体系


从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完成内核编译所需的文件列表。

 2.1.1 内核目标

Kbuild Makefile的最核心的部分是内核目标的定义。主要是定义需要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。

例如,源代码某目录中有一个foo.c,要编译成一对象,那么该目录中的Kbuild Makefile其中必有一行形式如下:

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

$(CONFIG_FOO)来自.config内核配置文件,可以为y(编译进内核) m(编译成模块)。如果CONFIG_FOO不是y和m,那么该文件就不会被编译联接了。

例如

ifeq ($(CONFIG_FB_MSM_LCDC_PANEL_AUTO_DETECT),y)

obj-y += lcdc_hx8363a_wvga.o

...

else
obj-$(CONFIG_FB_MSM_LCDC_HX8363A_WVGA) += lcdc_hx8363a_wvga.o

....

ifeq ($(CONFIG_FB_MSM_LCDC_PANEL_AUTO_DETECT),y)表示AUTO_DETECT变量是否配置为y,是就执行第二行,obj-y  中的y表示编译进内核,而obj-$(CONFIG_FB_MSM_LCDC_HX8363A_WVGA)  中CONFIG_FB_MSM_LCDC_HX8363A_WVGA则表示一个变量,类似于我们C语言中的变量,用$( )来表示,它一般可以取三种值y ,m ,n.y表示编译进内核,而m则表示以模块的方式进行编译,n表示不编译进内核。obj-y    += 等号后面的.o后缀文件则是由该目录下的对应名称的.c文件编译而来。

而上面CONFIG_FB_MSM_LCDC_HX8363A_WVGA变量的取值则是通过.config文件来集中赋值的。


(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'的目录。


2.1.2 主机程序

在内核编译阶段,Kbuild 要先去编译那些将在编译阶段使用的可执行文件。为了使用该可执行文件,要将编译分成二个阶段:
第一阶段是告诉Kbuild存在哪些可执行文件。这是通过变量 hostprogs-y来完成的。
第二阶段是添加一个对可执行文件的显性依赖。有两种方法:增加依赖关系到一个规则中,或是利用变量 $(always)。以下是详细叙述:

 (1)简单的主机程序 

在编译内核时,有时会需要编译并运行一个程序。下面这行就告诉了kbuild,程序bin2hex用来在编译阶段被执行:
hostprogs-y := bin2hex 

在上面的例子中,Kbuild假设bin2hex是由一个与其在同一目录下,名为 bin2hex.c 的C语言源文件编译而成的。 

(2)复合的主机程序 

本机程序可以由多个文件编译而成。所使用的语法与内核的相应语法很相似。$(<executeable>-objs) 列出了联接成最后的可执行文件所需的所有目标文件:
#scripts/lxdialog/Makefile
hostprogs-y := lxdialog
lxdialog-objs := checklist.o lxdialog.o

 扩展名为.o的文件是从相应的.c文件编译而来的。在上面的例子中,checklist.c 编译成了checklist.o,lxdialog.c编译成了lxdialog.o。最后,两个.o文件联接成了一可执行文件,lxdialog。

 注意:语法 <executable>-y不是只能用来生成本机程序。

 (3)定义共享库 

扩展名为so的文件称为共享库,被编译成位置无关对象。Kbuild也支持共享库,但共享库的使用很有限。在下面的例子中,libconfig.so共享库用来联接到可执行文件 conf中:
#scripts/kconfig/Makefile
hostprogs-y := conf
conf-objs := conf.o libkconfig.so
libkcofig-objs := expr.o type.o 

共享库文件经常要求一个相应的 -objs,在上面的例子中,共享库libkconfig是由 expr.o 和 type.o两个文件组成的。expr.o 和 type.o 将被编译成位置无关码,然后联接成共享库文件 libkconfig.so。注意,KBuild还支持用C++写的主机程序,但C++并不支持共享库。


2.1.3 编译标志

KBuild体系还有一个重要的概念,那就是编译标志。我们可以在一些Makefile中看到如下标志:
EXTRA_CFLAGS、 EXTRA_AFLAGS、 EXTRA_LDFLAGS、EXTRA_ARFLAGS 

这些EXTRA_开头的大写字母变量都是编译标志,所有的 EXTRA_ 变量只在所定义的Kbuild Makefile中起作用。EXTRA_ 变量可以在Kbuild Makefile中所有命令中使用。

 $(EXTRA_CFLAGS) 是用 $(CC) 编译C源文件时的选项,例如:

# drivers/sound/emu10kl/Makefile
EXTRA_CFLAGS += -I$(obj)
ifdef DEBUG
EXTRA_CFLAGS += -DEMU10KL_DEBUG
endif

 由于顶层Makefile的$(CC)拥有变量 $(CFLAGS) 用来作为整个源代码树的编译选项,所以在这里做这么一个设置就是将$(CFLAGS)替换成EXTRA_CFLAGS。

 $(EXTRA_AFLAGS) 也是一个针对每个目录的选项,只不过它是用来编译汇编源代码的:

#arch/x86_64/kernel/Makefile
EXTRA_AFLAGS := -traditional 

$(EXTRA_LDFLAGS) 和 $(EXTRA_ARFLAGS)分别与 $(LD)和 $(AR)类似,只不过,他们是针对每个目录的:
#arch/m68k/fpsp040/Makefile
EXTRA_LDFLAGS := -x

 CFLAGS_$@, AFLSGA_$@

CFLAGS_$@ 和 AFLAGS_$@ 只能在当前Kbuild Makefile中的命令中使用。
$(CFLAGS_$@) 是 $(CC) 针对每个文件的选项,而不是目录。$@ 表明了具体操作的文件:
# drivers/scsi/Makefile
CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF
CFLAGS_gdth.o = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ /
-DGDTH_STATISTICS
CFLAGS_seagate.o = -DARBITRATE -DPARITY -DSEAGATE_USE_ASM 

以上三行分别设置了aha152x.o,gdth.o 和 seagate.o的编辑选项。

$(AFLAGS_$@) 也类似,只不是是针对汇编语言的。
# arch/arm/kernel/Makefile
AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional 

注意,Kbuild跟踪在以下方面依赖:
1) 所有要参与编译的文件(所有的.c 和.h文件)
2) 在参与编译文件中所要使用的 CONFIG_ 选项
3) 用于编译目标的命令行

因此,如果你改变了 $(CC) 的编译选项,所有受影响的文件都要重新编译。



你可能感兴趣的:(内核映像的形成 —— KBuild体系(一))