使用kbuild构建bzImage内核映像

这里通过以下三个最经典的步骤来分析下,一个bzImage内核映像是如何配置编译,并最终安装使用的。当然在整个内核构建过程中,还支持许多的特性,相信这个经典过程明确之后,分析其他的情况就不会太难了,这里以x86体系来做分析。分析版本2.6.34.1

(1) make menuconfig
研究代码最好的方法就是手眼并用,看代码的同时多多动手调试,我们先贴出了它实际运行的过程:
[root@www linux-2.6.34.1]# make menuconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/basic/docproc
  HOSTCC  scripts/basic/hash
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/kxgettext.o
  HOSTCC  scripts/kconfig/lxdialog/checklist.o
  HOSTCC  scripts/kconfig/lxdialog/inputbox.o
  HOSTCC  scripts/kconfig/lxdialog/menubox.o
  HOSTCC  scripts/kconfig/lxdialog/textbox.o
  HOSTCC  scripts/kconfig/lxdialog/util.o
  HOSTCC  scripts/kconfig/lxdialog/yesno.o
  HOSTCC  scripts/kconfig/mconf.o
  SHIPPED scripts/kconfig/zconf.tab.c
  SHIPPED scripts/kconfig/lex.zconf.c
  SHIPPED scripts/kconfig/zconf.hash.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/mconf
scripts/kconfig/mconf arch/x86/Kconfig
当我们在内核顶层目录执行make menuconfig(假设你知道menuconfig的作用,这里就不多讲了)时,就指定了当前make的对象是menuconfig,那么我们到Makefile中去找到这个目标。注意的是,在众多kbuild系统分析的博客中,都会交代都诸多的细节,我写这个系列的目的主要是为了理出一条清晰的思路,而不去纠缠太多细枝末节的条件判断,当然在最后我会贴出一些比较好的,并且包含多细节的博客分享给大家。

由于我们的目标是menuconfig,在Makefile里面属于一种叫做config-targets的构建对象,在Makefile中会将“include $(srctree)/arch/$(SRCARCH)/Makefile”,即arch/x86/Makefile包含进来,include的动作是相当于把arch/x86/Makefile的内容直接复制到上述语句的所在的地方,成为了当前的Makefile的一部分,自然了,里面的变量也就不需要export出来了。
接下来有一块内容:
%config: scripts_basic outputmakefile FORCE
     $(Q)mkdir -p include/linux include/config
     $(Q)$(MAKE) $(build)=scripts/kconfig $@
这里%config是Makefile里面的通配表示,匹配以config结尾的所以字符,自然我们的menuconfig也是它匹配的类型。它的依赖包括script_basic, outputmakefile,剩下的FORCE,它的作用一句话概括,只要依赖条件中含有FORCE,目标总会被构建。接下来它的构建指令需要重点关注的是第二个(第一条就是创建两个目录),关于变量$(@),这里有详细的介绍。
这句指令中涉及到的$(build)在script/kbuild.include中被定义,它真实的样子是:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
所以通过这个build的定义,上面的那句话可以翻译为:make -f script/Makefile.build obj=script/kconfig menuconfig
实际的执行过程可以到script下的Makefile.build看到:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
这三句话就把script/kconfig/目录下的Makefile包含了进来,在这个Makefile中,定义了menuconfig的构建过程:
menuconfig: $(obj)/mconf
     $< $(Kconfig)
我们的讨论环境里,$(Kconfig)指向的是arch/x86/Kconfig文件,这个文件里有啥?很明了,$(obj)/mconf,即script/kconfig/目录下生成的mconf程序会读取arch/x86/Kconfig文件中的内容来生成我们熟悉的配置界面,配置结束之后,在顶层目录下会生成一个名为.config的隐藏文件,这是后续构建过程的重要依据和参考。

关于mconf的生成,前面贴的运行过程已经很清楚了,想了解跟多细节的同学,这里推荐一篇文章:走一走makefile menuconfig流程

依赖条件script_basic和outputmakefile又有怎样的前世今生呢?这里粗略的分析一下。
outputmakefile在我们make时通过”O=“指定了make输出目录才会执行有意义的动作,这里不是我们讨论的情况,先跳过。
script_basic我们可以在主Makefile中找到它:
scripts_basic:
     $(Q)$(MAKE) $(build)=scripts/basic
     $(Q)rm -f .tmp_quiet_recordmcount
主要的动作翻译过来就是:make -f script/Makefile.build obj=script/kconfig,由于没有指定构建对象(即$(build)=script/basic后面为空),所以make的目标就是针对script/Makefile.build的默认目标,很显然它就是“__build”。
这里我跳到重点的片段:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
     @:
这里需要说明的一点就是,在当前的讨论情景下只有always是含有实际对象的,其它的都为空,这里用到的的always是script/basic/Makefile中定义的,在开始时看到执行过程,则是通过Makefile.host产生了一些构建必要的工具,同时将这些工具生成指令写到一个.cmd结尾的文件里,如.docproc.cmd,.fixdep.cmd,.hash.cmd等。
关于@:,在这里有详细的说明。
这篇文章详细的描述了menuconfig的构建过程,上面我所写的这些,貌似重复和多余,但是对我个人而言,关于menuconfig,脑海里保存这些信息就足够了,细节就像是手册中的帮助文档,需要的时候查阅就可以了。
(2) make
直接make,那么就会执行makefile中的默认对象,这里就是vmlinux。
这里我们要提一点,在主Makefile中,include $(srctree)/arch/$(SRCARCH)/Makefile,也就是说将当前架构文件夹下的Makefile包含进来了。在该makfile中bzImage最终还是要以vmlinux为依赖,所以vmlinux是重点。那么vmlinux需要哪些依赖呢?我们列出来看:
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o)

vmlinux-dirs   := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
               $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
               $(net-y) $(net-m) $(libs-y) $(libs-m)))
vmlinux.o: $(modpost-init) $(vmlinux-main)
以上两个是他们各自的依赖,而下面的这几个则是赋值。
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
kallsyms.o := .tmp_kallsyms$(last_kallsyms).o #last_kallsyms是1,2,3等数值
注意这里vmlinux-dirs,后面出现了它的依赖:
$(vmlinux-dirs): prepare scripts 
到这里说句题外话,有很多前辈的工作,极大的缩短了我这样刚刚开始研究内核的人的学习曲线,想写点东西,前辈都写的很清楚了,这里就不想重复了。
这里给出云松大牛疯狂内核中的关于内核映像构建相关链接,很前面也很详细。这里主要的内容算是自己的一种总结和理解。
整理一下思路:
1. 各个目录下.o文件的编译生成
$(vmlinux-dirs): prepare scripts
     $(Q)$(MAKE) $(build)=$@
该命令的执行启动了.o文件的编译,由于vmlinux-dirs实际上就是各个目录的替身,在makefile.build中我们看到:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
这里的操作就是会依次包含进各个目录下的Makefile,来编译该目录下的对象,编译的实际过程则是:
$(obj)/%.o: $(src)/%.c FORCE
     $(call cmd,force_checksrc)
     $(call if_changed_rule,cc_o_c)
if_changed_rule和cc_o_c是什么?看这里
2. 目录下的.o文件生成built-in.o
下面的命令启动.o连接成built-in.o
$(builtin-target): $(obj-y) FORCE
     $(call if_changed,link_o_target)
这里$(obj-y)是啥?你随便点开一个目录下的Makefile,看看就知道了,其实就是各个.o的集合。
3. 生成vmlinux.o
直接看这里吧,呵呵。
4. vmlinux.lds的生成
.lds文件称为链接脚本,它的语法和作用,参考这里。它在最终链接生成vmlinux起着重要的作用。它的构建规则在Makefile.build中定义:
$(obj)/%.lds: $(src)/%.lds.S FORCE
     $(call if_changed_dep,cpp_lds_S)
5. 链接生成vmlinux
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
     $(call if_changed_rule,vmlinux__)
这里关于kallsyms.o,可以参考这里。当目前位置,各个目录下的.o都已构建完毕,通过$(call if_changed_rule,vmlinux__)
就可以在顶层目录下生成vmlinux了。
6. 生成bzImage
生成bzImage的工作集中在x86/boot目录和x86/compressed目录中,其中boot目录下的makefile会使用tools/build这个工具将当前目录中生成的setup.bin vmlinux.bin和启动设备号信息(即ROOT_DEV)“打包”成bzImage。
其中boot目录下的setup.ld会将目录下的所有的.o文件连接成setup.elf,然后使用objcopy处理得到setup.bin。
在compressed目录下的makefile会将misc.o,piggy.o,head_32.o或者head_64.o链接成vmlinux,这个vmlinux会在上层boot目录中被objcopy处理生成vmlinux.bin。这里有个奇怪的东西,就是piggy.o,这个东西实际上就是将顶层目录下生成的vmlinux进行了objcopy+gzip处理之后的最终结果。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章