众所周知,内核的编译系统kbuild是个很庞大的系统。并且,它所使用的make和我们平常用的make是一模一样的。kbuild只是通过预定义一些变量(obj-m,obj-y等等)和目标(bzImage,menuconfig等等),使内核的编译和扩充显得非常便捷。我们不妨yy一下kbuild的一些功能:
1.考虑到Linux才能便捷地移植到各个硬件平台,kbuild也必须很容易添加对某个新的平台的支持,同时下层的Makefile不须要做大的改动。
2.Linux下有诸多驱动设备。它们的Makefile希望才能尽可能简练。简约到只要指定要编译的.o文件就行。(这方面kbuild定义了好多有用的变量如obj-mobj-y,-objs等等,用户只要为这种变量形参,kbuild会手动把代码编译到内核或则编译成模块)
3.要有便捷的可订制性。好多参数可以让用户指定。这方面kbuild也提供了大量的变量如EXTRA_CFLAGS,用户假如想include自己的头文件或则加其它编译参数,只要设置一下EXTRA_CFLAGS就可以。
4.有能力递归地调用Makefile。由于内核是一个庞大的软件。它的源代码的目录层次很深。要提供一种简约的机制,使下层的Makefile能便捷地调用上层的Makefile。在这过程中,面向对象的思想或许值得借鉴。
5.在配置内核时,要提供友好的用户界面。这方面kbuild也提供了不少工具linux系统好用吗,如常用的makemenuconfig等等。
我们完全可以把kbuild想像成一个例程,它为普通的内核开发人员提供了插口(obj-mobj-yEXTRA_CFLAGS等等),为用户提供了订制工具(makemenuconfig)
假如想了解kbuild的使用方式,可以参阅源代码自带的文档:
Documentation/kbuild/makefiles.txt
Documentation/kbuild/modules.txt
通常情况下是不须要晓得具体的编译次序的。不仅在某些情况下,如do_initcalls()中就和函数在.initcall.initsection中的次序有关。不过喜欢寻根究底的我,还是想理一下编译内核时几个常用的命令,如makebzImage,makemenuconfig等等,从而了解kbuild的构架。先看makebzImage吧。
它的大约脉络是如何的呢?可以用以下命令查看。
make-nbzImage
假如嫌内容太多,可以过滤掉多余的信息:
make-nbzImage|grep“make-f”
可以猜到:
先作一些打算工作
make-fscripts/Makefile.buildobj=scripts/basic
之后依次递归地调用源代码中的Makefile
make-fscripts/Makefile.buildobj=init
make-fscripts/Makefile.buildobj=usr
make-fscripts/Makefile.buildobj=arch/i386/kernel
make-fscripts/Makefile.buildobj=arch/i386/kernel/acpi
make-fscripts/Makefile.buildobj=arch/i386/kernel/cpu
make-fscripts/Makefile.buildobj=arch/i386/kernel/cpu/cpufreq
make-fscripts/Makefile.buildobj=arch/i386/kernel/cpu/mcheck
make-fscripts/Makefile.buildobj=arch/i386/kernel/cpu/mtrr
make-fscripts/Makefile.buildobj=arch/i386/kernel/timers
。。。
最后压缩内核linux防火墙设置,生成bzImage
make-fscripts/Makefile.buildobj=arch/i386/bootarch/i386/boot/bzImage
make-fscripts/Makefile.buildobj=arch/i386/boot/compressedIMAGE_OFFSET=0x100000arch/i386/boot/compressed/vmlinux
好,我们从头开始。找makebzImage的入口:
第一反应,自然是在/usr/src/linux/Makefile中找
bzImage:
...
可惜没找到。
不过没关系,用lxr搜索一下,可知bzImage定义在arch/i386/Makefile,所以可以推测,该makefile一定是被include了。果然,在/usr/src/linux/Makefile中有:
447includeS(srctree)/arch/S(ARCH)/Makefile
又由于在arch/i386/Makefile中定义有
141zImagebzImage:vmlinux
142S(Q)S(MAKE)S(build)=S(boot)S(KBUILD_IMAGE)
其中这个S(build)定义在/usr/src/linux/Makefile中
1335build:=-fS(ifS(KBUILD_SRC),S(srctree)/)scripts/Makefile.buildobj
我们在之前查看make-nbzImage信息和以后会常常听到。我们会发觉kbuild一般不会直接去调用某个目录下的Makefile,而是让该目录作为scripts/Makefile.build的参数。scripts/Makefile.build会对该目录下的Makefile中的内容(主要是obj-m和obj-y等等)进行处理。由此看来scripts/Makefile.build这个文件很重要。瞧瞧它做了哪些:
因为scripts/Makefile.build前面没跟目标,所以默认为第一个目标:
007.PHONY:__build
008__build:
009
010#Read.configifitexist,otherwiseignore
011-include.config
012
013includeS(ifS(wildcardS(obj)/Kbuild),S(obj)/Kbuild,S(obj)/Makefile)
014
015includescripts/Makefile.lib
这儿可以看见,scripts/Makefile.build执行时会include.config文件。.config是makemenuconfig后生成的内核配置文件。
上面有如下句子:
CONFIG_ACPI_THERMAL=y
CONFIG_ACPI_ASUS=m
CONFIG_ACPI_IBM=m
。。。
曾经我仍然对它的格式表示奇怪,如今很清楚了,它们是作为makefile的一部份,通过读取CONFIG_XXX的值就可以晓得她们是作为模块还是作为内核的一部份而编译的。
据悉,还包含了S(obj)/Makefile。这就是通过在make时传递目录名S(obj)间接调用Makefile的手法。对于内核普通代码所对应的Makefile而言,上面只是对obj-mobj-y,-objs等变量进行形参操作。
接下去是includescripts/Makefile.lib
。正如它的文件名所示,这类似于一个库文件。它负责对obj-mobj-y,-objs等变量进行加工处理。从中提取出subdir-ym等变量,这是个很重要的变量,记录了须要递归调用的子目录。之后递归调用Makefile全靠它了。这儿也充分彰显了GNUmake对字符串进行操作的强悍功能。
回到Makefile.build。这时,重要变量S(builtin-target),S(subdir-ym)等都早已估算完毕。开始列依赖关系和具体操作了。
079__build:S(ifS(KBUILD_BUILTIN),S(builtin-target)S(lib-target)S(extra-y))
080S(ifS(KBUILD_MODULES),S(obj-m))
081S(subdir-ym)S(always)
082@:
S(builtin-target)是指当前目录下的目标文件,即S(obj)/built-in.o
如前文所说,S(subdir-ym)拿来递归调用子目录的Makefile
306#Descending
307#---------------------------------------------------------------------------
308
309.PHONY:S(subdir-ym)
310S(subdir-ym):
311S(Q)S(MAKE)S(build)=S@
通过这些方法,实现了对某个目录及其子目录的编译。
剖析完Makefile.build,回过头来再看bzImage.从arch/i386/Makefile中可以见到,bzImage是在vmlinux基础上加以压缩拼接而成。从vmlinux到bzImage的过程在《读核体会-
Linux内核
启动-内核的生成》中早已有介绍。现今瞧瞧vmlinux是怎样生成的。
见/usr/src/linux/Makefile
728vmlinux:S(vmlinux-lds)S(vmlinux-init)S(vmlinux-main)S(kallsyms.o)FORCE
729S(callif_changed_rule,vmlinux__)
611vmlinux-init:=S(head-y)S(init-y)
612vmlinux-main:=S(core-y)S(libs-y)S(drivers-y)S(net-y)
613vmlinux-all:=S(vmlinux-init)S(vmlinux-main)
614vmlinux-lds:=arch/S(ARCH)/kernel/vmlinux.lds
vmlinux所依赖的目标S(vmlinux-lds)是对arch/i386/kernel/vmlinux.lds.S进行预处理的结果:arch/i386/kernel/vmlinux.lds,其它的依赖关系也都可以在/usr/src/linux/Makefile中查到。
所以,当用户在源代码目录下执行makebzImage。make会检测bzImage的依赖目标,之后不停地递归调用各个Makefile,最终生成一个bzImage文件。
假如我们换个角度,还可以归纳出不少有趣的东西。假如把make看成是一种脚本语言,这么Makefile就是代码。make就是协程。make里也有函数linux内核启动流程,也有变量。通过定义目标,可以实现类似于函数的疗效。而目标之间的依赖关系则类似于函数内部再调用其它函数。
假如我们考虑变量的作用域linux内核启动流程,还可以归纳出以下几点:
1.Makefile内部定义的变量作用域只限于那种Makefile中,如obj-m。
2.要使变量的作用域扩充到整个make命令的执行过程(包括递归调用的其它Makefile),须要使用export命令。
调用Makefile的形式也有好多种:
1.一种是隐式调用,如运行make,它会手动在当前目录找寻Makefile等。
2.一种是显式调用,如用make-f指定。
3.一种是用include来调用。