3.2.3 vmlinux的构建过程

所有的体系结构都需要构建vmlinux,所以vmlinux的构建规则在顶层的Makefile中。

3.2.3 vmlinux的构建过程 - 图1

注意,构建vmlinux的命令使用了make的内置函数call。这是一个比较特殊的内置函数,make使用它来引用用户自己定义的带有参数的函数。if_changed是kbuild定义的一个函数,这里通过call引用这个函数,传递的实参是link-vmlinux。函数if_changed的定义如下:

3.2.3 vmlinux的构建过程 - 图2

在ifchanged中,any-prereq检查是否有依赖比目标新,或者依赖还没有创建;arg-check检查编译目标的命令相对上次是否发生变化。如果两者中只要有一个发生改变,就执行if函数的if块。注意if块中的使用黑体标识的部分,其中“1”代表的就是传给if_changed的第一个实参。由此可见,if_changed核心功能就是当目标的依赖或者编译命令发生变化时,执行表达式"cmd$(1)"展开后的值。

这里,传给ifchanged的第一个实参是link-vmlinux,因此,cmd$(1)展开后为cmd_link-vmlinux。注意cmd_link-vmlinux中的第二项“$<”,这是make的自动变量,翻译自"Automatic Variable",意指变量名相同,但是make根据具体上下文,将其自动替换为合适的值。这里,make会将这个自动变量替换为构建vmlinux中的规则中的第一个依赖,即shell脚本文件scripts/link-vmlinux.sh,该脚本文件中负责vmlinux链接的脚本如下:

3.2.3 vmlinux的构建过程 - 图3

根据函数vmlinux_link的实现,如果平台不是"um",那么就调用链接器将变量KBUILD_VMLINUX_INIT、KBUILD_VMLINUX_MAIN中记录的目标文件链接为vmlinux。我们看看这两个变量的定义:

3.2.3 vmlinux的构建过程 - 图4

我们以core-y为例来分析变量KBUILD_VMLINUX_MAIN的值。

3.2.3 vmlinux的构建过程 - 图5

patsubst是make的内置函数,功能是在输入的文本中查找与模式匹配的字符串,然后使用特定字符串进行替换。具体到这里,其目的就是在变量core-y的值中将字符串“/”替换为"/built-in.o"。经过函数patsubst替换后,最后变量core-y的值如下:

3.2.3 vmlinux的构建过程 - 图6

除了各个子目录下的built-in.o,有些子目录(如lib)下还会编译lib.a。总之,vmlinux就是由这些目录下的built-in.o、lib.a等链接而成的。

那么这些子目录下面的目标文件built-in.o或者lib.a是在什么时机构建的呢?我们来回顾一下vmlinux的构建规则;

3.2.3 vmlinux的构建过程 - 图7

我们看到,除了依赖scripts/link-vmlinux.sh,vmlinux的另外一个依赖是vmlinux-deps,其构建规则也在顶层Makefile中定义:

3.2.3 vmlinux的构建过程 - 图8

我们首先看看变量vmlinux-deps,显然,其记录的就是我们前面讨论的最终链接为vmlinux的内核子目录下的目标文件的名字,如built-in.o等。也就说,vmlinux的构建规则表达得很清楚,要最后链接vmlinux,首先需要构建这些目标文件。但是注意目标vmlinux-deps的构建规则,其“规则体”是空的,也就是说这个构建规则下没有任何命令可执行,但是可以看到这些目标文件依赖于另外一个目标vmlinux-dirs,我们继续跟踪目标vmlinux-dirs的构建。

我们来关注一下变量vmlinux-dirs的值。注意该变量的赋值脚本,其中函数filter也是make的内置函数,其功能是过滤掉输入文本中不以“/”结尾的字符串。前面我们看到,输入到filter的这些变量,比如core-y,其中所有的子目录都以“/”结尾,因此,这里filter的目的是过滤掉这些变量中的非目录。patsubst这个make的内置函数我们刚刚讨论过,显然是将过滤出来的子目录后面的字符“/”去掉。因此,正如其名字所揭示的,变量vmlinux-dirs的值是多个目录,所以构建vmlinux-dirs的规则也是一个多目标规则,等价于:

3.2.3 vmlinux的构建过程 - 图9

规则中的命令展开后为:

3.2.3 vmlinux的构建过程 - 图10

其中“$@”是make的自动变量,表示规则的目标,所以这里会被make自动替换为构建的子目录,如init、kernel等,即相当于逐个编译这些子目录,使用的Makefile是Makefile.build。如3.2.1节讨论的那样,Makefile.build将包含构建目录中的Makefile或Kbuild,最终形成完整地Makefile。make命令中没有显式指定构建目标,因此,将构建Makefile.build中默认的目标。Makefile.build中的默认目标是__build,脚本如下所示:

3.2.3 vmlinux的构建过程 - 图11

目标build涵盖了内核映像和模块,这里我们只关注内核映像的构建,不关注模块的构建。对于编译内核映像来说,目标build依赖builtin-target、lib-target、extra-y、subdir-ym和always。我们先来看builtin-target和lib-target:

3.2.3 vmlinux的构建过程 - 图12

根据上述脚本片断可见,builtin-target代表的就是子目录下的built-in.o,lib-target代表的就是子目录下的lib.a。对于构建的子目录,如果变量obj-y等值非空,那么就构建built-in.o。如果变量lib-y等的值非空,那么就构建lib.a。我们来看看built-in.o和lib.a的构建:

3.2.3 vmlinux的构建过程 - 图13

前面已经讨论过函数if_changed,如果理解了这个函数,就很容易理解built-in.o和lib.a的构建过程了,对于built-in.o,就是调用链接器将变量obj-y中记录的各个目标文件链接为built-in.o。对于lib-target,就是调用创建静态库的程序AR将变量lib-y中的各个目标文件链接为lib.a。

编译内核时需要一些临时工具,比如我们前面用到的mkpiggy、build等,这些是一定需要编译的,因为构建内核时会用到。因此,kbuild中定义了一个变量always,其中记录的就是必须要编译的构建目标。

另外,可能有多层目录嵌套的情况,因此__build依赖列表中有这么一项:subdir-ym。目标subdir-ym的规则如下:

3.2.3 vmlinux的构建过程 - 图14

上面的代码看上去是不是很熟悉?没错,它和前面处理vmlinux-dirs的规则完全相同,显然,这是在处理目录中还有子目录的情况。

至此,链接vmlinux的目标文件经构建完成。回顾一下vmlinux的构建过程,kbuild将依次构建Makefile中指定的子目录,生成builtin.o、lib.a等目标文件,然后调用链接器将这些目标文件链接为vmlinux,并保存在顶层目录下。