LV05-03-Kernel-03-02-make-single-module

本文主要是kernel——编译单个内核模块的流程的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、概述

1. 帮助信息

我们看一下 Makefile - help

1
2
3
4
5
6
7
8
9
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等相关命令编译模块:

1
2
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

1
2
3
4
5
6
7
# 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

1
2
3
###
# Escape single quote for use in echo statements
escsq = $(subst $(squote),'\$(squote)',$1)

这个subst函数是字符串替换函数,包含三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作用的字串。用法如下:

1
$(subst <from>,<to>,<text>)

作用就是在text中将from替换成to。所以这里的含义就是在传入的参数$1中将$(squote)替换成\$(squote),这个squote定义在Kbuild.include - scripts/Kbuild.include - squote

1
squote  := '

所以将escsq展开就是:

1
escsq = $(subst ','\'',$1)

就是将$1中的'替换成\'

1.1.2 echo-why

再来看一下这个echo-why,它定义在Kbuild.include - scripts/Kbuild.include - echo-why

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
###
# 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:

1
2
3
4
# 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来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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))负责执行命令:

image-20241129000124387

2. wanring函数

接下来看一个makefile的控制函数,warning,会打印当前行号和自定义文本:

1
$(warning TEXT...)

不能直接用echo吗?当然也可以,但是echo只能在target:后面的语句中使用,且前面是个TAB。这个的话,就哪里都能用。这个warning命令也可以打印变量,和echo一样。

1
$(warning "a=$a")

三、make dir/file.ko

我们来了解一下编译指定模块,先从简单的来。这里以内核中的 drivers/media/i2c/ov5640.ko 这个模块为例。

1. %.ko目标

我们从执行的命令 make dir/file.ko 中推测,在顶层makefile中应该存在对应的目标,我们去搜一下,会看到这个目标 Makefile - %ko

1
2
3
4
5
%.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

我们编译的命令为:

1
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

1
2
3
4
# 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目标的规则中加点打印信息看一下:

1
2
3
4
5
6
%.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

然后执行以下命令:

1
2
3
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 # 编译指定模块

会看到以下打印信息:

1
2
echo "MODVERDIR=.tmp_versions KBUILD_MODULES="
MODVERDIR=.tmp_versions KBUILD_MODULES=

所以cmd_crmodverdir展开就是:

1
2
cmd_crmodverdir = $(Q)mkdir -p .tmp_versions \
$(if $(KBUILD_MODULES),; rm -f .tmp_versions/*)

if函数的用法如下:

1
$(if CONDITION,THEN-PART[,ELSE-PART])

if 函数的第一个参数 CONDITION表示条件判断,展开后如果非空,则条件为真,执行 THEN-PART部分;否则,如果有ELSE-PART部分,则执行ELSE-PART部分。

这里因为KBUILD_MODULES为空,所以这里应该执行ELSE-PART部分,但是命令中无ELSE-PART部分,所以这里就没有内容了,所以最后

1
cmd_crmodverdir = $(Q)mkdir -p .tmp_versions

这个变量最后就是在创建.tmp_versions目录了。

1.2 KBUILD_MODULES

我们继续往下看KBUILD_MODULES,它的值由CONFIG_MODULES决定:

1
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \

这个我们可以使用如下命令在配置后的linux内核源码中搜索一下,会在顶层源码目录中生成的.config文件中或者imx_v6_v7_defconfig - arch/arm/configs/imx_v6_v7_defconfig - CONFIG_MODULES中看到:

1
CONFIG_MODULES=y

所以KBUILD_MODULES=1。

1.3 build、build-dir

我们再看一下这个build这一行:

1
$(build)=$(build-dir) $(@:.ko=.o)

1.3.1 build

build定义在 Kbuild.include - scripts/Kbuild.include - build

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

1.3.2 build-dir

build-dir定义在 Makefile - build-dir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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

1
2
3
4
5
6
7
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif

SUBDIRS是没有定义的,所以KBUILD_EXTMOD的值来自于命令行的M参数,而前面我们编译单独模块的时候没有M参数,所以这里KBUILD_EXTMOD为空。所以build-dir值如下:

1
build-dir  = $(patsubst %/,%,$(dir $@))

$@就是目标,就是前面的%.ko,根据编译命令:

1
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- drivers/media/i2c/ov5640.ko -j16

我们知道$@=drivers/media/i2c/ov5640.ko。所以有:

1
build-dir  = $(patsubst %/,%,$(dir drivers/media/i2c/ov5640.ko))

dir函数的功能是从文件名序列 names 中取出目录部分,如果names 中没有 / ,取出的值为 ./ 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回 ./。所以我们取消dir函数:

1
build-dir  = $(patsubst %/,%,drivers/media/i2c/)

patsubst函数用法为:

1
$(patsubst pattern,replacement,text)

pattern:要匹配的模式。replacement:替换后的字符串。text:要进行模式替换的文本。所以这里最后的结果是:

1
build-dir  = drivers/media/i2c

1.3.3 $(@:.ko=.o)

$(@:.ko=.o)是是 Makefile 中的一个变量替换语法,用于将目标文件中的后缀 .ko 替换为 .o。前面我们知道$@=drivers/media/i2c/ov5640.ko,所以这里就是:

1
$(drivers/media/i2c/ov5640.ko:.ko=.o)

替换后就是:

1
drivers/media/i2c/ov5640.o

1.3.4 命令展开

我们已经分析了每一个变量,这里这条命令:

1
$(build)=$(build-dir) $(@:.ko=.o)

我们展开如下:

1
-f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o

这里其实是半句,其实和前面的KBUILD_MODULES加在一起才是一条完整命令:

1
2
$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1)   \
$(build)=$(build-dir) $(@:.ko=.o)

我们一起展开:

1
@make KBUILD_MODULES=1 -f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o

1.4 %.ko规则展开

上面我们对每一部分进行了分析,我们来展开一下%.ko的规则:

1
2
3
4
5
%.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

展开后为:

1
2
3
4
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.modpost

srctree这个值为.,定义在 Makefile - srctree

2. Makefile.build

上面已经把%.ko目标展开了,接下来我们来分析这个命令:

1
$(Q)make KBUILD_MODULES=1 -f ./scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o

make -f 命令用于指定 Makefile 文件的名称。默认情况下,make 会查找名为 Makefilemakefile 的文件。项目中使用了不同的文件名,可以通过 -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

1
src := $(obj)

可以看到其实src和obj是相等的。

2.2 $(obj)/%.o目标

2.2.1 匹配哪一个目标?

$(obj)/%.o这个目标在Makefile.build - scripts/Makefile.build文件中有三处定义:

1
2
3
4
# 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)
1
2
3
4
5
6
7
# 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)
1
2
$(obj)/%.o: $(src)/%.S $(objtool_dep) FORCE
$(call if_changed_rule,as_o_S)

那我们使用的这个编译命令:

1
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 文件中被调用。具体是什么,这里就暂时先不深入去追了。直接加打印信息吧,两个位置都加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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)

然后我们执行:

1
2
3
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 # 编译指定模块

可以看到有以下打印信息:

1
2
3
4
5
6
7
8
9
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):

1
2
3
4
5
6
7
# 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)

接下来我们来看这个命令:

1
$(call cmd,force_checksrc)

这个其实应该很熟悉了,cmd函数定义在:Kbuild.include - scripts/Kbuild.include - cmd

1
2
3
4
5
6
7
# 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

1
2
3
4
5
6
7
8
9
10
# 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

KBUILD_CHECKSRC定义在Makefile - KBUILD_CHECKSRC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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

1
2
3
4
5
6
# 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

1
2
3
# 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

1
2
3
4
5
6
7
8
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)
endif
2.2.3.3 $(rule_$(1))

$(1)就是cc_o_c,所以这里展开就是:

1
$(rule_$(1)) = $(rule_cc_o_c)

rule_cc_o_c定义在Makefile.build - scripts/Makefile.build - rule_cc_o_c

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
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

3.1 checksrc

我们来看一下这一行Makefile.build - scripts/Makefile.build

1
$(call echo-cmd,checksrc) $(cmd_checksrc)

3.1.1 $(cmd_checksrc)

先看这个cmd_checksrc,它定义在Makefile.build - scripts/Makefile.build - cmd_checksrc

1
2
3
4
5
6
7
8
9
10
# 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

1
2
3
4
# 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_。所以这里展开是:

1
2
3
4
5
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)

接下来来看一下这个命令:

1
$(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选择不同的命令:

1
2
3
4
5
6
7
8
9
10
ifndef CONFIG_TRIM_UNUSED_KSYMS
# (1)
cmd_and_fixdep = \
#......
else
#......
# (2)
cmd_and_fixdep = \
# ......
endif

我们搜一下这个CONFIG_TRIM_UNUSED_KSYMS配置项,会发现是没有的,所以这里执行(1)的命令:

1
2
3
4
5
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,所以这里是:

1
2
3
4
5
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

1
2
3
4
# 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
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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中有定义:

1
CONFIG_MODVERSIONS=y

所以这里就是:

1
cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<

显然是非空的,这里if成立,就是:

1
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,的值为

1
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

1
2
3
4
5
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行:

1
2
3
4
5
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

1
2
3
4
5
6
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;

然后执行以下命令:

1
2
3
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文件的,所以调用的地方有很多,我们加的打印信息也会出现很多次,我们找这个:

1
2
3
4
5
6
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.c

info(2)这里是前面 二、make dir/file.ko部分的2.2 $(obj)/%.o目标 这一个小节添加的,刚好帮我们定位我们所需要的 cmd_and_fixdep info,整理一下:

1
2
3
4
5
6
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

1
2
3
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

1
2
3
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

1
$(call echo-cmd,objtool) $(cmd_objtool)

3.4.1 $(cmd_objtool)

先看这个cmd_objtool,它定义在Makefile.build - scripts/Makefile.build - cmd_objtool

1
2
3
4
5
6
7
8
9
# '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

1
2
3
4
5
6
7
8
9
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是先替替换函数内容然后执行,所以可能意义也不大)。值然后执行以下命令:

1
2
3
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 # 编译指定模块

会看到如下打印信息:

1
scripts/Makefile.build:311: cmd_objtool=

所以在单独编译这个模块的时候这里为空,就先不管了。

3.4.2 $(call echo-cmd,objtool)

再来看一下这个echo-cmd函数,前面分析过的,echo-cmd定义在Kbuild.include - scripts/Kbuild.include - echo-cmd

1
2
3
4
# 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_。所以这里展开是:

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
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

然后继续执行以下命令:

1
2
3
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 # 编译指定模块

会看到如下打印信息:

1
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;

整理一下就是:

1
2
3
4
5
6
7
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

1
2
3
4
define rule_cc_o_c
#......
$(call echo-cmd,record_mcount) $(cmd_record_mcount)
endef

3.6.1 $(cmd_record_mcount)

cmd_record_mcount定义在Makefile.build - scripts/Makefile.build - cmd_record_mcount

1
2
3
4
5
cmd_record_mcount =						\
if [ "$(findstring $(CC_FLAGS_FTRACE),$(_c_flags))" = \
"$(CC_FLAGS_FTRACE)" ]; then \
$(sub_cmd_record_mcount) \
fi;

还是省点事,直接在这里加打印信息:Makefile.build - scripts/Makefile.build

1
2
3
4
5
6
7
8
9
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

然后还是执行:

1
2
3
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 # 编译指定模块

会看到以下打印信息:

1
2
3
4
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

1
2
3
4
# 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_。所以这里展开是:

1
2
3
4
5
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](https://elixir.bootlin.com/linux/v4.19.71/source/scripts/Makefile.build#L279):

1
2
3
4
5
6
7
8
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的时候,这个函数里面其实可以简化为:

1
2
3
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目标的最后一条命令:

1
$(Q)make -f $(srctree)/scripts/Makefile.modpost

这里的srctree=.,所以要分析的是这个文件:Makefile.modpost - scripts/Makefile.modpost。先来看一下文件开头的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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

1
2
3
4
5
6
7
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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 所有出现的目标

简化一下,看看所有出现的目标吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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行又出现了它的新的依赖,那么怎么执行的?我们来试一下一个目标先后出现不同的规则的时候怎么运行的:

1
2
3
4
5
6
7
8
9
10
11
12
PHONY += test
test: test2

test: test1

test1:
@echo "test1"

test2:
@echo "test2"

.PHONY: $(PHONY)

我们执行make,会有如下打印信息:

1
2
test1
test2

实践证明,只要是目标依赖的目标中的规则都会执行,并且先出现的先执行。所以这里我们大概就可以知道怎么去跟这部分的代码了。

4.2 _modpost目标

默认目标是哪个?一般来说makefile的第一个目标就是默认目标,刚才大分析文件结构的时候大概已经清楚默认目标了,我们打开Makefile.modpost - scripts/Makefile.modpost,看到的第一个目标就是:

1
2
PHONY := _modpost
_modpost: __modpost

可以看到_modpost又依赖于__modpost,这个时候就会先去完成__modpost目标中的规则。

4.3 __modpost目标

接下来先来看这个__modpost目标,它定义在 Makefile.modpost - scripts/Makefile.modpost - __modpost目标

1
2
3
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

1
2
3
4
5
6
7
# 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

1
2
3
# 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

1
MODLISTCMD := find $(MODVERDIR) -name '*.mod' | xargs -r grep -h '\.ko$$' | sort -u

MODPOST_OPT定义在Makefile.modpost - scripts/Makefile.modpost - MODPOST_OPT

1
MODPOST_OPT=$(subst -i,-n,$(filter -i,$(MAKEFLAGS)))

modpost定义在Makefile.modpost - scripts/Makefile.modpost - modpost

1
2
3
4
5
6
7
8
9
10
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函数,所以是带有命令回显的:

1
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 函数用于查找匹配指定模式的文件,并返回这些文件的列表。这个函数在处理文件路径和文件名时非常有用,特别是在需要动态生成文件列表的情况下。一般用法如下:

1
$(wildcard 指定文件类型)

所以这里其实是在匹配当前路径下的vmlinux文件。显然这里为空。

4.3.3 $(modules:.ko=.o)

前面__modpost目标还依赖于$(modules:.ko=.o),我们来看一下这是个什么。再来看一下这个依赖项,这个内部其实是makefile中的字符串替换语法,就是把modules中所有成员的.ko替换成.o。modules变量定义在 Makefile.modpost - scripts/Makefile.modpost - modules

1
modules   := $(patsubst %.o,%.ko, $(wildcard $(__modules:.ko=.o)))

这里直接在这个位置加个打印信息吧:Makefile.modpost - scripts/Makefile.modpost

1
2
3
4
PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
$(warning "info modules=$(modules) (modules:.ko=.o)=$(modules:.ko=.o)")
$(call cmd,modpost) $(wildcard vmlinux)

然后执行以下命令:

1
2
3
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

可以看到如下打印信息:

1
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目标中的规则是在执行这条命令:

1
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

1
2
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))

直接在这里加打印信息吧,我是没找到在哪定义:

1
2
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))
$(warning KBUILD_MODPOST_NOFINAL=$(KBUILD_MODPOST_NOFINAL) (modules:.ko:.o)=$(modules:.ko:.o) modules=$(modules))

查看打印信息如下:

1
scripts/Makefile.modpost:70: KBUILD_MODPOST_NOFINAL= (modules:.ko:.o)= modules=drivers/media/i2c/ov5640.ko

所以其实上面展开就是:

1
2
# 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

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

1
2
3
4
5
# 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)至少有一个不会为空,所以这里简化为:

1
2
3
4
5
# 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

1
2
3
4
5
6
7
# 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,为了方便区分,我们在这里加一条打印:

1
2
3
4
5
6
     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)来打印,会报错。然后我们执行:

1
2
3
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

会看到如下打印信息:

1
2
  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的命令回显啦:

1
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:

image-20241129001133528