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

前言

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

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

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

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

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

实验

目标log展开

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

make mm/page_alloc.o

在kernel根目录下的Makefile中有:

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

# 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中语句较为清晰:

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

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

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

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

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

#@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)展开后是:

-f ./scripts/Makefile.build obj

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

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

###
# 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相关的语句。

$(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

# 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的定义:

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寻找定义:

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

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

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

终于,真相大白!

总结

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

    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:类似于头文件,包含很多重要函数

猜你喜欢

转载自blog.csdn.net/fly_wt/article/details/127187319