Kbuild和Makefile.docx
《Kbuild和Makefile.docx》由会员分享,可在线阅读,更多相关《Kbuild和Makefile.docx(33页珍藏版)》请在冰豆网上搜索。
Kbuild和Makefile
KBUILD系统原理分析
kbuild,即kernelbuild,用于编译Linux内核文件。
kbuild对makefile进行了功能上的扩充,使其在编译内核文件时更加高效,简洁。
大部分内核中的Makefile都是使用Kbuild组织结构的kbuild Makefile。
下面将分两部分介绍,首先介绍Linux的命令工具make及其所操作的makefile,它负责将源代码编译成可执行文件;然后介绍kbuildmakefile对makefile做了哪些扩充,以及kbuildmakefile的工作原理。
Chapter1.MAKE概述
1.1准备知识
一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。
然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。
编译:
把高级语言书写的代码转换为机器可识别的机器指令。
编译高级语言后生成的指令虽然可被机器识别,但是还不能被执行。
编译时,编译器检查高级语言的语法、函数与变量的声明是否正确。
只有所有的语法正确、相关变量定义正确编译器就可以编译出中间目标文件。
通常,一个高级语言的源文件都可对应一个目标文件。
目标文件在Linux中默认后缀为“.o”(如“hello.c”的目标文件为“hello.o”)。
链接:
将多个.o文件,或者.o文件和库文件链接成为可被操作系统执行的可执行程序。
链接器不检查函数所在的源文件,只检查所有.o文件中的定义的符号。
将.o文件中使用的函数和其它.o或者库文件中的相关符号进行合并,最后生成一个可执行的程序。
“ld”是GNU的链接器。
静态库:
又称为文档文件(ArchiveFile)。
它是多个.o文件的集合。
Linux中静态库文件的后缀为“.a”。
静态库中的各个成员(.o文件)没有特殊的存在格式,仅仅是一个.o文件的集合。
使用“ar”工具维护和管理静态库。
共享库:
也是多个.o文件的集合,但是这些.o文件时有编译器按照一种特殊的方式生成。
对象模块的各个成员的地址(变量引用和函数调用)都是相对地址。
因此在程序运行时,可动态加载库文件和执行共享的模块(多个程序可以共享使用库中的某一个模块)。
1.2makefile简介
make在执行时,需要一个命名为Makefile的文件。
这个文件告诉make以何种方式编译源代码和链接程序,即告诉make需要做什么(完成什么任务),该怎么做。
典型地,可执行文件可由一些.o文件按照一定的顺序生成或者更新。
如果在你的工程中已经存在一个或者多个正确的Makefile。
当对工程中的若干源文件修改以后,需要根据修改来更新可执行文件或者库文件,正如前面提到的你只需要在shell下执行“make”。
make会自动根据修改情况完成源文件的对应.o文件的更新、库文件的更新、最终的可执行程序的更新。
make通过比较对应文件(规则的目标和依赖,)的最后修改时间,来决定哪些文件需要更新、那些文件不需要更新。
对需要更新的文件make就执行数据库中所记录的相应命令(在make读取Makefile以后会建立一个编译过程的描述数据库。
此数据库中记录了所有各个文件之间的相互关系,以及它们的关系描述)来重建它,对于不需要重建的文件make什么也不做。
而且可以通过make的命令行选项来指定需要重新编译的文件。
在执行make之前,需要一个命名为Makefile的特殊文件(本文的后续将使用Makefile作为这个特殊文件的文件名)来告诉make需要做什么(完成什么任务),该怎么做。
通常,make工具主要被用来进行工程编译和程序链接。
当使用make工具进行编译时,工程中以下几种文件在执行make时将会被编译(重新编译):
1.所有的源文件没有被编译过,则对各个C源文件进行编译并进行链接,生成最后的可执行程序;
2.每一个在上次执行make之后修改过的C源代码文件在本次执行make时将会被重新编译;
3.头文件在上一次执行make之后被修改。
则所有包含此头文件的C源文件在本次执行make时将会被重新编译。
后两种情况是make只将修改过的C源文件重新编译生成.o文件,对于没有修改的文件不进行任何工作。
重新编译过程中,任何一个源文件的修改将产生新的对应的.o文件,新的.o文件将和以前的已经存在、此次没有重新编译的.o文件重新连接生成最后的可执行程序。
首先让我们先来看一些Makefile相关的基本知识。
1.3Makefile规则介绍
一个简单的Makefile描述规则组成:
TARGET:
PREREQUISITES
[TAB]COMMAND
target:
规则的目标。
通常是程序中间或者最后需要生成的文件名。
可以是.o文件、也可以是最后的可执行程序的文件名。
另外,目标也可以是一个make执行的动作的名称,如目标“clean”,成这样的目标是“伪目标”。
prerequisites:
规则的依赖。
生成规则目标所需要的文件名列表。
通常一个目标依赖于一个或者多个文件。
command:
规则的命令行。
是make程序所有执行的动作(任意的shell命令或者可在shell下执行的程序)。
一个规则可以有多个命令行,每一条命令占一行。
注意:
每一个命令行必须以[Tab]字符开始,[Tab]字符告诉make此行是一个命令行。
make按照命令完成相应的动作。
这也是书写Makefile中容易产生,而且比较隐蔽的错误。
命令就是在任何一个目标的依赖文件发生变化后重建目标的动作描述。
一个目标可以没有依赖而只有动作(指定的命令)。
比如Makefile中的目标“clean”,此目标没有依赖,只有命令。
它所指定的命令用来删除make过程产生的中间文件(清理工作)。
在Makefile中“规则”就是描述在什么情况下、如何重建规则的目标文件,通常规则中包括了目标的依赖关系(目标的依赖文件)和重建目标的命令。
make执行重建目标的命令,来创建或者重建规则的目标(此目标文件也可以是触发这个规则的上一个规则中的依赖文件)。
规则包含了目标和依赖的关系以及更新目标所要求的命令。
1.4简单的示例
此例子由3个头文件和8个C文件组成。
写一个简单的Makefile,创建最终的可执行文件“edit”,此可执行文件依赖于8个C源文件和3个头文件。
Makefile文件的内容如下:
#sampleMakefile
edit:
main.okbd.ocommand.odisplay.o\
insert.osearch.ofiles.outils.o
cc-oeditmain.okbd.ocommand.odisplay.o\
insert.osearch.ofiles.outils.o
main.o:
main.cdefs.h
cc-cmain.c
kbd.o:
kbd.cdefs.hcommand.h
cc-ckbd.c
command.o:
command.cdefs.hcommand.h
cc-ccommand.c
display.o:
display.cdefs.hbuffer.h
cc-cdisplay.c
insert.o:
insert.cdefs.hbuffer.h
cc-cinsert.c
search.o:
search.cdefs.hbuffer.h
cc-csearch.c
files.o:
files.cdefs.hbuffer.hcommand.h
cc-cfiles.c
utils.o:
utils.cdefs.h
cc-cutils.c
clean:
rmeditmain.okbd.ocommand.odisplay.o\
insert.osearch.ofiles.outils.o
在书写时,一个较长行可以使用反斜线(\)分解为多行,这样做可以使Makefile清晰、容易阅读。
注意:
反斜线之后不能有空格(这也是大家最容易犯的错误,而且错误比较隐蔽)。
大家在书写Makefile时,推荐者中将较长行分解为使用反斜线连接得多个行的方式。
当我们完成了这个Maekfile以后;创建可执行程序“edit”,你所要做的就是在包含此Makefile的目录(当然也在代码所在的目录)下输入命令“make”。
删除已经本目录下生成的文件和所有的.o文件,只需要输入命令“makeclean”就可以了。
在这个Makefile中,目标(target)包含:
可执行文件“edit”和.o文件(main.o,kbd.o….),依赖(prerequisites)就是冒号后面的那些.c文件和.h文件。
所有的.o文件既是依赖(相对于可执行程序edit)又是目标(相对于.c和.h文件)。
命令包括“cc–cmaic.c”、“cc–ckbd.c”……
目标是一个文件时,当它的任何一个依赖文件被修改以后,这个目标文件将会被重新编译或者重新连接。
当然,此目标的任何一个依赖文件如果有必要则首先会被重新编译。
在这个例子中,“edit”的依赖为8个.o文件;而“main.o”的依赖文件为“main.c”和“defs.h”。
当“main.c”或者“defs.h”被修改以后,再次执行“make”时“main.o”就会被更新(其它的.o文件不会被更新),同时“main.o”的更新将会导致“edit”被更新。
在描述目标和依赖之下的shell命令行,它描述了如何更新目标文件。
命令行必需以[Tab]键开始,以和Makefile其他行区别。
就是说所有的命令行必需以[Tab]字符开始,但并不是所有的以[Tab]键出现行都是命令行。
但make程序会把出现在第一条规则之后的所有的以[Tab]字符开始的行都作为命令行来处理。
(要记住:
make程序不关心命令是如何工作的,对目标文件的更新需要你在规则的描述中提供正确的命令。
“make”程序所做的就是当目标程序需要更新时执行规则所定义的命令)。
目标“clean”不是一个文件,它仅仅代表了执行一个动作的标识。
通常情况下,不需要执行这个规则所定义的动作,因此目标“clean”没有出现在其它规则的依赖列表中。
在执行make时,它所指定的动作不会被执行。
除非执行make时明确地指定它作为重建目标。
而且目标“clean”没有任何依赖文件,它只有一个目的,就是通过这个目标名来执行它所定义的命令。
Makefile中把那些没有任何依赖只有执行动作的目标称为“伪目标”(phonytargets)。
执行“clean”目标所定义的命令,可在shell下输入:
makeclean。
1.5make如何工作
默认的情况下,make执行Makefile中的第一个规则,此规则的第一个目标称之为“最终目的”或者“终极目标”(就是一个Makefile最终需要更新或者创建的目标)。
上例的Makefile,目标“edit”在Makefile中是第一个目标,因此它就是make的“终极目标”。
当修改了任何C源文件或者头文件后,执行make将会重建终极目标“edit”。
当在shell提示符下输入“make”命令以后。
make读取当前目录下的Makefile文件,并将Makefile文件中的第一个目标作为其“终极目标”,开始处理第一个规则(终极目标所在的规则)。
在我们的例子中,第一个规则就是目标“edit”所在的规则。
规则描述了“edit”的依赖关系,并定义了链接.o文件生成目标“edit”的命令;make在处理这个规则之前,首先将处理目标“edit”的所有的依赖文件(例子中的那些.o文件)的更新规则;对包含这些.o文件的规则进行处理。
对.o文件所在的规则的处理有下列三种情况:
1.目标.o文件不存在,使用其描述规则创建它;
2.目标.o文件存在,目标.o文件所依赖的.c源文件、.h文件中的任何一个比目标.o文件“更新”(在上一次make之后被修改)。
则根据规则重新编译生成它;
3.目标.o文件存在,目标.o文件比它的任何一个依赖文件(的.c源文件、.h文件)“更新”(它的依赖文件在上一次make之后没有被修改),则什么也不做。
这些.o文件所在的规则之所以会被执行,是因为这些.o文件出现在“终极目标”的依赖列表中。
如果在Makefile中一个规则所描述的目标不是“终极目标”所依赖的(或者“终极目标”的依赖文件所依赖的),那么这个规则将不会被执行。
除非明确指定这个规则(可以通过make的命令行指定重建目标,那么这个目标所在的规则就会被执行,例如“makeclean”)。
在编译或者重新编译生成一个.o文件时,make同样会去寻找它的依赖文件的重建规则(是这样一个规则:
这个依赖文件在规则中作为目标出现),就是.c和.h文件的重建规则。
在上例的Makefile中没有哪个规则的目标是.c或者.h文件,所以没有重建.c和.h文件的规则。
完成了对.o文件的创建(第一次编译)或者更新之后,make程序将处理终极目标“edit”所在的规则,分为以下三种情况:
1.目标文件“edit”不存在,则执行规则创建目标“edit”。
2.目标文件“edit”存在,其依赖文件中有一个或者多个文件比它“更新”,则根据规则重新链接生成“edit”。
3.目标文件“edit”存在,它比它的任何一个依赖文件都“更新”,则什么也不做。
上例中,如果更改了源文件“insert.c”后执行make,“insert.o”将被更新,之后终极目标“edit”将会被重生成;如果我们修改了头文件“command.h”之后运行“make”,那么“kbd.o”、“command.o”和“files.o”将会被重新编译,之后同样终极目标“edit”也将被重新生成。
以上我们通过一个简单的例子,介绍了Makefile中目标和依赖的关系。
对于Makefile中的目标。
在执行“make”时首先执行终极目标所在的规则,接下来一层层地去寻找终极目标的依赖文件所在的规则并执行。
当终极目标的规则被完全的展开以后,make将从最后一个被展开的规则处开始执行,之后处理倒数第二个规则,……依次回退。
最后一步执行的就是终极目标所在的规则。
整个过程就类似于C语言中的递归实现一样。
在更新(或者创建)终极目标的过程中,如果出现错误make就立即报错并退出。
整个过程make只是负责执行规则,而对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不做任何判断。
就是说,一个规则的依赖关系是否正确、描述重建目标的规则命令行是否正确,make不做任何错误检查。
因此,需要正确的编译一个工程。
需要在提供给make程序的Makefile中来保证其依赖关系的正确性、和执行命令的正确性。
1.6总结
make的执行过程如下:
1.依次读取变量“MAKEFILES”定义的makefile文件列表
2.读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)
3.依次读取工作目录makefile文件中使用指示符“include”包含的文件
4.查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)
5.初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支
6.根据“终极目标”以及其他目标的依赖关系建立依赖关系链表
7.执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)
8.执行“终极目标”所在的规则
Chapter2.KBUILDMAKE原理介绍
2.1 概述
Linux内核的Makefile分为5个部分:
Makefile
顶层Makefile
.config
内核配置文件
arch/$(ARCH)/Makefile
具体架构的Makefile
scripts/Makefile.*
通用的规则等,面向所有的Kbuild Makefiles。
kbuild Makefiles
内核源代码中大约有500个这样的文件
顶层Makefile阅读的.config文件,而该文件是由内核配置程序生成的。
顶层Makefile负责制作:
vmlinux(内核文件)与模块(任何模块文件)。
制作的过程主要是通过递归向下访问子目录的形式完成。
并根据内核配置文件确定访问哪些子目录。
顶层Makefile要原封不动的包含一具体架构的Makefile,其名字类似于 arch/$(ARCH)/Makefile。
该架构Makefile向顶层Makefile提供其架构的特别信息。
每一个子目录都有一个Kbuild Makefile文件,用来执行从其上层目录传递下来的命令。
Kbuild Makefile从.config文件中提取信息,生成Kbuild完成内核编译所需的文件列表。
scripts/Makefile.*包含了所有的定义、规则等信息。
这些文件被用来编译基于kbuildMakefile的内核。
2.2 Kbuild文件
大部分内核中的Makefile都是使用Kbuild组织结构的Kbuild Makefile。
这章介绍了Kbuild Makefile的语法。
Kbuild文件倾向于"Makefile"这个名字,"Kbuild"也是可以用的。
但如果"Makefile""Kbuild"同时出现的话,使用的将会是"Kbuild"文件。
3.1节 目标定义是一个快速介绍,以后的几章会提供更详细的内容以及实例。
2.2.1 目标定义
目标定义是Kbuild Makefile的主要部分,也是核心部分。
主要是定义了要编译的文件,所有的选项,以及到哪些子目录去执行递归操作。
最简单的Kbuild makefile 只包含一行:
obj-y += foo.o
该例子告诉Kbuild在这目录里,有一个名为foo.o的目标文件。
foo.o将从foo.c或foo.S文件编译得到。
如果foo.o要编译成一模块,那就要用obj-m了。
所采用的形式如下:
obj-$(CONFIG_FOO) += foo.o
$(CONFIG_FOO)可以为y(编译进内核) 或m(编译成模块)。
如果CONFIG_FOO不是y和m,那么该文件就不会被编译联接了。
2.2.2 编译进内核 - obj-y
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)将会在启动时按照他们出现的顺序进行调用。
所以,记住改变联接的顺序可能改变你SCSI控制器的检测顺序,从而导致你的硬盘数据损害。
#drivers/isdn/i4l/Makefile
# Makefile for the kernel ISDN subsystem and device drivers.
# Each configuration option enables a list of files.
obj-$(CONFIG_ISDN)+= isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP)+= isdn_bsdcomp.o
2.2.3 编译可装载模块 - obj-m
$(obj-m) 列举出了哪些文件要编译成可装载模块。
一个模块可以由一个文件或多个文件编译而成。
如果是一个源文件,KbuildMakefile只需简单的将其加到$(obj-m)中去就可以了。
#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
注意:
此例中 $(CONFIG_ISDN_PPP_BSDCOMP) 的值为'm'
如果内核模块是由多个源文件编译而成,那你就要采用上面那个例子一样的方法去声明你所要编译的模块。
Kbuild需要知道你所编译的模块是基于哪些文件,所以你需要通过变量$(-objs)来告诉它。
#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN) += 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中。
2.2.4 目标库文件 - lib-y
在 obj-* 中所列文件是用来编译模块或者是联接到特定目录中的 built-in.o。
同样,也可以列出一些将被包含在lib.a库中的文件。
在 lib-y 中所列出的文件用来组成该目录下的一个库文件。
在 obj-y 与 lib-y 中同时列出的文件,因为都是可以访问的,所以该文件是不会被包含在库文件中的。
同样的情况, lib-m 中的文件就要包含在 lib.a 库文件中。
注意,一个Kbuild makefile可以同时列出要编译进内核的文件与要编译成库的文件。
所以,在一个目录里可以同时存在 built-in.o 与 lib.a 两个文件。
#arch/i386/lib/Makefile
lib-y:
= chechsum.o delay.o
这将由 checksum.o 和delay.o 两个文件创建一个库文件 lib.a。
为了让Kbuild 真正认识到这里要有一个库文件 lib.a 要创建,其所在的目录要加到 libs-y 列表中。
还可参考"6.3 递归下向时要访问的目录列表"lib-y 使用一般限制在 lib/ 和 arch/*/lib 中。
2.2.5递归向下访问目录
一个Makefile只对编译所在目录的对象负责。
在子目录中的文件的编译要由其所在的子目录的Makefile来管理。
只要你让Kbuild知道它应该递归操作,那么该系统就会在其子目录中自动的调用 make 递归操作。
这就是 obj-y 和 obj