MEMORY | INTERRUPT | TIMER | 并发与同步 | 进程管理 | 调度 | uboot | DTB | ARMV8 | ATF | Kernel Data Structure | PHY | LINUX2.6 | 驱动合集 | UART子系统 | USB专题 |

内核源码中单个.o文件的编译过程(六)

通过对过渡篇的学习,相信你已经具有了相当的知识储备,接下来就来继续学习单个.o文件的编译过程

以/drivers/char/mem.c的编译为例

make  /drivers/char/mem.o

一. 找到目标及其构建规则*
在顶层目录的Makefile中:

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

这格式其实和我们平时自己写的规则是差不多的。.o的文件依赖于同名的.c文件,然后执行了一个命令来生成。不过呢,确实还多了些东西,包括一些特殊的依赖条件。

为了更直接的看懂这条规则,在这段规则前面添加一个“#”号

#$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)

运行一次make /drivers/char/mem.o 命令
在这里插入图片描述
从打印的命令来看,生成drivers/char/mem.o这个目标文件是重新又调用了一次make,而这次使用的是script/Makefile.build这个规则文件,传入的参数是drivers/char,目标还是drivers/char/mem.o。

三. 解析这条构建规则
1. build变量

# scripts/Kbuild.include

build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
# 其中$(KBUILD_SRC)常规情况下为空,所以变量的定义可简化为
build := -f scripts/Makefile.build obj

2. build-dir 和 target-dir变量

#Single targets
#---------------------------------------------------------------------------
#Single targets are compatible with:
#- build with mixed source and output
#- build with separate output dir 'make O=...'
#- external modules
#
#target-dir => where to store outputfile
#build-dir  => directory in kernel source tree to use

ifeq ($(KBUILD_EXTMOD),)
       build-dir  = $(patsubst %/,%,$(dir $@))
       target-dir = $(dir $@)
else
       zap-slash=$(filter-out .,$(patsubst %/,%,$(dir $@)))
       build-dir  = $(KBUILD_EXTMOD)$(if $(zap-slash),/$(zap-slash))
       target-dir = $(if $(KBUILD_EXTMOD),$(dir $<),$(dir $@))
endif

通过注释可以了解到,这两个变量一个是做编译的路径,一个是目标存放的路径。

定义本身分为两种情况

  • 当KBUILD_EXTMOD为空,表示不是编译内核模块
  • 当KBUILD_EXTMOD不为空,表示是在编译内核模块

我们这里并没有编译内核模块,所以KBUILD_EXTMOD为空,采用的是第一种情况的定义。从显示出的命令来看build-dir和target-dir分别定义为drivers/char 和drivers/char/。只是相差了最后的一个/。

3. 规则展开的最终形式

@make -f scripts/Makefile.build obj=drivers/char drivers/char/mem.o

四. scripts/Makefile.build是如何编译出drivers/char/mem.o的?

1. 找到目标drivers/char/mem.o的生成规则
注意不是使用顶层Makefile生成.o的规则,因为已经通过-f 指定了scripts/Makefile.build这个文件。在scripts/Makefile.build文件中找到*.o目标

# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c FORCE
	$(call cmd,force_checksrc)
	$(call if_changed_rule,cc_o_c)

依赖条件暂不关注,看多了会头痛,哈哈。这个规则中的两条命令:
第一条是做代码检查的,暂不关注。
第二条看着名字就有点像,cc_o_c,把c代码通过cc制作成.o文件。这个正是我们要关注的,就分析它了。

2. if_changed_rule函数
上一节,我们把关注的焦点定在了这条语句。

$(call if_changed_rule,cc_o_c)

其中if_changed_rule函数定义在Kbuild.include中,因为在顶层Makefile中包含了Kbuild.include这个文件

include $(srctree)/scripts/Kbuild.include

if_changed_rule函数的定义如下:

# 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 $(strip $(any-prereq) $(arg-check) ),                 \
	@set -e;                                                             \
	$(rule_$(1)))

根据本节前面的基础知识储备小节,你就会发现,这其实就是一个if语句。if_changed 函数在当发现规则的依赖有更新,或者是对应目标的命令行参数发生改变时($ (strip $ (any-prereq) $ (arg-check)) 语句结果不为空),执行后面的语句。set -e 表示如果命令执行有错那么命令停止执行并退出(指的是rule_ $ (1)这个命令)。
我们把注意力仍然集中在rule_$(1)。根据前面的基础知识储备篇,可以知道if_changed_rule函数对应的传入参数是cc_o_c。在这里展开后就是 rule_cc_o_c
这里有点像回调函数的感觉,if_changed_rule负责判断有没有前置条件是新的,是否需要重新生成目标。如果需要,if_changed_rule就会调用所要求的函数再去生成目标。

3. rule_cc_o_c
经过对if_changed_rule函数的分析,我们的目标又变成了rule_cc_o_c。现在的任务就是找到它。
rule_cc_o_c定义在scripts/Makefile.build中:

define rule_cc_o_c
	$(call echo-cmd,checksrc) $(cmd_checksrc)			  \
	$(call echo-cmd,cc_o_c) $(cmd_cc_o_c);				  \
	$(cmd_modversions)						  \
	$(call echo-cmd,record_mcount)					  \
	$(cmd_record_mcount)						  \
	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
	                                              $(dot-target).tmp;  \
	rm -f $(depfile);						  \
	mv -f $(dot-target).tmp $(dot-target).cmd
endef

又是一长串…。不过相信你经过前面的基础知识的学习,能够轻松应对它。经过对无用命令的剥离,剩下一个与我们相关的命令行:

	$(call echo-cmd,cc_o_c) $(cmd_cc_o_c);	

把$(call echo-cmd,cc_o_c) $(cmd_cc_o_c)展开得到:

# $(call echo-cmd,cc_o_c)展开:
echo 'CC  $@' ; CC  $@

执行make /drivers/char/mem.o命令时打印如下:
在这里插入图片描述
总结
linux内核中编译单个.o的执行顺序:

-----------------------------------------------------------------------------------
#  Makefile(顶层)
    %.o: %.c
        make -f scripts/Makefile.build obj=drivers/char drivers/char/mem.o
        
-----------------------------------------------------------------------------------
# scripts/Makefile.build
    $(obj)/%.o: $(src)/%.c
        $(call if_changed_rule,cc_o_c)
        
-----------------------------------------------------------------------------------
#scripts/Makefile.build 打印这条命令,并且执行命令
    rule_cc_o_c
		$(call echo-cmd,cc_o_c) $(cmd_cc_o_c)

-----------------------------------------------------------------------------------
#scripts/Makefile.build 执行命令
        cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

posted on 2022-11-02 22:23  BSP-路人甲  阅读(421)  评论(0编辑  收藏  举报

导航