KBUILD系统原理分析
kbuild,即kernel build,⽤于编译Linux内核⽂件。kbuild对makefile进⾏了功能上的扩充,使其在编译内核⽂件时更加⾼效,简洁。⼤部分内核中的Makefile都是使⽤Kbuild组织结构的kbuild Makefile。
下⾯将分两部分介绍,⾸先介绍Linux的命令⼯具make及其所操作的makefile,它负责将源代码编译成可执⾏⽂件;然后介绍kbuild makefile对makefile做了哪些扩充,以及kbuild makefile的⼯作原理。
Chapter 1. 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的链接器。
静态库:⼜称为⽂档⽂件(Archive File)。它是多个.o⽂件的集合。Linux中静态库⽂件的后缀为“.a”。静态库中的各个成员(.o⽂件)没有特殊的存在格式,仅仅是⼀个.o⽂件的集合。使⽤“ar”⼯具维护和管理静态库。
共享库:也是多个.o⽂件的集合,但是这些.o⽂件时有编译器按照⼀种特殊的⽅式⽣成。对象模块的各个成员的地址(变量引⽤和函数调⽤)都是相对地址。因此在程序运⾏时,可动态加载库⽂件和执⾏共享的模块(多个程序可以共享使⽤库中的某⼀个模块)。
makefile简介
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.3 Makefile规则介绍
⼀个简单的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⽂件的内容如下:
#sample Makefile
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
在书写时,⼀个较长⾏可以使⽤反斜线(\)分解为多⾏,这样做可以使Makefile清晰、容易阅读。注意:反斜线之后不能有空格(这也是⼤家最容易犯的错误,⽽且错误⽐较隐蔽)。⼤家在书写Makefile时,推荐者中将较长⾏分解为使⽤反斜线连接得多个⾏的⽅式。当我们完成了这个Maekfile以后;创建可执⾏程序“edit”,你所要做的就是在包含此Makefile的⽬录(当然也在代码所在的⽬录)下输⼊命
令“make”。删除已经本⽬录下⽣成的⽂件和所有的.o⽂件,只需要输⼊命令“make clean”就可以了。
在这个Makefile中,⽬标(target)包含:可执⾏⽂件“edit”和.o⽂件(main.o,kbd.o….),依赖(prerequisites)就是冒号后⾯的那些 .c ⽂件和 .h⽂件。所有的.o⽂件既是依赖(相对于可执⾏程序edit)⼜是⽬标(相对于.c和.h⽂件)。命令包括 “cc –c maic.c”、“cc –c kbd.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中把那些没有任何依赖只有执⾏动作的⽬标称为“伪⽬标”(phony targets)。执⾏“clean”⽬标所定义的命令,可在shell下输⼊:make clean。
1.5 make如何⼯作
默认的情况下,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的命令⾏指定重建⽬标,那么这个⽬标所在的规则就会被执⾏,例如 “make clean”)。在编译或者重新编译⽣成⼀个.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 phony
以上我们通过⼀个简单的例⼦,介绍了Makefile中⽬标和依赖的关系。对于Makefile中的⽬标。在执⾏“make”时⾸先执⾏终极⽬标所在的规则,接下来⼀层层地去寻终极⽬标的依赖⽂件所在的规则并执⾏。当终极⽬标的规则被完全的展开以后,make将从最后⼀个被展开的规则处开始执⾏,之后处理倒数第⼆个规则,……依次回退。最后⼀步执⾏的就是终极⽬标所在的规则。整个过程就类似于C语⾔中的递归实现⼀样。在更新(或者创建)终极⽬标的过程中,如果出现错误make就⽴即报错并退出。整个过程make只是负责执⾏规则,⽽对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不做任何判断。就是说,⼀个规则的依赖关系是否正确、描述重建⽬标的规则命令⾏是否正确,make不做任何错误检查。
因此,需要正确的编译⼀个⼯程。需要在提供给make程序的Makefile中来保证其依赖关系的正确性、和执⾏命令的正确性。
总结
make的执⾏过程如下:
1. 依次读取变量“MAKEFILES”定义的makefile⽂件列表
2. 读取⼯作⽬录下的makefile⽂件(根据命名的查顺序“GNUmakefile”,“makefile”,“Makefile”,⾸先到那个就读取那个)
3. 依次读取⼯作⽬录makefile⽂件中使⽤指⽰符“include”包含的⽂件
4. 查重建所有已读取的makefile⽂件的规则(如果存在⼀个⽬标是当前读取的某⼀个makefile⽂件,则执⾏此规则重建此makefile ⽂件,完成以后从第⼀步开始重新执⾏)
5. 初始化变量值并展开那些需要⽴即展开的变量和函数并根据预设条件确定执⾏分⽀
6. 根据“终极⽬标”以及其他⽬标的依赖关系建⽴依赖关系链表
7. 执⾏除“终极⽬标”以外的所有的⽬标的规则(规则中如果依赖⽂件中任⼀个⽂件的时间戳⽐⽬标⽂件新,则使⽤规则所定义的命令重建⽬标⽂件)
8. 执⾏“终极⽬标”所在的规则
Chapter 2. KBUILD MAKE原理介绍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.*包含了所有的定义、规则等信息。这些⽂件被⽤来编译基于kbuild Makefile的内核。
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中。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论