LV085-single-modules目标
后面我们再内核外编译驱动,虽说是在内核外,但是也还是会用到 kernel 中的单一模块的编译命令,这里我们了解一下模块是怎么编译的。
一、概述
1. 帮助信息
我们看一下 Makefile - help:
PHONY += help
help:
#......
@echo '* modules - Build all modules'
@echo ' modules_install - Install all modules to INSTALL_MOD_PATH (default: /)'
#......
@echo ' dir/file.ko - Build module including final link'
@echo ' modules_prepare - Set up for building external modules'
# ......2. 模块编译命令
从帮助信息中可以知道,我们可以通过 modules 等相关命令编译模块:
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j16 # 编译所有的模块
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dir/file.ko -j16 # 编译指定模块二、函数说明
这里先来了解几个函数,后面会经常用到。
1. cmd 函数
cmd 函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# printing commands
cmd = @$(echo-cmd) $(cmd_$(1))主要是它包含了 echo-cmd,后面会经常看到,这里就单独分析一下吧。quiet 就是一个前缀,make V=1 的话,quiet 为空,make V=0,quiet =@。
1.1 $(echo-cmd)
先看这个 echo-cmd,从字面理解,应该是回显命令,而且初看也是打印命令到终端。
1.1.1 escsq
先来看一下 escsq 函数,它定义在 Kbuild.include - scripts/Kbuild.include - escsq:
###
# Escape single quote for use in echo statements
escsq = $(subst $(squote),'\$(squote)',$1)这个 subst 函数是字符串替换函数,包含三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作用的字串。用法如下:
$(subst <from>,<to>,<text>)作用就是在 text 中将 from 替换成 to。所以这里的含义就是在传入的参数 $1 中将 $(squote) 替换成 \$(squote),这个 squote 定义在 Kbuild.include - scripts/Kbuild.include - squote:
squote := '所以将 escsq 展开就是:
escsq = $(subst ','\'',$1)就是将 $1 中的 ' 替换成 \'。
1.1.2 echo-why
再来看一下这个 echo-why,它定义在 Kbuild.include - scripts/Kbuild.include - echo-why:
###
# why - tell why a target got built
# enabled by make V=2
# Output (listed in the order they are checked):
# (1) - due to target is PHONY
# (2) - due to target missing
# (3) - due to: file1.h file2.h
# (4) - due to command line change
# (5) - due to missing .cmd file
# (6) - due to target not in $(targets)
# (1) PHONY targets are always build
# (2) No target, so we better build it
# (3) Prerequisite is newer than target
# (4) The command line stored in the file named dir/.target.cmd
# differed from actual command line. This happens when compiler
# options changes
# (5) No dir/.target.cmd file (used to store command line)
# (6) No dir/.target.cmd file and target not listed in $(targets)
# This is a good hint that there is a bug in the kbuild file
ifeq ($(KBUILD_VERBOSE),2)
why = \
$(if $(filter $@, $(PHONY)),- due to target is PHONY, \
$(if $(wildcard $@), \
$(if $(strip $(any-prereq)),- due to: $(any-prereq), \
$(if $(arg-check), \
$(if $(cmd_$@),- due to command line change, \
$(if $(filter $@, $(targets)), \
- due to missing .cmd file, \
- due to $(notdir $@) not in $$(targets) \
) \
) \
) \
), \
- due to target missing \
) \
)
echo-why = $(call escsq, $(strip $(why)))这个还挺复杂的,但是最开始有注释,enabled by make V=2,所以这里我们直接忽略,以后用到再说吧。
1.1.3 总结
我们简化一下 echo-cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))';)其实就只是把 echo-why 去掉。
1.2 $(cmd_$(1))
这个其实没什么说的,$(1) 就是传入的参数,替换后就是一个变量。例如 $(call cmd,param),这里·$(1) = param,所以就是 $(cmd_param),随后找到 cmd_param 变量即可。
1.3 cmd 到底在做什么?
前面大概分析了两个变量,这里我们写一个 demo 来测试一下:
squote := '
quiet :=
escsq = $(subst $(squote),'\$(squote)',$1)
echo-cmd = $(echo ' $(call escsq,ls)';)
cmd_sumucmd = ls
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))';)
# printing commands
cmd = @$(echo-cmd) $(cmd_$(1))
test: FORCE
$(call cmd,sumucmd)
$(echo-cmd) pwd
PHONY+=FORCE
FORCE:
.PHONY: $(PHONY)然后执行,就会发现,$(echo-cmd) 部分负责显示命令,$(cmd_$(1)) 负责执行命令:

2. wanring 函数
接下来看一个 makefile 的控制函数,warning,会打印当前行号和自定义文本:
$(warning TEXT...)不能直接用 echo 吗?当然也可以,但是 echo 只能在 target:后面的语句中使用,且前面是个 TAB。这个的话,就哪里都能用。这个 warning 命令也可以打印变量,和 echo 一样。
$(warning "a=$a")三、make dir/file.ko
我们来了解一下编译指定模块,先从简单的来。这里以内核中的 drivers/media/i2c/ov5640.ko 这个模块为例。
1. %.ko 目标
我们从执行的命令 make dir/file.ko 中推测,在顶层 makefile 中应该存在对应的目标,我们去搜一下,会看到这个目标 Makefile - %ko:
%.ko: prepare scripts FORCE
$(cmd_crmodverdir)
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
$(build)=$(build-dir) $(@:.ko=.o)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost我们编译的命令为:
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16所以这里的 %.ko 匹配的就是 drivers/media/i2c/ov5640.ko。
1.1 cmd_crmodverdir
cmd_crmodverdir 定义在 Makefile - cmd_crmodverdir:
# Create temporary dir for module support files
# clean it up only when building all modules
cmd_crmodverdir = $(Q)mkdir -p $(MODVERDIR) \
$(if $(KBUILD_MODULES),; rm -f $(MODVERDIR)/*)这里就不详细去找定义在哪里了,直接在%.ko 目标的规则中加点打印信息看一下:
%.ko: prepare scripts FORCE
echo "MODVERDIR=$(MODVERDIR) KBUILD_MODULES=$(KBUILD_MODULES)"
$(cmd_crmodverdir)
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
$(build)=$(build-dir) $(@:.ko=.o)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost然后执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16 # 编译指定模块会看到以下打印信息:
echo "MODVERDIR=.tmp_versions KBUILD_MODULES="
MODVERDIR=.tmp_versions KBUILD_MODULES=所以 cmd_crmodverdir 展开就是:
cmd_crmodverdir = $(Q)mkdir -p .tmp_versions \
$(if $(KBUILD_MODULES),; rm -f .tmp_versions/*)if 函数的用法如下:
$(if CONDITION,THEN-PART[,ELSE-PART])if 函数的第一个参数 CONDITION 表示条件判断,展开后如果非空,则条件为真,执行 THEN-PART 部分;否则,如果有 ELSE-PART 部分,则执行 ELSE-PART 部分。
这里因为 KBUILD_MODULES 为空,所以这里应该执行 ELSE-PART 部分,但是命令中无 ELSE-PART 部分,所以这里就没有内容了,所以最后
cmd_crmodverdir = $(Q)mkdir -p .tmp_versions这个变量最后就是在创建.tmp_versions 目录了。
1.2 KBUILD_MODULES
我们继续往下看 KBUILD_MODULES,它的值由 CONFIG_MODULES 决定:
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \这个我们可以使用如下命令在配置后的 linux 内核源码中搜索一下,会在顶层源码目录中生成的.config 文件中或者 imx_v6_v7_defconfig - arch/arm/configs/imx_v6_v7_defconfig - CONFIG_MODULES 中看到:
CONFIG_MODULES=y所以 KBUILD_MODULES = 1。
1.3 build、build-dir
我们再看一下这个 build 这一行:
$(build)=$(build-dir) $(@:.ko=.o)1.3.1 build
build 定义在 Kbuild.include - scripts/Kbuild.include - build:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj1.3.2 build-dir
build-dir 定义在 Makefile - build-dir
# 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,它定义在 Makefile - KBUILD_EXTMOD:
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endifSUBDIRS 是没有定义的,所以 KBUILD_EXTMOD 的值来自于命令行的 M 参数,而前面我们编译单独模块的时候没有 M 参数,所以这里 KBUILD_EXTMOD 为空。所以 build-dir 值如下:
build-dir = $(patsubst %/,%,$(dir $@))$@ 就是目标,就是前面的 %.ko,根据编译命令:
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16我们知道 $@ = drivers/media/i2c/ov5640.ko。所以有:
build-dir = $(patsubst %/,%,$(dir drivers/media/i2c/ov5640.ko))dir 函数的功能是从文件名序列 names 中取出目录部分,如果 names 中没有 / ,取出的值为 ./ 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回 ./。所以我们取消 dir 函数:
build-dir = $(patsubst %/,%,drivers/media/i2c/)patsubst 函数用法为:
$(patsubst pattern,replacement,text)pattern:要匹配的模式。replacement:替换后的字符串。text:要进行模式替换的文本。所以这里最后的结果是:
build-dir = drivers/media/i2c1.3.3 $(@:.ko=.o)
$(@:.ko=.o) 是是 Makefile 中的一个变量替换语法,用于将目标文件中的后缀 .ko 替换为 .o。前面我们知道 $@ = drivers/media/i2c/ov5640.ko,所以这里就是:
$(drivers/media/i2c/ov5640.ko:.ko=.o)替换后就是:
drivers/media/i2c/ov5640.o1.3.4 命令展开
我们已经分析了每一个变量,这里这条命令:
$(build)=$(build-dir) $(@:.ko=.o)我们展开如下:
-f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o这里其实是半句,其实和前面的 KBUILD_MODULES 加在一起才是一条完整命令:
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
$(build)=$(build-dir) $(@:.ko=.o)我们一起展开:
@make KBUILD_MODULES=1 -f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o1.4 %.ko 规则展开
上面我们对每一部分进行了分析,我们来展开一下 %.ko 的规则:
%.ko: prepare scripts FORCE
$(cmd_crmodverdir)
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
$(build)=$(build-dir) $(@:.ko=.o)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost展开后为:
drivers/media/i2c/ov5640.ko: prepare scripts FORCE
$(Q)mkdir -p .tmp_versions
$(Q)make KBUILD_MODULES=1 -f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o
$(Q)make -f $(srctree)/scripts/Makefile.modpostsrctree 这个值为 .,定义在 Makefile - srctree
2. Makefile.build
上面已经把 %.ko 目标展开了,接下来我们来分析这个命令:
$(Q)make KBUILD_MODULES=1 -f ./scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.omake -f 命令用于指定 Makefile 文件的名称。默认情况下,make 会查找名为 Makefile 或 makefile 的文件。项目中使用了不同的文件名,可以通过 -f 选项来指定。所以这里,很显然,就是指定 make 命令使用的文件为 Makefile.build。
这个命令传入 KBUILD_MODULES 和 obj 参数。并且指定了目标为 drivers/media/i2c/ov5640.o。
2.1 obj 与 src
先来看两个变量 obj 和 src,这个 obj 是调用 Makefile.build - scripts/Makefile.build 文件的时候传进来的,src 定义在:Makefile.build - scripts/Makefile.build - src
src := $(obj)可以看到其实 src 和 obj 是相等的。
2.2 $(obj)/%.o 目标
2.2.1 匹配哪一个目标?
$(obj)/%.o 这个目标在 Makefile.build - scripts/Makefile.build 文件中有三处定义:
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)# Single-part modules are special since we need to mark them in $(MODVERDIR)
$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
@{ echo $(@:.o=.ko); echo $@; \
$(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)$(obj)/%.o: $(src)/%.S $(objtool_dep) FORCE
$(call if_changed_rule,as_o_S)那我们使用的这个编译命令:
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16到底匹配的哪个哪个目标?我们可以看到(3)这个其实是跟汇编文件相关的,可以看一下 i2c - drivers/media/i2c 这个目录下,其实是没有汇编文件的,所以还是匹配的上面.c 相关的规则。还是在(1)和(2)中,那是哪个?先看一下这个 single-used-m,它是定义在 Makefile.lib - scripts/Makefile.lib - single-used-m,这个文件在 Makefile.build - scripts/Makefile.build 文件中被调用。具体是什么,这里就暂时先不深入去追了。直接加打印信息吧,两个位置都加:
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
echo "info(1) obj=$(obj) src=$(src) $@ $<"
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
# ......
$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
echo "info(2) single-used-m=$(single-used-m) obj=$(obj) src=$(src) $@ $<"
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
@{ echo $(@:.o=.ko); echo $@; \
$(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)然后我们执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16 # 编译指定模块可以看到有以下打印信息:
echo "info(1) obj=scripts/mod src=scripts/mod scripts/mod/empty.o scripts/mod/empty.c"
# ......
info(1) obj=scripts/mod src=scripts/mod scripts/mod/empty.o scripts/mod/empty.c
mkdir -p .tmp_versions
make KBUILD_MODULES=1 \
-f ./scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o
echo "info(2) single-used-m=drivers/media/i2c/adv7180.o drivers/media/i2c/ov5640.o obj=drivers/media/i2c src=drivers/media/i2c drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.c"
info(2) single-used-m=drivers/media/i2c/adv7180.o drivers/media/i2c/ov5640.o obj=drivers/media/i2c src=drivers/media/i2c drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.c所以这里其实两个地方都用的到了,但是完成 ov5640.c 文件编译的目标其实是(2):
# Single-part modules are special since we need to mark them in $(MODVERDIR)
$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
@{ echo $(@:.o=.ko); echo $@; \
$(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)2.2.2 $(call cmd,force_checksrc)
接下来我们来看这个命令:
$(call cmd,force_checksrc)这个其实应该很熟悉了,cmd 函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# printing commands
cmd = @$(echo-cmd) $(cmd_$(1))直接看 $(cmd_$(1)) 吧,这个 $(1) = force_checksrc,所以这里其实调用的就是 cmd_force_checksrc,它定义在 Makefile.build - scripts/Makefile.build - cmd_force_checksrc:
# Linus' kernel sanity checking tool
ifneq ($(KBUILD_CHECKSRC),0)
ifeq ($(KBUILD_CHECKSRC),2)
quiet_cmd_force_checksrc = CHECK $<
cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;
else
quiet_cmd_checksrc = CHECK $<
cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;
endif
endifKBUILD_CHECKSRC 定义在 Makefile - KBUILD_CHECKSRC:
# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/dev-tools/sparse.rst" for more details,
# including where to get the "sparse" utility.
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif这个其实就是代码检查,由于我们没有传入 C 参数(注意这里的 C 和-C 是不一样的含义,这里 C 为变量名,是要赋值的,-C 是指定一个源码目录,不一样的),就没有定义 KBUILD_CHECKSRC,这里的 KBUILD_CHECKSRC 就是 0,所以 cmd_force_checksrc 就为空。
2.2.3 $(call if_changed_rule,cc_o_c)
if_changed_rule 定义在 Kbuild.include - 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)), @:)set -e 是一个在 shell 脚本中常用的命令,用于在脚本中启用“遇到错误即退出”的行为。具体来说,当脚本中的任何命令返回非零状态时,脚本会立即终止执行。
strip 函数是去掉变量值两端的空白字符(空格和制表符),if 是判断后面去掉空白字符的变量值是否为空,当 any-prereq 和 arg-check 有一个非空,if 就成立,就执行 $(rule_$(1))。否则就执行 @:,在 shell 中 : 是一个特殊的命令,它实际上是一个内建命令,用于执行空操作。
这里我们第一次编译的时候 any-prereq 和 arg-check 肯定至少有一个非空,这两个变量就不多了解了,主要后面看执行的 $(rule_$(1))。
2.2.3.1 any-prereq
any-prereq 定义在 Kbuild.include - scripts/Kbuild.include - any-prereq
# Find any prerequisites that is newer than target or that does not exist.
# PHONY targets skipped in both cases.
any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)2.2.3.2 arg-check
arg-check 定义在 Kbuild.include - scripts/Kbuild.include - arg-check:
ifneq ($(KBUILD_NOCMDDEP),1)
# Check if both arguments are the same including their order. Result is empty
# string if equal. User may override this check using make KBUILD_NOCMDDEP=1
arg-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(cmd_$@))), \
$(subst $(space),$(space_escape),$(strip $(cmd_$1))))
else
arg-check = $(if $(strip $(cmd_$@)),,1)
endif2.2.3.3 $(rule_$(1))
$(1) 就是 cc_o_c,所以这里展开就是:
$(rule_$(1)) = $(rule_cc_o_c)rule_cc_o_c 定义在 Makefile.build - scripts/Makefile.build - rule_cc_o_c:
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(cmd_modversions_c) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef其实到这里,$(obj)/%.o 目标的规则就展开基本结束了。我们后面继续深入这个 rule_cc_o_c。
3. rule_cc_o_c 函数
为什么单独写呢?因为标题嵌套太深啦,哈哈哈。这里我们详细再把这个 rule_cc_o_c 分析一下。这个 rule_cc_o_c,前面已经找到它定义在 Makefile.build - scripts/Makefile.build - rule_cc_o_c:
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(cmd_modversions_c) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef3.1 checksrc
我们来看一下这一行 Makefile.build - scripts/Makefile.build:
$(call echo-cmd,checksrc) $(cmd_checksrc)3.1.1 $(cmd_checksrc)
先看这个 cmd_checksrc,它定义在 Makefile.build - scripts/Makefile.build - cmd_checksrc:
# Linus' kernel sanity checking tool
ifneq ($(KBUILD_CHECKSRC),0)
ifeq ($(KBUILD_CHECKSRC),2)
quiet_cmd_force_checksrc = CHECK $<
cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;
else
quiet_cmd_checksrc = CHECK $<
cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;
endif
endif会发现,这里其实和我们分析的 二、make dir/file.ko 这一节中的 2.2.2 $(call cmd,force_checksrc) 这部分逻辑一样的,取决于 KBUILD_CHECKSRC 的值,我们前面分析了,使用 make C = 2 这样的命令这里才会有定义,所以这里也是为空。
3.1.2 $(call echo-cmd,checksrc)
echo-cmd 定义在 Kbuild.include - scripts/Kbuild.include - echo-cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)这里的 $(1) = checksrc,quiet 定义在 Makefile - quiet,分析一下就可以知道,当我们执行 make V=1 的时候,这里 quiet 为空,当执行 make V=0 的时候,quiet = quiet_。所以这里展开是:
echo-cmd = $(if $(cmd_checksrc),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# 或者
echo-cmd = $(if $(quiet_cmd_checksrc),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)cmd_check 和 quiet_cmd_checksrc 都定义在 Makefile.build - cmd_check 这里,还是和二、make dir/file.ko 这一节中的 2.2.2 $(call cmd,force_checksrc) 一样,由于我们没有传入 C 参数的值,这里都是空的。
3.1.3 总结
由于没有传入 make C = 2 其他值,这里并没有开启代码检查。所以 $(call echo-cmd,checksrc) 和 $(cmd_checksrc) 都为空。
3.2 $(call cmd_and_fixdep,cc_o_c)
接下来来看一下这个命令:
$(call cmd_and_fixdep,cc_o_c)这个命令是调用函数 cmd_and_fixdep,传入参数为 cc_o_c。
cmd_and_fixdep 定义在 Kbuild.include - scripts/Kbuild.include - cmd_and_fixdep,它有两个定义,通过 CONFIG_TRIM_UNUSED_KSYMS 选择不同的命令:
ifndef CONFIG_TRIM_UNUSED_KSYMS
# (1)
cmd_and_fixdep = \
#......
else
#......
# (2)
cmd_and_fixdep = \
# ......
endif我们搜一下这个 CONFIG_TRIM_UNUSED_KSYMS 配置项,会发现是没有的,所以这里执行(1)的命令:
cmd_and_fixdep = \
$(echo-cmd) $(cmd_$(1)); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd;$(1) = cc_o_c, 所以这里是:
cmd_and_fixdep = \
$(echo-cmd) $(cmd_cc_o_c); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd;3.2.1 $(echo-cmd)
echo-cmd 定义在 Kbuild.include - echo-cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)根据前面分析的这里就是:
echo-cmd = $(if $(cmd_cc_o_c),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# 或者
echo-cmd = $(if $(quiet_cmd_cc_o_c),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)这里按 make V=1 的结果进行的分析,那我们要找的就是 cmd_cc_o_c,其实最终他们执行的都是一样的,只不过就是打印信息的区别。cmd_cc_o_c 定义在这个位置:Makefile.build - scripts/Makefile.build - cmd_cc_o_c
# C (.c) files
# The C file is compiled and updated dependency information is generated.
# (See cmd_cc_o_c + relevant part of rule_cc_o_c)
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
ifndef CONFIG_MODVERSIONS
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
else
#......
cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<
# ......
endif我们搜索一下 CONFIG_MODVERSIONS,发现在 imx_v6_v7_defconfig 和.config 中有定义:
CONFIG_MODVERSIONS=y所以这里就是:
cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<显然是非空的,这里 if 成立,就是:
echo-cmd = echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';3.2.2 $(cmd_cc_o_c)
上一小节其实已经分析完了,cmd_cc_o_c 定义在 Makefile.build - scripts/Makefile.build - cmd_cc_o_c,的值为
cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<$(@D)表示 "$@" 的目录部分(不以斜杠作为结尾) ,如果 "$@" 值是 "dir/foo.o",那么 "$(@D)" 就是 "dir",而如果 "$@" 中没有包含斜杠的话,其值就是 "."(当前目录) 。$(@F)表示 "$@" 的文件部分,如果 "$@" 值是 "dir/foo.o",那么 "$(@F)" 就是 "foo.o","$(@F)" 相当于函数 "$(notdir $@)"
所以这里其实就是把 c 文件编译成 o 的地方。
3.2.3 $(echo-cmd) $(cmd_$(1)) 做了什么?
上面已经单独分析了这两个变量,但是好像也不清楚具体是做了什么,里面有一些变量还是没有办法确定,前面简单展开了 cmd_and_fixdep:
cmd_and_fixdep = \
$(echo-cmd) $(cmd_cc_o_c); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd;我们这里再继续展开一下第 2 行:
cmd_and_fixdep = \
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)'; $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<; \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd;在这里加个打印信息吧 Kbuild.include - scripts/Kbuild.include - cmd_and_fixdep:
cmd_and_fixdep = \
echo "cmd_and_fixdep info CC=$(CC) c_flags=$(c_flags) @D=$(@D) @F=$(@F) $<";\
$(echo-cmd) $(cmd_cc_o_c); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
rm -f $(depfile); \
mv -f $(dot-target).tmp $(dot-target).cmd;然后执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16其实 cmd_and_fixdep 命令就是用来编译 C 文件的,所以调用的地方有很多,我们加的打印信息也会出现很多次,我们找这个:
mkdir -p .tmp_versions
make KBUILD_MODULES=1 \
-f ./scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o
echo "info(2) single-used-m=drivers/media/i2c/adv7180.o drivers/media/i2c/ov5640.o obj=drivers/media/i2c src=drivers/media/i2c drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.c"
info(2) single-used-m=drivers/media/i2c/adv7180.o drivers/media/i2c/ov5640.o obj=drivers/media/i2c src=drivers/media/i2c drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.c
cmd_and_fixdep info CC=arm-linux-gnueabihf-gcc c_flags=-Wp,-MD,drivers/media/i2c/.ov5640.o.d -nostdinc -isystem /opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/../lib/gcc/arm-linux-gnueabihf/4.9.2/include -I./arch/arm/include -I./arch/arm/include/generated -I./include -I./arch/arm/include/uapi -I./arch/arm/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -fno-dwarf2-cfi-asm -fno-omit-frame-pointer -mapcs -mno-sched-prolog -fno-ipa-sra -mabi=aapcs-linux -mfpu=vfp -marm -Wa,-mno-warn-deprecated -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -msoft-float -Uarm -fno-delete-null-pointer-checks -O2 --param=allow-store-data-races=0 -Wframe-larger-than=1024 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-var-tracking-assignments -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DMODULE -DKBUILD_BASENAME='ov5640' -DKBUILD_MODNAME='ov5640' @D=drivers/media/i2c @F=ov5640.o drivers/media/i2c/ov5640.cinfo(2)这里是前面 二、make dir/file.ko 部分的 2.2 $(obj)/%.o 目标 这一个小节添加的,刚好帮我们定位我们所需要的 cmd_and_fixdep info,整理一下:
cmd_and_fixdep info:
CC=arm-linux-gnueabihf-gcc
c_flags=-Wp,-MD,drivers/media/i2c/.ov5640.o.d -nostdinc -isystem /opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/../lib/gcc/arm-linux-gnueabihf/4.9.2/include -I./arch/arm/include -I./arch/arm/include/generated -I./include -I./arch/arm/include/uapi -I./arch/arm/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -fno-dwarf2-cfi-asm -fno-omit-frame-pointer -mapcs -mno-sched-prolog -fno-ipa-sra -mabi=aapcs-linux -mfpu=vfp -marm -Wa,-mno-warn-deprecated -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -msoft-float -Uarm -fno-delete-null-pointer-checks -O2 --param=allow-store-data-races=0 -Wframe-larger-than=1024 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-var-tracking-assignments -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DMODULE -DKBUILD_BASENAME='ov5640' -DKBUILD_MODNAME='ov5640'
@D=drivers/media/i2c
@F=ov5640.o
drivers/media/i2c/ov5640.c所以总的来说,$(echo-cmd) $(cmd_$(1)) 完成了 drivers/media/i2c/ov5640.c 到 drivers/media/i2c/ov5640.o 的编译。
3.3 $(cmd_checkdoc)
接下来看这个 cmd_checkdoc,它是定义在 Makefile.build - scripts/Makefile.build - cmd_checkdoc:
ifneq ($(KBUILD_ENABLE_EXTRA_GCC_CHECKS),)
cmd_checkdoc = $(srctree)/scripts/kernel-doc -none $< ;
endif我们搜索一下这个 KBUILD_ENABLE_EXTRA_GCC_CHECKS,会发现它定义在 Makefile.extrawarn - scripts/Makefile.extrawarn - KBUILD_ENABLE_EXTRA_GCC_CHECKS:
ifeq ("$(origin W)", "command line")
export KBUILD_ENABLE_EXTRA_GCC_CHECKS := $(W)
endif可以看到这里是从命令行读取 W 参数,但是我们编译的时候没有加入这个参数,所以这里为空。
Makefile.extrawarn - scripts/Makefile.extrawarn 这个文件是在顶层 Makefile 中被包含。这里 ifneq 就是判断参数是否不相等,这里判断的是 KBUILD_ENABLE_EXTRA_GCC_CHECKS 与 0 是否不相等嫌显然这里不成立,所以 cmd_checkdoc 为空。这条命令相当于没有。
3.4 objtool
我们继续看这条命令 Makefile.build - scripts/Makefile.build:
$(call echo-cmd,objtool) $(cmd_objtool)3.4.1 $(cmd_objtool)
先看这个 cmd_objtool,它定义在 Makefile.build - scripts/Makefile.build - cmd_objtool:
# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
# 'OBJECT_FILES_NON_STANDARD_foo.o := 'n': override directory skip for a file
cmd_objtool = $(if $(patsubst y%,, \
$(OBJECT_FILES_NON_STANDARD_$(basetarget).o)$(OBJECT_FILES_NON_STANDARD)n), \
$(__objtool_obj) $(objtool_args) "$(objtool_o)";)
objtool_obj = $(if $(patsubst y%,, \
$(OBJECT_FILES_NON_STANDARD_$(basetarget).o)$(OBJECT_FILES_NON_STANDARD)n), \
$(__objtool_obj))这里直接加个打印信息看下结果吧,就在这个位置加:Makefile.build - scripts/Makefile.build
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(warning cmd_objtool=$(cmd_objtool)) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(cmd_modversions_c) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef在这里我加了 $(warning <text>) 函数,它不用像 echo 一样有限制,echo 只能在 target:后面的语句中使用,且前面是个 TAB。用这个函数还可以打印出行号(不过这个行号感觉和文件中的有些区别,因为 Makefile 是先替替换函数内容然后执行,所以可能意义也不大)。值然后执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16 # 编译指定模块会看到如下打印信息:
scripts/Makefile.build:311: cmd_objtool=所以在单独编译这个模块的时候这里为空,就先不管了。
3.4.2 $(call echo-cmd,objtool)
再来看一下这个 echo-cmd 函数,前面分析过的,echo-cmd 定义在 Kbuild.include - scripts/Kbuild.include - echo-cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)这里的 $(1) = objtool,quiet 定义在 Makefile - quiet,分析一下就可以知道,当我们执行 make V=1 的时候,这里 quiet 为空,当执行 make V=0 的时候,quiet = quiet_。所以这里展开是:
echo-cmd = $(if $(cmd_objtool),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# 或者
echo-cmd = $(if $(quiet_cmd_objtool),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)前面其实分析过 cmd_objtool 这个了,它是为空,所以这里也不会有什么信息。
3.4.3 总结
前面已经分析了 $(call echo-cmd,objtool) 和 $(cmd_objtool),他们都是为空,所以这行就不用管了,我们在打印信息里其实也会发现不会有相关的内容出现。
3.5 $(cmd_modversions_c)
接下来看一下这个 cmd_modversions_c,它定义在 Makefile.build - scripts/Makefile.build - cmd_modversions_c:
cmd_modversions_c = \
if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then \
$(call cmd_gensymtypes_c,$(KBUILD_SYMTYPES),$(@:.o=.symtypes)) \
> $(@D)/.tmp_$(@F:.o=.ver); \
\
$(LD) $(KBUILD_LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F) \
-T $(@D)/.tmp_$(@F:.o=.ver); \
rm -f $(@D)/.tmp_$(@F) $(@D)/.tmp_$(@F:.o=.ver); \
else \
mv -f $(@D)/.tmp_$(@F) $@; \
fi;又是一堆的变量,这里直接接打印信息吧:Makefile.build - scripts/Makefile.build
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(warning cmd_modversions_c=$(cmd_modversions_c)) \
$(cmd_modversions_c) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef然后继续执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16 # 编译指定模块会看到如下打印信息:
scripts/Makefile.build:311: cmd_modversions_c=if arm-linux-gnueabihf-objdump -h drivers/media/i2c/.tmp_ov5640.o | grep -q __ksymtab; then arm-linux-gnueabihf-gcc -E -D__GENKSYMS__ -Wp,-MD,drivers/media/i2c/.ov5640.o.d -nostdinc -isystem /opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/../lib/gcc/arm-linux-gnueabihf/4.9.2/include -I./arch/arm/include -I./arch/arm/include/generated -I./include -I./arch/arm/include/uapi -I./arch/arm/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -fno-dwarf2-cfi-asm -fno-omit-frame-pointer -mapcs -mno-sched-prolog -fno-ipa-sra -mabi=aapcs-linux -mfpu=vfp -marm -Wa,-mno-warn-deprecated -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -msoft-float -Uarm -fno-delete-null-pointer-checks -O2 --param=allow-store-data-races=0 -Wframe-larger-than=1024 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-var-tracking-assignments -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DMODULE -DKBUILD_BASENAME='"ov5640"' -DKBUILD_MODNAME='"ov5640"' drivers/media/i2c/ov5640.c | scripts/genksyms/genksyms -r /dev/null > drivers/media/i2c/.tmp_ov5640.ver; arm-linux-gnueabihf-ld -EL -r -o drivers/media/i2c/ov5640.o drivers/media/i2c/.tmp_ov5640.o -T drivers/media/i2c/.tmp_ov5640.ver; rm -f drivers/media/i2c/.tmp_ov5640.o drivers/media/i2c/.tmp_ov5640.ver; else mv -f drivers/media/i2c/.tmp_ov5640.o drivers/media/i2c/ov5640.o; fi;整理一下就是:
cmd_modversions_c=
if arm-linux-gnueabihf-objdump -h drivers/media/i2c/.tmp_ov5640.o | grep -q __ksymtab; then
arm-linux-gnueabihf-gcc -E -D__GENKSYMS__ -Wp,-MD,drivers/media/i2c/.ov5640.o.d -nostdinc -isystem /opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/../lib/gcc/arm-linux-gnueabihf/4.9.2/include -I./arch/arm/include -I./arch/arm/include/generated -I./include -I./arch/arm/include/uapi -I./arch/arm/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -mlittle-endian -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -fno-dwarf2-cfi-asm -fno-omit-frame-pointer -mapcs -mno-sched-prolog -fno-ipa-sra -mabi=aapcs-linux -mfpu=vfp -marm -Wa,-mno-warn-deprecated -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -msoft-float -Uarm -fno-delete-null-pointer-checks -O2 --param=allow-store-data-races=0 -Wframe-larger-than=1024 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-var-tracking-assignments -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -DMODULE -DKBUILD_BASENAME='"ov5640"' -DKBUILD_MODNAME='"ov5640"' drivers/media/i2c/ov5640.c | scripts/genksyms/genksyms -r /dev/null > drivers/media/i2c/.tmp_ov5640.ver; arm-linux-gnueabihf-ld -EL -r -o drivers/media/i2c/ov5640.o drivers/media/i2c/.tmp_ov5640.o -T drivers/media/i2c/.tmp_ov5640.ver;
rm -f drivers/media/i2c/.tmp_ov5640.o drivers/media/i2c/.tmp_ov5640.ver;
else
mv -f drivers/media/i2c/.tmp_ov5640.o drivers/media/i2c/ov5640.o;
fi;3.6 record_mcount
接下来看一下 rule_cc_o_c 的最后一条命令:Makefile.build - scripts/Makefile.build
define rule_cc_o_c
#......
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef3.6.1 $(cmd_record_mcount)
cmd_record_mcount 定义在 Makefile.build - scripts/Makefile.build - cmd_record_mcount:
cmd_record_mcount = \
if [ "$(findstring $(CC_FLAGS_FTRACE),$(_c_flags))" = \
"$(CC_FLAGS_FTRACE)" ]; then \
$(sub_cmd_record_mcount) \
fi;还是省点事,直接在这里加打印信息:Makefile.build - scripts/Makefile.build
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(cmd_modversions_c) \
$(warning record_mcount=$(record_mcount)) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef然后还是执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16 # 编译指定模块会看到以下打印信息:
mkdir -p .tmp_versions
make KBUILD_MODULES=1 \
-f ./scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o
scripts/Makefile.build:311: record_mcount=可以看到这里还是为空。
3.6.2 $(call echo-cmd,record_mcount)
接下来看一下 $(call echo-cmd,record_mcount), 再来看一下这个 echo-cmd 函数,前面分析过的,echo-cmd 定义在 Kbuild.include - scripts/Kbuild.include - echo-cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)这里的 $(1) = record_mcount,quiet 定义在 Makefile - quiet,分析一下就可以知道,当我们执行 make V=1 的时候,这里 quiet 为空,当执行 make V=0 的时候,quiet = quiet_。所以这里展开是:
echo-cmd = $(if $(cmd_record_mcount),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# 或者
echo-cmd = $(if $(quiet_cmd_record_mcount),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)前面其实分析过 record_mcount 这个了,它是为空,所以这里也不会有什么信息。
3.6.3 总结
综上所述,这里这行命令其实也是空。
3.7 rule_cc_o_c 总结
经过以上分析,我们再来看一下这个函数:Makefile.build - scripts/Makefile.build - rule_cc_o_c:
define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call cmd_and_fixdep,cc_o_c) \
$(cmd_checkdoc) \
$(call echo-cmd,objtool) $(cmd_objtool) \
$(cmd_modversions_c) \
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef在我们单独编译 drivers/media/i2c/ov5640.ko 的时候,这个函数里面其实可以简化为:
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c) \
endef这一条命令就是将 drivers/media/i2c/ov5640.c 编译成 drivers/media/i2c/ov5640.o。
4. Makefile.modpost
4.1 文件结构
最后我们来看 %.ko 目标的最后一条命令:
$(Q)make -f $(srctree)/scripts/Makefile.modpost这里的 srctree = .,所以要分析的是这个文件:Makefile.modpost - scripts/Makefile.modpost。先来看一下文件开头的注释:
# Stage one of module building created the following:
# a) The individual .o files used for the module
# b) A <module>.o file which is the .o files above linked together
# c) A <module>.mod file in $(MODVERDIR)/, listing the name of the the preliminary <module>.o file, plus all .o files
# Stage 2 is handled by this file and does the following
# 1) Find all modules from the files listed in $(MODVERDIR)/
# 2) modpost is then used to
# 3) create one <module>.mod.c file pr. module
# 4) create one Module.symvers file with CRC for all exported symbols
# 5) compile all <module>.mod.c files
# 6) final link of the module to a <module.ko> file
# Step 3 is used to place certain information in the module's ELF section, including information such as:
# Version magic (see include/linux/vermagic.h for full details)
# - Kernel release
# - SMP is CONFIG_SMP
# - PREEMPT is CONFIG_PREEMPT
# - GCC Version
# Module info
# - Module version (MODULE_VERSION)
# - Module alias'es (MODULE_ALIAS)
# - Module license (MODULE_LICENSE)
# - See include/linux/module.h for more details
# Step 4 is solely used to allow module versioning in external modules, where the CRC of each module is retrieved from the Module.symvers file.这里其实就是一个构建 ko 驱动文件的说明,主要分了 4 个阶段。我们打开文件就会发现,文件中有一些注释来告诉我们哪些是第一阶段,哪些是第二阶段。为什么要先看这个?因为我没找到默认目标,属于完全没看懂的样子,然后就发现这个文件中有不同阶段的注释,先来看一看吧。
4.1.1 Step 1
Makefile.modpost - scripts/Makefile.modpost - Step 1:
# Step 1), find all modules listed in $(MODVERDIR)/
MODLISTCMD := find $(MODVERDIR) -name '*.mod' | xargs -r grep -h '\.ko$$' | sort -u
__modules := $(shell $(MODLISTCMD))
modules := $(patsubst %.o,%.ko, $(wildcard $(__modules:.ko=.o)))
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))4.1.2 Step 2/3/4
Makefile.modpost - scripts/Makefile.modpost - Step 2/3/4:
# Step 2), invoke modpost
# Includes step 3,4
modpost = scripts/mod/modpost \
$(if $(CONFIG_MODVERSIONS),-m) \
$(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,) \
$(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile) \
$(if $(KBUILD_EXTMOD),-I $(modulesymfile)) \
$(if $(KBUILD_EXTMOD),$(addprefix -e ,$(KBUILD_EXTRA_SYMBOLS))) \
$(if $(KBUILD_EXTMOD),-o $(modulesymfile)) \
$(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S) \
$(if $(CONFIG_SECTION_MISMATCH_WARN_ONLY),,-E) \
$(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w)
MODPOST_OPT=$(subst -i,-n,$(filter -i,$(MAKEFLAGS)))
# We can go over command line length here, so be careful.
quiet_cmd_modpost = MODPOST $(words $(filter-out vmlinux FORCE, $^)) modules
cmd_modpost = $(MODLISTCMD) | sed 's/\.ko$$/.o/' | $(modpost) $(MODPOST_OPT) -s -T -
PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
$(call cmd,modpost) $(wildcard vmlinux)
quiet_cmd_kernel-mod = MODPOST $@
cmd_kernel-mod = $(modpost) $@
vmlinux.o: FORCE
$(call cmd,kernel-mod)
# Declare generated files as targets for modpost
$(modules:.ko=.mod.c): __modpost ;4.1.3 Step 5
Makefile.modpost - scripts/Makefile.modpost - Step 5:
# Step 5), compile all *.mod.c files
# modname is set to make c_flags define KBUILD_MODNAME
modname = $(notdir $(@:.mod.o=))
quiet_cmd_cc_o_c = CC $@
cmd_cc_o_c = $(CC) $(c_flags) $(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE) \
-c -o $@ $<
$(modules:.ko=.mod.o): %.mod.o: %.mod.c FORCE
$(call if_changed_dep,cc_o_c)
targets += $(modules:.ko=.mod.o)
ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
# Step 6), final link of the modules with optional arch pass after final link
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o = \
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
-o $@ $(filter-out FORCE,$^) ; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
$(modules): %.ko :%.o %.mod.o FORCE
+$(call if_changed,ld_ko_o)
targets += $(modules)4.1.4 所有出现的目标
简化一下,看看所有出现的目标吧:
PHONY := _modpost
_modpost: __modpost
# Step 1), find all modules listed in $(MODVERDIR)/
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))
# Step 2), invoke modpost
# Includes step 3,4
PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
$(call cmd,modpost) $(wildcard vmlinux)
vmlinux.o: FORCE
$(call cmd,kernel-mod)
# Declare generated files as targets for modpost
$(modules:.ko=.mod.c): __modpost ;
# Step 5), compile all *.mod.c files
$(modules:.ko=.mod.o): %.mod.o: %.mod.c FORCE
$(call if_changed_dep,cc_o_c)
$(modules): %.ko :%.o %.mod.o FORCE
+$(call if_changed,ld_ko_o)可以看到 _modpost 是出现的第一个规则,他应该是默认规则,这个规则第一次出现的时候依赖于__modpost 目标,然后往后就会发现,第 7 行又出现了它的新的依赖,那么怎么执行的?我们来试一下一个目标先后出现不同的规则的时候怎么运行的:
PHONY += test
test: test2
test: test1
test1:
@echo "test1"
test2:
@echo "test2"
.PHONY: $(PHONY)我们执行 make,会有如下打印信息:
test1
test2实践证明,只要是目标依赖的目标中的规则都会执行,并且先出现的先执行。所以这里我们大概就可以知道怎么去跟这部分的代码了。
4.2 _modpost 目标
默认目标是哪个?一般来说 makefile 的第一个目标就是默认目标,刚才大分析文件结构的时候大概已经清楚默认目标了,我们打开 Makefile.modpost - scripts/Makefile.modpost,看到的第一个目标就是:
PHONY := _modpost
_modpost: __modpost可以看到_modpost 又依赖于__modpost,这个时候就会先去完成__modpost 目标中的规则。
4.3 __modpost 目标
接下来先来看这个__modpost 目标,它定义在 Makefile.modpost - scripts/Makefile.modpost - __modpost 目标:
PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
$(call cmd,modpost) $(wildcard vmlinux)4.3.1 $(call cmd,modpost)
先来看这个 $(call cmd,modpost) 命令,cmd 函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
# echo command.
# Short version is used, if $(quiet) equals `quiet_', otherwise full one.
echo-cmd = $(if $($(quiet)cmd_$(1)),\
echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
# printing commands
cmd = @$(echo-cmd) $(cmd_$(1))直接看 $(cmd_$(1)),前面 cmd 函数调用的时候传入的参数是 modpost,所以 $(1) = modpost,这里就是 $(cmd_modpost),定义在 Makefile.modpost - scripts/Makefile.modpost - cmd_modpost:
# We can go over command line length here, so be careful.
quiet_cmd_modpost = MODPOST $(words $(filter-out vmlinux FORCE, $^)) modules
cmd_modpost = $(MODLISTCMD) | sed 's/\.ko$$/.o/' | $(modpost) $(MODPOST_OPT) -s -T -MODLISTCMD 定义在 Makefile.modpost - scripts/Makefile.modpost - MODLISTCMD:
MODLISTCMD := find $(MODVERDIR) -name '*.mod' | xargs -r grep -h '\.ko$$' | sort -uMODPOST_OPT 定义在 Makefile.modpost - scripts/Makefile.modpost - MODPOST_OPT:
MODPOST_OPT=$(subst -i,-n,$(filter -i,$(MAKEFLAGS)))modpost 定义在 Makefile.modpost - scripts/Makefile.modpost - modpost:
modpost = scripts/mod/modpost \
$(if $(CONFIG_MODVERSIONS),-m) \
$(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,) \
$(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile) \
$(if $(KBUILD_EXTMOD),-I $(modulesymfile)) \
$(if $(KBUILD_EXTMOD),$(addprefix -e ,$(KBUILD_EXTRA_SYMBOLS))) \
$(if $(KBUILD_EXTMOD),-o $(modulesymfile)) \
$(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S) \
$(if $(CONFIG_SECTION_MISMATCH_WARN_ONLY),,-E) \
$(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w)这里涉及的变量较多,我们直接看打印信息吧,因为它调用的是 cmd 函数,所以是带有命令回显的:
find .tmp_versions -name '*.mod' | xargs -r grep -h '\.ko$' | sort -u | sed 's/\.ko$/.o/' | scripts/mod/modpost -m -a -o ./Module.symvers -S -s -T -这行打印新奇其实就是在执行 $(call cmd,modpost)。这里不深究了先到此为止。
4.3.2 $(wildcard vmlinux)
这个 wildcard 函数用于查找匹配指定模式的文件,并返回这些文件的列表。这个函数在处理文件路径和文件名时非常有用,特别是在需要动态生成文件列表的情况下。一般用法如下:
$(wildcard 指定文件类型)所以这里其实是在匹配当前路径下的 vmlinux 文件。显然这里为空。
4.3.3 $(modules:.ko=.o)
前面__modpost 目标还依赖于 $(modules:.ko=.o),我们来看一下这是个什么。再来看一下这个依赖项,这个内部其实是 makefile 中的字符串替换语法,就是把 modules 中所有成员的.ko 替换成.o。modules 变量定义在 Makefile.modpost - scripts/Makefile.modpost - modules:
modules := $(patsubst %.o,%.ko, $(wildcard $(__modules:.ko=.o)))这里直接在这个位置加个打印信息吧:Makefile.modpost - scripts/Makefile.modpost:
PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
$(warning "info modules=$(modules) (modules:.ko=.o)=$(modules:.ko=.o)")
$(call cmd,modpost) $(wildcard vmlinux)然后执行以下命令:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16可以看到如下打印信息:
scripts/Makefile.modpost:94: "info modules=drivers/media/i2c/ov5640.ko (modules:.ko=.o)=drivers/media/i2c/ov5640.o"这里其实就是依赖于 drivers/media/i2c/ov5640.o。这个文件在前面已经生成了的。
4.3.4 总结
所以这个__modpost 目标中的规则是在执行这条命令:
find .tmp_versions -name '*.mod' | xargs -r grep -h '\.ko$' | sort -u | sed 's/\.ko$/.o/' | scripts/mod/modpost -m -a -o ./Module.symvers -S -s -T -这里也就不再深究了。
4.4 _modpost 目标
上面已经分析完了,接下来再来看下一个_modpost 目标,定义在 Makefile.modpost - scripts/Makefile.modpost - _modpost:
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))直接在这里加打印信息吧,我是没找到在哪定义:
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))
$(warning KBUILD_MODPOST_NOFINAL=$(KBUILD_MODPOST_NOFINAL) (modules:.ko:.o)=$(modules:.ko:.o) modules=$(modules))查看打印信息如下:
scripts/Makefile.modpost:70: KBUILD_MODPOST_NOFINAL= (modules:.ko:.o)= modules=drivers/media/i2c/ov5640.ko所以其实上面展开就是:
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: drivers/media/i2c/ov5640.ko就这样,又产生了一个要追的目标 $(modules)。
4.5 $(modules) 目标
接下来我们来看 $(modules) 目标,它定义在 Makefile.modpost - scripts/Makefile.modpost - modules:
$(modules): %.ko :%.o %.mod.o FORCE
+$(call if_changed,ld_ko_o)4.5.1 if_changed 函数
if_changed 函数定义在 Kbuild.include - scripts/Kbuild.include - if_changed:
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)由于我么要生成 ko 文件,这里 $(any-prereq) 和 $(arg-check) 至少有一个不会为空,所以这里简化为:
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd前面其实分析过 $(echo-cmd) $(cmd_$(1)) 这句在干什么,$(echo-cmd) 负责命令回显,$(cmd_$(1)) 负责命令执行。所以这里我们直接看这个 $(cmd_$(1)) 即可。这里就是 $(cmd_ld_ko_o)。
4.5.2 cmd_ld_ko_o
cmd_ld_ko_o 定义在 Makefile.modpost - scripts/Makefile.modpost - cmd_ld_ko_o:
# Step 6), final link of the modules with optional arch pass after final link
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o = \
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
-o $@ $(filter-out FORCE,$^) ; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)那这句是什么?其实吧前面分析过的 $(echo-cmd) 可以回显命令的,显示的就是后面的 $(cmd_$(1)) 在这里就是 cmd_ld_ko_o,为了方便区分,我们在这里加一条打印:
cmd_ld_ko_o = \
echo "cmd_ld_ko_o info >>>";\
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
-o $@ $(filter-out FORCE,$^) ; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)这里不能用 $(warning) 来打印,会报错。然后我们执行:
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16会看到如下打印信息:
echo "cmd_ld_ko_o info >>>"; arm-linux-gnueabihf-ld -r -EL -T ./scripts/module-common.lds -T ./arch/arm/kernel/module.lds --build-id -o drivers/media/i2c/ov5640.ko drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.mod.o ; true
cmd_ld_ko_o info >>>所以其实下面这条就是 cmd_ld_ko_o 的命令回显啦:
echo "cmd_ld_ko_o info >>>"; arm-linux-gnueabihf-ld -r -EL -T ./scripts/module-common.lds -T ./arch/arm/kernel/module.lds --build-id -o drivers/media/i2c/ov5640.ko drivers/media/i2c/ov5640.o drivers/media/i2c/ov5640.mod.o ; true可以看到这里其实就是 arm-linux-gnueabihf-ld 将相关的.o 文件链接成 drivers/media/i2c/ov5640.ko 的命令,到此为止,我们就生成了最终的 drivers/media/i2c/ov5640.ko:
