本文将简要分析Linux Kernel编译zImage的过程。读者需具备GNU Make、Bash Shell、Python脚

本、编译器、链接器等方面的基础知识。虽然重点是分析kernel的构建过程,但是也会顺带的分析一些

其他的小的知识点。我们坐车去远行,欣赏沿途的风景,并不会妨碍我们最终抵达我们的目的地,不是

吗?Linux Kernel Makefile简析 之 make zImage_ Makefile


    先描述一下具体开发环境:

    . host os: ubuntu 14.04 server LTS

    . cross toolchain: crosstool-ng生成的交叉编译器

    . target: tegra, jeston-tk1

    . kernel: 3.10.40


    在开始分析之前,先做一些必要的说明:

    . 分析过程中,所有文件路径,都是相对于kernel源码根目录的;

    . 除顶层Makefile文件外,所有Makefile代码片段都会在第一行注释表明路径。


    好了,接下来开始分析zImage的编译过程。

    启动终端,在kernel源码根目录下运行命令. envsetup.sh设置编译环境。envsetup.sh的内容如

下:

#!/bin/bash
#
# filename: envsetup.sh

export ARCH=arm
export CROSS_COMPILE=~/tegra/tk1/crosstool/crosstool-ng/install/bin/arm-cortex_a9-linux-gnueabi-

    然后运行命令make zImage O=output编译kernel。make读取kernel源码根目录下的Makefile,看到

如下代码片断:

Linux Kernel Makefile简析 之 make zImage_Kernel_02

图1

    这里会先判断KBUILD_SRC的值是否为空,因为此时 KBUILD_SRC 尚未定义,所以这个判断是成的。

接着将KBUILD_OUTPUT赋为从命令行传过来的变量O的值,即:

KBUILD_OUTPUT := $(O) # KBUILD_OUTPUT := output

附带的变量saved-output也被赋成了同样的值output,然后将KBUILD_OUTPUT的值更新为output目录的完

整路径。KBUILD_OUTPUT是用来存储编译产生的输出的目录。

Linux Kernel Makefile简析 之 make zImage_Kernel_03

图2

这里是很重要的片段,是进入构建最终目标的开端。上面图2中的代码片段此时被展开为:

Linux Kernel Makefile简析 之 make zImage_ Makefile_04

图3

请注意,其中/path/to/output/current/path都不是真实的目录名,具体的值依赖于kernel源码根目录的实际路径。/current/path在此时的值为kernel源码根目录所在的路径。

    终于,我们看到了zImage这个我们编译的终极目标,它是依赖于sub-make的。但请记住,make在看

到zImage的产生规则以及其依赖目标的规则后,并不会马上去执行这两条规则,而只是先建立zImage和

sub-make的依赖关系。为什么会这样呢?原来make的执行过程是一个2遍扫描过程,这类似于一些编译器

的编译过程,并不是一次到位,而是分为几个阶段。make在第一遍扫描的时候,只是会去创建变量(并

展开某些类型变量)、建立目标依赖关系树,而只有在第二遍的时候才会去跟根据规则去进行目标的创

建过程。所以在这里zImage以及sub-make规则都不会被马上执行,而只是在make内部作了相应记录,而

后继续往后扫描Makefile剩下的内容。

    这里有一个很重要的变量skip-makefile,被赋值为1,这个变量和我们接下来的分析密切相关。我

们继续看这个Makefile剩下的内容:

Linux Kernel Makefile简析 之 make zImage_ Makefile_05

图4

上图中代码用...省略了很多内容,中间的内容非常重要且核心,但对我们当前的第1遍是会被跳过的,

因为此时skip-makefile的值为1,所以ifeq ($(skip-makefile),)是不成立的,其间包含的内容自然也

被跳过。 

    FORCE这条特殊的空规则,在kernel所有的Makefile中,随时可以看到它的身影,所以有必要简单的

释一下它的用途。FORCE常见于一条规则依赖列表的末尾,作用是保证依赖它的目标总是被更新。原因

FORCE不对应任何实际文件(.PHONY伪目标),且不依赖任何其标,make会认为这类目标总是要被更新

的,相应依赖它的目标自然也要被更新。

    打起精神,我们的重头戏马上要登场了。make完成对kernel顶层Makefile的第1编扫描后,接下来进

入make对Makefile处理的第2阶段,根据阶段1构建的依赖关系树开始一步步地构建我们的终极目标

zImage。起点是图2所示的位置,我们看图2内容展开后的图3zImage依赖于sub-make,所以sub-make

条规则。我们来看sub-make规则的展开后的内容:

Linux Kernel Makefile简析 之 make zImage_ Makefile_06

图5

这条规则到底做了什么呢?这条规则完成了4个动作:

. make将自己的当前目录切换到了/path/to/output

设置KBUILD_SRC为当前目录(即kernel源码根目录)

. make将重新读取kernel的顶层Makefile(也就是我们当前正在执行的Makefile)

. 构造目标依旧是zImage

    到这里你可能产生了疑问,怎么又回到了我们正在执行的Makefile呢?这不是死循环了,没完没了

了吗?事实并非如此。我们一步步来解释为什么。执行sub-make规则的语句后,make重新从头开始读取

kernel的顶层Makefile,时光流逝,make的脚步又重新走到了图1所示的代码片段处,这里的条件分支判

KBUILD_SRC的值是否为空。记得吗?就在刚刚,我们重新进入Makefile之前,已经设置了KBUILD_SRC

的值为kernel顶层Makefile所在的路径(也是kernel源码根目录所在的路径),所以这个对KBUILD_SRC判空的分支是不成立的,自然分支里面的语句也不会被执行,因此和第1遍执行这个Makefile时有了不同的执行路径。

    make继续前进到了图4所示的代码片段处,要根据skip-makefile的值决定我们接下来要去向哪里。

而这一次,skip-makefile并没有被定义,所以ifeq ($(skip-makefile),)的判定成立,因此make将执行

被该条件分支包含的代码片段,这将我们整个顶层Makefile分析的核心内容。这里面的内容很多,我们

将逐段进行分析,对代码片段截取也是逐段截图。

Linux Kernel Makefile简析 之 make zImage_ Makefile_07

图6

这段代码工作很简单,完成了几个变量的设置。在我的开发环境下,这些变量分别得到了如下的值(至

于如何得到的,就要看官们自己分析了):

srctree = /path/to/kernel/source/base
objtree = /path/to/kernel/source/base/output
src = /path/to/kernel/source/base
obj = /path/to/kernel/source/base/output
VPATH = /path/to/kernel/source/base
SUBARCH = x86

值得注意的是,srctree objtree VPATH被export了。继续往下看:

Linux Kernel Makefile简析 之 make zImage_Kernel_08

图7(上接图6)

这段代码定义了一些变量,将得到如下所列的值:

ARCH = arm
CROSS_COMPILE = ~/tegra/tk1/crosstool/crosstool-ng/install/bin/arm-cortex_a9-linux-gnueabi-
UTS_MACHINE = arm
SRCARCH = arm
hdr-arch = arm
KCONFIG_CONFIG = .config
CONFIG_SHELL = /bin/bash

这些变量的重要性,我想也不用我多说了吧。路漫漫其修远兮,革命尚未成功,我们继续赶路吧。

Linux Kernel Makefile简析 之 make zImage_ Makefile_09

Linux Kernel Makefile简析 之 make zImage_ Makefile_10

Linux Kernel Makefile简析 之 make zImage_ Makefile_11

Linux Kernel Makefile简析 之 make zImage_Kernel_12

Linux Kernel Makefile简析 之 make zImage_Kernel_13

Linux Kernel Makefile简析 之 make zImage_Kernel_14

Linux Kernel Makefile简析 之 make zImage_ Makefile_15

图8(上接图7)

上面的代码看起来内容很多,但仔细看起来,也只是做了一些变量的定义而已,如交叉编译工具链等。而

包含进来的文件Kbuild.include定义了一些常用的功能集合。

    Kbuild.include中有一个变量build是出现频率也很重要的一个变量定义:

build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj

它用来调用Makefile.build来进行模块工具等的编译,调用方式为make $(build)=xxx。

    接下来的内容要打起精神仔细看了:

Linux Kernel Makefile简析 之 make zImage_ Makefile_16

图9(上接图8)

这段代码判断最终的编译目标:是进行kernel配置,还是编译kernel和(或)module。上面的代码运行

后,变量的值在我们的make目标下,会得到如下值:

config-targets=0, mixed-targets=0, dot-config=1

    接下来的代码条件判断分支有点多,我们先理清楚分支逻辑结构,然后再深入到符合条件的分支里

面分析细节。

Linux Kernel Makefile简析 之 make zImage_Kernel_17

图10(上接图9)

根据上面得到变量mixed-targetsconfig-targets的值,ifeq ($(mixed-targets),1)的条件分支测试

是不满足条件的,所以make进入到上面代码中的第11行及后面的代码。接着第12行又有条件分支测试

ifeq ($(config-targets),1),这里是判断我们是否要进行kernel的配置,即make *config(make 

config, make menuconfig...)等。如果我们的目标是进行kernel配置,需要从这里开始分析代码。不过

我们当前的make目标是zImage,同时config-targets的值目前是0,所以make进入第33行省略号部分的代

码,这将是我们分析代码的终极目标。

    继续看代码:

Linux Kernel Makefile简析 之 make zImage_ Makefile_18

图11(上接图10中...部分)

上图代码上接图10中的...部分,逻辑也简单,不多解释。看接下来的代码:

Linux Kernel Makefile简析 之 make zImage_ Makefile_19

图12(上接图11)

看到了吗?上图中第21行的代码,进入了架构相关的Makefile,即arch/arm/Makefile。看代码:

Linux Kernel Makefile简析 之 make zImage_ Makefile_20Linux Kernel Makefile简析 之 make zImage_Kernel_21

图13(ARCH Makefile)

上面的代码定义了一些平台相关的编译选项配置、代码目录、目标规则等。第39~46确定了KBUILD_IMAGE

的值为zImage。第63行规则描述zImage依赖于vmlinux,那我们就要看vmlinux是怎样生成的,这样又回

到了我们顶层的Makefile:

Linux Kernel Makefile简析 之 make zImage_ Makefile_22

Linux Kernel Makefile简析 之 make zImage_ Makefile_23

Linux Kernel Makefile简析 之 make zImage_Kernel_24

Linux Kernel Makefile简析 之 make zImage_Kernel_25

Linux Kernel Makefile简析 之 make zImage_ Makefile_26

图14(上接图12)

上图中第62行给出了vmlinux的生成规则。该规则的最后1行:

+$(call if_changed,link-vmlinux)

展开为:

/bin/bash link-vmlinux $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

也就是调用scripts/link-vmlinux.sh链接生成vmlinux文件,当然这是生成vmlinux的最后一步。我们来

逐步vmlinux所依赖的文件的生成过程。

    可以看到vmlinux依赖于vmlinux-deps以及其他2个目标,其他2个目标的生成过程比较简单,我们着

重分析vmlinux-deps的依赖树。vmlinux-deps是顶层代码目录的列表,从上面代码第76行可以看到它又

依赖于vmlinux-dirs,依次的vmlinux-dirs又依赖于prepare scripts(见上面代码第85行),对于这些

vmlinux-dirs依赖的目标,比较简单,在此不做分析,而且后面的分析过程和这这些目标的生成规则也

有重叠的地方,理解了我们将要展开的分析,也不难理解这些。

    我们将场景切换到vmlinux-dirs所有依赖树已经展开完成,开始执行vmlinux-dirs规则的时刻:

Linux Kernel Makefile简析 之 make zImage_ Makefile_27

图15

这里将从kernel源码顶层的各个子目录,逐级递归的、深度优先的执行各级Kbuild文件或Makefile。我

们来看这一过程是如何展开的。

    以drivers目录为例,vmlinux-dirs规则展开为:

drivers: prepare scripts
    @make -f $(srctree)/scripts/Makefile.build obj=drivers

其中$(srctree)当前为kernel源码根目录。也就是make切换到scripts/Makefile.build文件:

Linux Kernel Makefile简析 之 make zImage_Kernel_28Linux Kernel Makefile简析 之 make zImage_ Makefile_29

Linux Kernel Makefile简析 之 make zImage_Kernel_30

图16

上面是截取自文件的scripts/Makefile.build的代码片段。上面代码39-42的4行,包含了当前进入的子

目录的Kbuild或者Makefile,在我们drivers目录的没有Kbuild文件,所以包含进来的是

drivers/Makefile文件:

Linux Kernel Makefile简析 之 make zImage_ Makefile_31

图17

可以看到,定义了obj-y变量(和)可能的obj-m变量。是否定义了obj-m变量,取决于CONFIG_XXX是否定

义为模块编译方式。接下来回到scripts/Makefile.build的第46行,它包含了scripts/Makfile.lib文

件:

Linux Kernel Makefile简析 之 make zImage_Kernel_32

Linux Kernel Makefile简析 之 make zImage_Kernel_33Linux Kernel Makefile简析 之 make zImage_ Makefile_34

图18

我们主要看第33~42,74对包含当前目录Makefile obj-y,obj-m变量的收集整理,最终汇集到变量

subdir-ym等之中。执行流程继续回到scripts/Makefile.build(图16)中的第62行,目标__build是make

当前目录执行时的最终目标。它依赖于builtin-target、subdir-ym等其他目标,其中builtin-target是

将当前目录下的所有代码编译整合成的一个名为built-in.o文件,这里完成了当前目录下的编译过程;

而subdir-ym是进一步递归编译的子目录列表,scripts/Makefile.build(图16)中的第95的规则是递归进

入子目录的地方,于是,又一次的执行scripts/Makefile.build,重复我们前面分析的过程,直到...直

到...所有的目录都编译完成,或生成编译所需工具,或生成built-in.o,等等。最终make的执行流程回

图14第62行的vmlinux的规则处,并执行该规则,链接生成vmlinux,也就是我们的kernel。

    千辛万苦,似乎已经走完了我们要走的路。可是,我们还有一些收尾的工作还没有完成,再坚持一

下吧。还记得吗?zImage才是我们的最中目标,从顶层Makefile继续推到架构特定的Makefile,即图13

中的第63行,这条规则在我们的执行情境下展开为:

zImage: vmlinux
@make -f $(srctree)/scripts/Makefile.build obj=arch/arm/boot MACHINE=tegra \
                  arch/arm/boot/zImage

又进入了scripts/Makefile.build,重复前面的分析过程,包含了arch/arm/boot/Makefile:

Linux Kernel Makefile简析 之 make zImage_ Makefile_35

图19

此时我们make的目标为arch/arm/boot/zImage,顺着依赖树,在一次objcopy动作后,然后执行第34的规

则,然后又一次的进入了Makefile.build,然后包含文件arch/arm/boot/compressed/Makefile编译我们

kernel的解压程序。看代码:

Linux Kernel Makefile简析 之 make zImage_Kernel_36

Linux Kernel Makefile简析 之 make zImage_Kernel_37

图20

注意上图中阴影部分代码,我用一个简单的加减公式来总结一下它们的执行过程:

arch/arm/boot/compressed/vmlinux = head.o + piggy.gzip.o + misc.o + decompress.o + 其他.o
vmlinux -> ../Image -> piggy.gzip -> piggy.gzip.o

注意,其中的两个vmlinux不是同一个文件,下面一行的vmlinux(kernel)是做为上面一行vmlinux的一部

分。在compressed目录下完成vmlinux对解压程序的打包以及kernel解压代码的编译后,生成了另一个

vmlinux文件,它的作用是完成对它包含的kernel进行解压并执行的任务。回到我们图19的Makfile文件

arch/arm/boot/Makefile,对arch/arm/boot/compressed/vmlinux进行一次objcopy操作,生成目标文件

arch/arm/boot/zImage。make再一次回退到图13中第62行的zImage规则处,终于我们走到了这次分析之

旅的终点,一切尘埃落定。


后记

  这篇文章,写起来很累。一方面,是自己功力不足,未能对Kernel的Makefile做到举重若轻,游刃

有余;另一方面,不可否认,Kernel的Makefile也确实有些复杂。能坚持看完的,自己也能分析出来这

些东西,而且相信大部分人比我会做得更好。对于你们这样的观众来讲,这篇文章可谓鸡肋。所以,写

这篇文章主要还是我自己的对所得的一次梳理,也具备备忘录的功能。如果浪费了您的时间,只能说一

句抱歉了Linux Kernel Makefile简析 之 make zImage_ Makefile_38