本文将简要分析Linux Kernel编译zImage的过程。读者需具备GNU Make、Bash Shell、Python脚
本、编译器、链接器等方面的基础知识。虽然重点是分析kernel的构建过程,但是也会顺带的分析一些
其他的小的知识点。我们坐车去远行,欣赏沿途的风景,并不会妨碍我们最终抵达我们的目的地,不是
吗?
先描述一下具体开发环境:
. host os: ubuntu 14.04 server LTS
. cross toolchain: crosstool-ng生成的交叉编译器
. target: tegra, jeston-tk1
. kernel: 3.10.40
在开始分析之前,先做一些必要的说明:
. 分析过程中,所有文件路径,都是相对于kernel源码根目录的。
好了,接下来开始分析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,看到
如下代码片断:
图1
这里会先判断KBUILD_SRC的值是否为空,因为此时 KBUILD_SRC 尚未定义,所以这个判断是成的。
接着将KBUILD_OUTPUT赋为从命令行传过来的变量O的值,即:
KBUILD_OUTPUT := $(O) # KBUILD_OUTPUT := output
附带的变量saved-output也被赋成了同样的值output,然后将KBUILD_OUTPUT的值更新为output目录的完
整路径。KBUILD_OUTPUT是用来存储编译产生的输出的目录。
图2
这里是很重要的片段,是进入构建最终目标的开端。上面图2中的代码片段此时被展开为:
图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剩下的内容:
图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内容展开后的图3,zImage依赖于sub-make,所以sub-make这
条规则。我们来看sub-make规则的展开后的内容:
图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分析的核心内容。这里面的内容很多,我们
将逐段进行分析,对代码片段截取也是逐段截图。
图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了。继续往下看:
图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
这些变量的重要性,我想也不用我多说了吧。路漫漫其修远兮,革命尚未成功,我们继续赶路吧。
(未完待续)