前言
如何编译Linux内核源码?| 量子孤岛一文中提到了,vmlinux是原始的,未经压缩的内核可执行文件,即kernel编出来的原始产物。
本文的主题只有一个:vmlinux到底是怎么生成的?
日期 | 内核版本 | 架构 |
---|---|---|
2022-9-14 | Linux5.4.200 | ARM64 |
按图索骥-内核子系统编译
kernel中编译的起点是根目录下的Makefile。
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1099
1 | # Final link of vmlinux with optional arch pass after final link |
由脚本可以看出,产物vmlinux的scripts/link-vmlinux.sh
有两个依赖:
autoksyms_recursive
:初步看可是ko有关的语法,暂时跳过。
1 | ifdef CONFIG_TRIM_UNUSED_KSYMS |
vmlinux-deps
重点看。
1 | vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS) |
依次展开看。
KBUILD_LDS
1 | export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds |
vmlinux.lds
是非常重要的链接文件,打算后面另起一文详细讲,这里暂不展开。
KBUILD_VMLINUX_OBJS
1 | # Externally visible symbols (used by link-vmlinux.sh) |
这里岛主对比了2.6版本的Makefile写法,5.4版本删掉了原来的KBUILD_VMLINUX_INIT:
1 | export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) |
而直接把$(head-y)
$(init-y)
追加到了KBUILD_VMLINUX_OBJS
后面。
为了一探究竟,我们再依次展开KBUILD_VMLINUX_OBJS
的各个组成部分.
head-y
:与架构相关,arm64架构定义在arch/arm64/Makefile
https://elixir.bootlin.com/linux/v5.4.200/source/arch/arm64/Makefile#L107
1 | # Default value |
init-y
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L637
1 | ifeq ($(KBUILD_EXTMOD),) |
按照语句执行顺序,这里init-y
展开后为init/built-in.o
继续展开剩余的:
core-y
1 | core-y := usr/ |
libs-y2
1 | libs-y := lib/ |
drivers-y
1 | drivers-y := drivers/ sound/ |
net-y
1 | net-y := net/ |
virt-y
1 | virt-y := virt/ |
通过观察不难发现:几乎所有的依赖条件中,都会生成一个built-in.o文件.
KBUILD_VMLINUX_LIBS
1 | export KBUILD_VMLINUX_LIBS := $(libs-y1) |
依次展开后也不难发现,调用了Linux中的各个子系统的根目录,想必vmlinux也是这些根目标链接而成的。
备注:
简单回忆一下patsubst.查找<text>
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换
1 | $(patsubst <pattern>,<replacement>,<text> ) |
我们再回到根目录下的Makefile中,target:vmlinux后面还有一些重要信息需要澄清:
1 | targets := vmlinux |
我们来慢慢分析。在vmlinux-deps
中的变量,都依赖于vmlinux-dirs
变量的值。而vmlinux-dirs
展开是众多的子目录,其目标规则最终定定位到$(Q)$(MAKE) $(build)=$@
。按照之前我们的已经掌握的先验知识,在scripts/Kbuild.include
展开后即
1 | make -f scripts/Makefile.build obj=$@ |
即依次回到各个子目录执行编译。
粘合剂
上面详细展开了内核子系统的编译流程。当各个子模块都编译完成,这时就需要工具将它们全部粘合起来制作成vmlinux
。
开篇的第一行代码还没聊,这回该它登场了!
1 | # Final link of vmlinux with optional arch pass after final link |
$<表示依赖关系中的第一个依赖,即scripts/link-vmlinux.sh
,这个脚本完成后续的链接工作。
来吧,看看scripts/link-vmlinux.sh
!
1 | #link vmlinux.o |
vmlinux_link
是一个link函数,其中:
1 | ${LD} ${KBUILD_LDFLAGS} ${LDFLAGS_vmlinux} \ |
这里ld
的objects就是前面分析的vmlinux-deps
.
总结
用一张图来汇总上面的分析。