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 | PHONY += help |
2. 模块编译命令
从帮助信息中可以知道,我们可以通过modules等相关命令编译模块:
1 | make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j16 # 编译所有的模块 |
二、函数说明
这里先来了解几个函数,后面会经常用到。
1. cmd函数
cmd函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
1 | # echo command. |
主要是它包含了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 | ### |
这个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 | ### |
这个还挺复杂的,但是最开始有注释,enabled by make V=2
,所以这里我们直接忽略,以后用到再说吧。
1.1.3 总结
我们简化一下echo-cmd:
1 | # echo command. |
其实就只是把echo-why去掉。
1.2 $(cmd_$(1))
这个其实没什么说的,$(1)
就是传入的参数,替换后就是一个变量。例如$(call cmd,param)
,这里·$(1)
=param,所以就是$(cmd_param)
,随后找到cmd_param变量即可。
1.3 cmd到底在做什么?
前面大概分析了两个变量,这里我们写一个demo来测试一下:
1 | squote := ' |
然后执行,就会发现,$(echo-cmd)
部分负责显示命令,$(cmd_$(1))
负责执行命令:
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 | %.ko: prepare scripts FORCE |
我们编译的命令为:
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 | # Create temporary dir for module support files |
这里就不详细去找定义在哪里了,直接在%.ko目标的规则中加点打印信息看一下:
1 | %.ko: prepare scripts FORCE |
然后执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到以下打印信息:
1 | echo "MODVERDIR=.tmp_versions KBUILD_MODULES=" |
所以cmd_crmodverdir展开就是:
1 | cmd_crmodverdir = $(Q)mkdir -p .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 | ### |
1.3.2 build-dir
build-dir定义在 Makefile - build-dir
1 | # Single targets are compatible with: |
可以看到还有个变量KBUILD_EXTMOD,它定义在 Makefile - KBUILD_EXTMOD:
1 | ifdef SUBDIRS |
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 | $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ |
我们一起展开:
1 | @make KBUILD_MODULES=1 -f $(srctree)/scripts/Makefile.build obj=drivers/media/i2c drivers/media/i2c/ov5640.o |
1.4 %.ko规则展开
上面我们对每一部分进行了分析,我们来展开一下%.ko
的规则:
1 | %.ko: prepare scripts FORCE |
展开后为:
1 | drivers/media/i2c/ov5640.ko: prepare scripts FORCE |
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
会查找名为 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
1 | src := $(obj) |
可以看到其实src和obj是相等的。
2.2 $(obj)/%.o
目标
2.2.1 匹配哪一个目标?
$(obj)/%.o
这个目标在Makefile.build - scripts/Makefile.build文件中有三处定义:
1 | # Built-in and composite module parts |
1 | # Single-part modules are special since we need to mark them in $(MODVERDIR) |
1 | $(obj)/%.o: $(src)/%.S $(objtool_dep) FORCE |
那我们使用的这个编译命令:
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 | # Built-in and composite module parts |
然后我们执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
可以看到有以下打印信息:
1 | echo "info(1) obj=scripts/mod src=scripts/mod scripts/mod/empty.o scripts/mod/empty.c" |
所以这里其实两个地方都用的到了,但是完成ov5640.c文件编译的目标其实是(2):
1 | # Single-part modules are special since we need to mark them in $(MODVERDIR) |
2.2.2 $(call cmd,force_checksrc)
接下来我们来看这个命令:
1 | $(call cmd,force_checksrc) |
这个其实应该很熟悉了,cmd函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
1 | # echo command. |
直接看$(cmd_$(1))
吧,这个$(1)
=force_checksrc,所以这里其实调用的就是cmd_force_checksrc,它定义在Makefile.build - scripts/Makefile.build - cmd_force_checksrc:
1 | # Linus' kernel sanity checking tool |
KBUILD_CHECKSRC定义在Makefile - KBUILD_CHECKSRC:
1 | # Call a source code checker (by default, "sparse") as part of the |
这个其实就是代码检查,由于我们没有传入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 | # Usage: $(call if_changed_rule,foo) |
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 | # Find any prerequisites that is newer than target or that does not exist. |
2.2.3.2 arg-check
arg-check定义在 Kbuild.include - scripts/Kbuild.include - arg-check:
1 | ifneq ($(KBUILD_NOCMDDEP),1) |
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 | define rule_cc_o_c |
其实到这里,$(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 | define rule_cc_o_c |
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 | # Linus' kernel sanity checking tool |
会发现,这里其实和我们分析的 二、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 | # echo command. |
这里的$(1)
=checksrc,quiet定义在Makefile - quiet,分析一下就可以知道,当我们执行make V=1
的时候,这里quiet为空,当执行make V=0
的时候,quiet=quiet_。所以这里展开是:
1 | echo-cmd = $(if $(cmd_checksrc),\ |
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 | ifndef CONFIG_TRIM_UNUSED_KSYMS |
我们搜一下这个CONFIG_TRIM_UNUSED_KSYMS配置项,会发现是没有的,所以这里执行(1)的命令:
1 | cmd_and_fixdep = \ |
$(1)
=cc_o_c,所以这里是:
1 | cmd_and_fixdep = \ |
3.2.1 $(echo-cmd)
echo-cmd定义在Kbuild.include - echo-cmd:
1 | # echo command. |
根据前面分析的这里就是:
1 | echo-cmd = $(if $(cmd_cc_o_c),\ |
这里按make V=1
的结果进行的分析,那我们要找的就是cmd_cc_o_c,其实最终他们执行的都是一样的,只不过就是打印信息的区别。cmd_cc_o_c定义在这个位置:Makefile.build - scripts/Makefile.build - cmd_cc_o_c
1 | # C (.c) files |
我们搜索一下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 | cmd_and_fixdep = \ |
我们这里再继续展开一下第2行:
1 | cmd_and_fixdep = \ |
在这里加个打印信息吧Kbuild.include - scripts/Kbuild.include - cmd_and_fixdep:
1 | cmd_and_fixdep = \ |
然后执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
其实cmd_and_fixdep命令就是用来编译C文件的,所以调用的地方有很多,我们加的打印信息也会出现很多次,我们找这个:
1 | mkdir -p .tmp_versions |
info(2)这里是前面 二、make dir/file.ko部分的2.2 $(obj)/%.o
目标 这一个小节添加的,刚好帮我们定位我们所需要的 cmd_and_fixdep info,整理一下:
1 | cmd_and_fixdep info: |
所以总的来说,$(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 | ifneq ($(KBUILD_ENABLE_EXTRA_GCC_CHECKS),) |
我们搜索一下这个KBUILD_ENABLE_EXTRA_GCC_CHECKS,会发现它定义在Makefile.extrawarn - scripts/Makefile.extrawarn - KBUILD_ENABLE_EXTRA_GCC_CHECKS:
1 | ifeq ("$(origin W)", "command line") |
可以看到这里是从命令行读取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 | # 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory |
这里直接加个打印信息看下结果吧,就在这个位置加:Makefile.build - scripts/Makefile.build
1 | define rule_cc_o_c |
在这里我加了$(warning <text>)
函数,它不用像echo一样有限制,echo只能在target:后面的语句中使用,且前面是个TAB。用这个函数还可以打印出行号(不过这个行号感觉和文件中的有些区别,因为Makefile是先替替换函数内容然后执行,所以可能意义也不大)。值然后执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到如下打印信息:
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 | # echo command. |
这里的$(1)
=objtool,quiet定义在Makefile - quiet,分析一下就可以知道,当我们执行make V=1
的时候,这里quiet为空,当执行make V=0
的时候,quiet=quiet_。所以这里展开是:
1 | echo-cmd = $(if $(cmd_objtool),\ |
前面其实分析过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 | cmd_modversions_c = \ |
又是一堆的变量,这里直接接打印信息吧:Makefile.build - scripts/Makefile.build
1 | define rule_cc_o_c |
然后继续执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到如下打印信息:
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 | cmd_modversions_c= |
3.6 record_mcount
接下来看一下 rule_cc_o_c 的最后一条命令:Makefile.build - scripts/Makefile.build
1 | define rule_cc_o_c |
3.6.1 $(cmd_record_mcount)
cmd_record_mcount定义在Makefile.build - scripts/Makefile.build - cmd_record_mcount:
1 | cmd_record_mcount = \ |
还是省点事,直接在这里加打印信息:Makefile.build - scripts/Makefile.build
1 | define rule_cc_o_c |
然后还是执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到以下打印信息:
1 | mkdir -p .tmp_versions |
可以看到这里还是为空。
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 | # echo command. |
这里的$(1)
=record_mcount,quiet定义在Makefile - quiet,分析一下就可以知道,当我们执行make V=1
的时候,这里quiet为空,当执行make V=0
的时候,quiet=quiet_。所以这里展开是:
1 | echo-cmd = $(if $(cmd_record_mcount),\ |
前面其实分析过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 | define rule_cc_o_c |
在我们单独编译drivers/media/i2c/ov5640.ko的时候,这个函数里面其实可以简化为:
1 | define rule_cc_o_c |
这一条命令就是将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 | # Stage one of module building created the following: |
这里其实就是一个构建ko驱动文件的说明,主要分了4个阶段。我们打开文件就会发现,文件中有一些注释来告诉我们哪些是第一阶段,哪些是第二阶段。为什么要先看这个?因为我没找到默认目标,属于完全没看懂的样子,然后就发现这个文件中有不同阶段的注释,先来看一看吧。
4.1.1 Step 1
Makefile.modpost - scripts/Makefile.modpost - Step 1:
1 | # Step 1), find all modules listed in $(MODVERDIR)/ |
4.1.2 Step 2/3/4
Makefile.modpost - scripts/Makefile.modpost - Step 2/3/4:
1 | # Step 2), invoke modpost |
4.1.3 Step 5
Makefile.modpost - scripts/Makefile.modpost - Step 5:
1 | # Step 5), compile all *.mod.c files |
4.1.4 所有出现的目标
简化一下,看看所有出现的目标吧:
1 |
|
可以看到 _modpost是出现的第一个规则,他应该是默认规则,这个规则第一次出现的时候依赖于__modpost目标,然后往后就会发现,第7行又出现了它的新的依赖,那么怎么执行的?我们来试一下一个目标先后出现不同的规则的时候怎么运行的:
1 | PHONY += test |
我们执行make,会有如下打印信息:
1 | test1 |
实践证明,只要是目标依赖的目标中的规则都会执行,并且先出现的先执行。所以这里我们大概就可以知道怎么去跟这部分的代码了。
4.2 _modpost目标
默认目标是哪个?一般来说makefile的第一个目标就是默认目标,刚才大分析文件结构的时候大概已经清楚默认目标了,我们打开Makefile.modpost - scripts/Makefile.modpost,看到的第一个目标就是:
1 | PHONY := _modpost |
可以看到_modpost又依赖于__modpost,这个时候就会先去完成__modpost目标中的规则。
4.3 __modpost目标
接下来先来看这个__modpost目标,它定义在 Makefile.modpost - scripts/Makefile.modpost - __modpost目标:
1 | PHONY += __modpost |
4.3.1 $(call cmd,modpost)
先来看这个$(call cmd,modpost)
命令,cmd函数定义在:Kbuild.include - scripts/Kbuild.include - cmd:
1 | # echo command. |
直接看$(cmd_$(1))
,前面cmd函数调用的时候传入的参数是modpost,所以$(1)
=modpost,这里就是$(cmd_modpost)
,定义在Makefile.modpost - scripts/Makefile.modpost - cmd_modpost:
1 | # We can go over command line length here, so be careful. |
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 | modpost = scripts/mod/modpost \ |
这里涉及的变量较多,我们直接看打印信息吧,因为它调用的是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 | PHONY += __modpost |
然后执行以下命令:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
可以看到如下打印信息:
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 | # Stop after building .o files if NOFINAL is set. Makes compile tests quicker |
直接在这里加打印信息吧,我是没找到在哪定义:
1 | _modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules)) |
查看打印信息如下:
1 | scripts/Makefile.modpost:70: KBUILD_MODPOST_NOFINAL= (modules:.ko:.o)= modules=drivers/media/i2c/ov5640.ko |
所以其实上面展开就是:
1 | # Stop after building .o files if NOFINAL is set. Makes compile tests quicker |
就这样,又产生了一个要追的目标$(modules)
。
4.5 $(modules)
目标
接下来我们来看$(modules)
目标,它定义在Makefile.modpost - scripts/Makefile.modpost - modules:
1 | $(modules): %.ko :%.o %.mod.o FORCE |
4.5.1 if_changed函数
if_changed函数定义在Kbuild.include - scripts/Kbuild.include - if_changed:
1 | # Execute command if command has changed or prerequisite(s) are updated. |
由于我么要生成ko文件,这里$(any-prereq)
和 $(arg-check)
至少有一个不会为空,所以这里简化为:
1 | # Execute command if command has changed or prerequisite(s) are updated. |
前面其实分析过$(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 | # Step 6), final link of the modules with optional arch pass after final link |
那这句是什么?其实吧前面分析过的$(echo-cmd)
可以回显命令的,显示的就是后面的$(cmd_$(1))
在这里就是cmd_ld_ko_o,为了方便区分,我们在这里加一条打印:
1 | cmd_ld_ko_o = \ |
这里不能用$(warning)
来打印,会报错。然后我们执行:
1 | make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean |
会看到如下打印信息:
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 |
所以其实下面这条就是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: