Author: Wei Yang
作者公众号:杨小伟的世界
有编译过内核的话,一般都会看到在根目录下有个文件 vmlinux ,这个就是通常所说的内核了。但是用了这么久,倒是从来没看过是怎么编译出来的。那今天我们就通过它来试试我们这知识到底有没有掌握。
1 那些七大姑八大姨们
一切的一切都是 make 读取 makefile 编译链接的,就好像孙悟空逃不出如来佛祖的手掌, vmlinux 的出世也是在 makefile 的安排之下。那就现在看看 makefile 。
1 2 3 4 5 6 7 8 9 10 11 12 |
# SHELL used by kbuild CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) # Final link of vmlinux cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) quiet_cmd_link-vmlinux = LINK $@ vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE +$(call if_changed,link-vmlinux) |
第一次看完这一段差点一口老血吐出来,我真是没有想到他们尽然可以这么玩。。。还好我们已经经历过了之前的九九八十一难,见到这些也还能算是气定神闲了~
if_changed 也是在头文件 scripts/Kbuild.include 中定义,详细解释可以看 if_changed 。这个的用法也是类似之前看到的 if_changed_rule 是一个回调函数,当条件满足就运行 cmd_link-vmlinux 。 而这个 cmd_link-vmlinux 就是把第一个依赖作为脚本传给了系统使用的 shell ,由系统 shell 执行。好吧,我也是醉了,不过道行也就这么又深了一点点。你说是不是。
回到正题,这次我们先看看 vmlinux 相关依赖的都是谁。
vmlinux 一共依赖两个 一个是 vmlinux_prereq ,另一个是 vmlinux-deps 。那分头分析。
1.1 vmlinux_prereq
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Include targets which we want to execute sequentially if the rest of the # kernel build went well. If CONFIG_TRIM_UNUSED_KSYMS is set, this might be # evaluated more than once. PHONY += vmlinux_prereq vmlinux_prereq: $(vmlinux-deps) FORCE ifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_check endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif ifdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py endif ifdef CONFIG_TRIM_UNUSED_KSYMS $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \ "$(MAKE) KBUILD_MODULES=1 -f $(srctree)/Makefile vmlinux_prereq" endif |
这个看觉和最后的 vmlinux 关系不大, 咱暂时就不看了。
1.2 vmlinux-deps
1 2 |
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) |
vmlinux-deps 是这几个变量的集合,这下再次展开,我们一个个来看~
1.2.1 KBUILD_LDS —— 链接文件
1 2 |
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds |
这个好说,就是链接文件~好多秘密都在这里,暂且不表,之后再说。
1.2.2 KBUILD_VMLINUX_INIT
1 2 |
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) |
KBUILD_VMLINUX_INIT 又是两个变量的集合, head-y 和 init-y 。
1.2.2.1 head-y
这个 head-y 定义根据架构不同而不同,比如在 x86 架构下,该定义为:
1 2 3 4 |
head-y := arch/x86/kernel/head_$(BITS).o head-y += arch/x86/kernel/head$(BITS).o head-y += arch/x86/kernel/head.o |
恩,就一个架构,都还有三个文件,好多~忘了说了,这几个定义在 arch/x86/Makefile 中。那是怎么被根目录的 Makefile 引用到的呢?你猜?
在搜索的时候又发现这么一句在文档中。
$(head-y) lists objects to be linked first in vmlinux.
这个提醒了我一下,在链接的时候,文件出现的顺序是有影响的。
1.2.2.2 init-y
1 2 3 4 |
init-y := init/ init-y := $(patsubst %/, %/built-in.o, $(init-y)) |
init-y 有两种情况。
- 是 init/ 这个目录
- 是 init/built-in.o 这个文件。
在不同情况下展现出不同形式。而在 KBUILD_VMLINUX_INIT 这个变量中,保存的是 init/built-in.o 。
1.2.3 KBUILD_VMLINUX_MAIN
1 2 |
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y) $(virt-y) |
这个包含的内容比较多了啊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#core-y core-y := usr/ core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ core-y := $(patsubst %/, %/built-in.o, $(core-y)) #libs-y libs-y := lib/ libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) libs-y := $(libs-y1) $(libs-y2) #drivers-y drivers-y := drivers/ sound/ firmware/ drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) #net-y net-y := net/ net-y := $(patsubst %/, %/built-in.o, $(net-y)) #virt-y virt-y := virt/ virt-y := $(patsubst %/, %/built-in.o, $(virt-y)) |
这下,内核根目录下所有的子目录就都包含了~
除了lib目录下有两个文件, built-in.o 和 lib.a ,其余下都只有一个文件 built-in.o 。到这里你有没有感觉眼前一亮?有没有觉得这个 built-in.o 很眼熟?
1.2.3.1 VMLINUX_MAIN 中目标是如何的编译
可以看到 VMLINUX_MAIN 中包含的都是内核子系统的根目标了。我们可以猜到最终根目录下的 vmlinux 就是由这些根目标链接而成。那这些子系统的根目标是如何生成的呢?
过程有点绕,我们还是直接来看代码。
1 2 3 4 5 6 7 8 |
"Makefile" vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) # The actual objects are generated when descending, # make sure no implicit rule kicks in $(sort $(vmlinux-deps)): $(vmlinux-dirs) ; |
凡是在 vmlinux-deps 中的变量,都依赖于 vmlinux-dirs 变量的值。那就去看看 vmlinux-dirs 中的目标是什么规则咯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) # Handle descending into subdirectories listed in $(vmlinux-dirs) # Preset locale variables to speed up the build process. Limit locale # tweaks to this spot to avoid wrong language settings when running # make menuconfig etc. # Error messages still appears in the original language PHONY += $(vmlinux-dirs) $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ |
vmlinux-dirs 定义为目录。而他的规则是 $(Q)$(MAKE) $(build)=$@ 。按照在 scripts/Kbuild.include 中的定义,展开后就像下面这样。
make -f scripts/Makefile.build obj=$@
他的意思就是,各回各家,各找各妈,各自去各自的目录去执行编译吧~
2 谁是你们的粘合剂
看到了这么多内核子系统的根目标,现在该是时候把他们链接起来生成 vmlinux 了~
还记得 cmd_link-vmlinux 这个命令么?
1 2 |
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) |
这是 make 中定义的自动变量,表示依赖关系中的第一个依赖。在 vmlinux 规则中,第一个依赖是 scripts/link-vmlinux.sh 。所以接下来的链接工作,就靠它了。
2.1 vmlinux_link
这个脚本中除了链接 vmlinux 之外,还做了些其他的事情。我们暂且不表。还是来专注看看 vmlinux 是怎么链接的。
1 2 3 |
info LD vmlinux vmlinux_link "${kallsymso}" vmlinux |
然后又调用了 vmlinux_link 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# Link of vmlinux # ${1} - optional extra .o files # ${2} - output file vmlinux_link() { local lds="${objtree}/${KBUILD_LDS}" local objects if [ "${SRCARCH}" != "um" ]; then if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then objects="--whole-archive built-in.o ${1}" else objects="${KBUILD_VMLINUX_INIT} \ --start-group \ ${KBUILD_VMLINUX_MAIN} \ --end-group \ ${1}" fi ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \ -T ${lds} ${objects} else if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then objects="-Wl,--whole-archive built-in.o ${1}" else objects="${KBUILD_VMLINUX_INIT} \ -Wl,--start-group \ ${KBUILD_VMLINUX_MAIN} \ -Wl,--end-group \ ${1}" fi ${CC} ${CFLAGS_vmlinux} -o ${2} \ -Wl,-T,${lds} \ ${objects} \ -lutil -lrt -lpthread rm -f linux fi } |
截取其中关键的一点。
1 2 3 |
${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \ -T ${lds} ${objects} |
这不就是一个普通的链接么,再看看链接了谁? objects 嘛。那 objects 都是谁呢?就是我们刚才分析的 KBUILD_VMLINUX_INIT 和 KBUILD_VMLINUX_MAIN 呗~
嗯,没错就是它了。
3 一张图总结
最后画一张图吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
kernel/built-in.o certs/built-in.o mm/built-in.o fs/built-in.o ipc/buit-in.o security/built-in.o crypto/built-in.o block/built-in.o lib/built-in.o lib/lib.a head_64.o usr/built-in.o drivers/built-in.o head64.o sound/built-in.o firmware/built-in.o head.o init/built-in.o net/built-in.o virt/built-in.o || || || || $(head-y) $(init-y) $(core-y) $(drivers-y) $(net-y) $(libs-y) $(virt-y) | | | | | | | | \ / \ / \ / \ / KBUILD_VMLINUX_INIT KBUILD_VMLINUX_MAIN \ / \ / vmlinux |
嗯,丑是丑了点,不过意思都在了。
本文是 LinuxStory 柴米油盐计划的投稿文章。
由 LinuxStory 整理发布。
原作者署名为:Wei Yang 。
转载请注明出处,否则必究相关责任。
本文链接:https://linuxstory.org/kbuild-linux-7/
发表评论