Linux kernel中.o文件的编译过程

前言

在嵌入式Linux的开发过程中,内核编译是一个永远也绕不开的话题。

对内核编译系统的清晰把握,至少可以:

  • 了解整个内核的构造
  • 节省编译时间
  • 在编译报错时快速定位问题
  • 进一步了解内核的启动

本文从Linux kernel中.o文件的编译探索kbuild机制。

日期 内核版本 架构
2022-9-13 Linux5.4.200 arm

实验

目标log展开

我们以page_alloc.o的编译为例开始本次实验。

1
make mm/page_alloc.o

在kernel根目录下的Makefile中有:

https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1733

1
2
3
4
5
6
# Single targets
...
$(build-dirs): prepare
$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \
need-builtin=1 need-modorder=1

直接看不是很直观,kernel version 2.6的Makefile中语句较为清晰:

1
2
%.o: %.c prepare scripts FORCE
$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)

逻辑比较清晰,.o的文件依赖于同名的.c文件,然后执行一个命令来生成。

Tip:这里有个小技巧,注释掉构建语句,通过error log来逆向观察语句展开的结构

1
2
3
4
$(build-dirs): prepare
#$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(single-no-ko)),1) \
need-builtin=1 need-modorder=1

再次执行编译命令,则有:

1
2
3
#@make -f ./scripts/Makefile.build obj=mm \
single-build=1 \
need-builtin=1 need-modorder=1

从log可知,执行make mm/page_alloc.o又调用了一次make,使用script/Makefile.build这个规则文件,传入的参数是obj=mm。

build

由第一节的log我们知道$(build)展开后是:

1
-f ./scripts/Makefile.build obj

在哪定义的呢?全局搜索后,答案是scripts/Kbuild.include,类似头文件的东西。

https://elixir.bootlin.com/linux/v5.4.200/source/scripts/Kbuild.include#L160

1
2
3
4
5
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

好,将注意力转移到scripts/Makefile.build这个文件,寻找和编译.o相关的语句。

1
2
3
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $$(objtool_dep) FORCE
$(call if_changed_rule,cc_o_c) #调用if_changed_rull这个变量
$(call cmd,force_checksrc) #代码检查

重点关注$(call if_changed_rule,cc_o_c),这里调用了if_changed_rull变量,该变量同样定义在scripts/Kbuild.include

1
2
3
4
# Usage: $(call if_changed_rule,foo)
# Will check if $(cmd_foo) or any of the prerequisites changed,
# and if so will execute $(rule_foo).
if_changed_rule = $(if $(any-prereq)$(cmd-check),$(rule_$(1)),@:)

这是一条逻辑判断语句,当条件为真,执行逗号之前的动作。当条件为假,则执行后面的@:。这里的@:为的是减少一些log输出,具体可以看提交:kernel/git/torvalds/linux.git - Linux kernel source tree

而如果条件为真,rule_$(1)展开就是rule_cc_o_c

好,现在回到scripts/Makefile.build,寻找rule_cc_o_c的定义:

1
2
3
4
5
6
7
8
9
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,objtool)
$(call cmd,modversions_c)
$(call cmd,record_mcount)
endef

OK,离故事接近真相还差最后一步,$(call cmd_and_fixdep,cc_o_c)语句是我们重点关注的。同样是call函数,所以在此回到头文件scripts/Kbuild.include寻找定义:

1
2
3
cmd_and_fixdep =                          \
$(echo-cmd) $(cmd_$(1)); \
...

根据经验,再次展开就是cmd_cc_o_c了,它还是在scripts/Makefile.build文件定义。

1
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

终于,真相大白!

总结

.o文件的编译一共分为如下四个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  Makefile
---------------
1: %.o: %.c
make -f scripts/Makefile.build obj=mm

scripts/Makefile.build
---------------
2: $(obj)/%.o: $(src)/%.c
$(call if_changed_rule,cc_o_c)

scripts/Makefile.build
---------------
3: rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)

scripts/Makefile.build
---------------
4: cmd_cc_o_c
$(CC) $(c_flags) -c -o $@ $<

这里重点强调下前面反复提到的两个文件:

  • scripts/Makefile.build:包含了几乎所有的重要规则
  • scripts/Kbuild.include:类似于头文件,包含很多重要函数
-------------本文结束感谢您的阅读-------------

本文标题:Linux kernel中.o文件的编译过程

文章作者:孤岛violet

发布时间:2022年09月12日 - 21:43

最后更新:2022年09月13日 - 22:49

原始链接:http://yoursite.com/2022/09/12/compile-obj-file/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

undefined