linux Kbuild详解关于if_changed_rule的any-prereq和arg-check原理及info调试关于fixdep没有展开,这里说下。
文章目录
- 1. escsq
- 2. Q、quiet
- 2. 1 make V=(0、1、2)
- 2. 2 make V=(0、1)来控制Q、quiet
- 3. fixdep
- 3. 1 fixdep是什么
- 3. 2 fixdep为什么
- 3.2.1 .config和autoconf.h的关系
- 3.2.2 autoconf.h被引用
- 3.2.3 GNU产生的依赖均包含autoconf.h
- 3.2.4 全局头文件autoconf.h改动的影响
- 3.2.5 fixdep登场了
- 3. 3 fixdep怎么做
- 3.3.1 fixdep参数depfile
- 3.3.2 fixdep参数target
- 3.3.3 fixdep参数cmdline
- 3.3.4 fixdep输出
- 3.3.5 过程总结
- 4. 参考
1. escsq
quote := "
squote := '
# Escape single quote for use in echo statements
escsq = $(subst $(squote),'\$(squote)',$1)
escsq :转义(escape)单引号 (single quote)
$(subst $from, $to, $var)的用法:将$var中的$from部分替换成$to。
因此上述代码就很明确了:$(squote)是单引号,'$(squote)'是双引号,替换。
2. Q、quiet
2. 1 make V=(0、1、2)
V含义为verbose,表示详细的,即打印更多的信息 。
@echo ' make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build'
@echo ' make V=2 [targets] 2 => give reason for rebuild of target'
- V=0:表示不输出编译信息。在编译时不指定V时,默认 V=0。
- V=1:表示输出详尽编译信息。
- V=2:给出重新编译目标的理由。
2. 2 make V=(0、1)来控制Q、quiet
- 控制Q值
ifeq ("$(origin V)", "command line")KBUILD_VERBOSE = $(V)
endif
#如果V值来源于命令行,则KBUILD_VERBOSE从$(V)获取值ifeq ($(KBUILD_VERBOSE),1) #V=1quiet =Q =
else #V=0quiet=quiet_Q = @
endif
示例如下:
%o: %c$(Q)$(CC) -c $< -o $@
如Q=@就能控制编译$(CC)命令不输出。那还需要quiet做什么?
- 控制quiet值
quiet有什么神奇的力量?其实quiet并没有什么神通的力量,只是一个普通的变量,用于生成特定区分详细程度或参数不同(如quiet_cmd_xx、cmd_xx)的命令。
看一个例子:
quiet_cmd_checksrc = CHECK $<
cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;
两条命令可化成一条命令为:$(quiet)cmd_checksrc 进行操作了。
3. fixdep
3. 1 fixdep是什么
fixdep是一个用Host主机从源文件fixdep.c编译得到的整理依赖的可执行工具:
xx@xx-vb:~/Downloads/linux$ file scripts/basic/fixdep
scripts/basic/fixdep: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3b57a9ce45b07c8ea86176d2002706756d35e692, for GNU/Linux 3.2.0, not stripped
执行提示如下:
xx@xx-vb:~/Downloads/linux$ scripts/basic/fixdep
Usage: fixdep [-e] <depfile> <target> <cmdline>-e insert extra dependencies given on stdin
代码在scripts/basic/fixdep.c,逻辑不复杂,代码中更像详细点的介绍:
* It is invoked as** fixdep <depfile> <target> <cmdline>** and will read the dependency file <depfile>** The transformed dependency snipped is written to stdout.** It first generates a line** cmd_<target> = <cmdline>** and then basically copies the .<target>.d file to stdout, in the* process filtering out the dependency on autoconf.h and adding* dependencies on include/config/MY_OPTION for every* CONFIG_MY_OPTION encountered in any of the prerequisites.
简而言之:它输入<depfile>,<target>,和编译该<target>的编译命令<cmdline>,基本上拷贝.<target>.d的文件内容到标准输出,但移除其中的对autoconf.h的依赖,将依赖文件中形如CONFIG_MY_OPTION的配置项转成include/config/MY_OPTION的文件作为依赖。可能迷糊的你,依然好奇,这是个啥玩意?!先看fixdep为什么。
3. 2 fixdep为什么
3.2.1 .config和autoconf.h的关系
编译内核前,通常需要进行make [xx]config,生成.config和各种由此产生的文件如tristate.conf、auto.conf、autoconf.h等(生成过程可参考文章 auto.conf, auto.conf.cmd, autoconf.h)。可以把 .config看作其他配置文件的来源,其形式为:
...
CONFIG_X86=y
CONFIG_HZ=250
CONFIG_INSTRUCTION_DECODER=y
...
而C文件不能使用这样的信息,kbuild conf从.config转换得到autoconf.h作为所有C文件公共配置项头文件,其形式为:
...
#define CONFIG_X86 1
#define CONFIG_HZ 250
#define CONFIG_INSTRUCTION_DECODER 1
...
3.2.2 autoconf.h被引用
在scripts/Kbuild.include中:
cmd_and_fixdep = \
272 $(echo-cmd) $(cmd_$(1)); \
273 scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp; \
274 rm -f $(depfile); \
275 mv -f $(dot-target).tmp $(dot-target).cmd;其中$(1)为 cmd_cc_o_c:
177 cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
272行执行编译命令展开为177行,其中c_flags在scripts/Makefile.lib中:
154 c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
155 -include $(srctree)/include/linux/compiler_types.h \
156 $(__c_flags) $(modkern_cflags) \
157 $(basename_flags) $(modname_flags)
154行包含$(LINUXINCLUDE):
LINUXINCLUDE在Makefile中定义如下:
412 LINUXINCLUDE := \413 -I$(srctree)/arch/$(SRCARCH)/include \414 -I$(objtree)/arch/$(SRCARCH)/include/generated \415 $(if $(KBUILD_SRC), -I$(srctree)/include) \416 -I$(objtree)/include \417 $(USERINCLUDE)
415行-I$(srctree)/include包含include目录下的头文件,这里包括 include/generated/autoconf.h。
3.2.3 GNU产生的依赖均包含autoconf.h
154行-Wp,-MD,$(depfile):编译器的–MD FILE选项将会把依赖文件输入到FILE中:
--MD FILE write dependency information in FILE (default none)
这个选项在cmd_and_fixdep的273行会将编译依赖生成到$(depfile)也即.<target>.d文件中,以init/main.c为例,其在177行cmd_cc_o_c命令下编译同时生成的init/.main.o.d文件,内容包含了autoconf.h,如下第一行:
main.o: init/main.c include/linux/kconfig.h include/generated/autoconf.h \ #autoconf.hinclude/linux/compiler_types.h include/linux/compiler-gcc.h \include/linux/types.h include/uapi/linux/types.h \...
其他文件大部分都会包含include/generated/autoconf.h,因为这是个全局的配置项头文件。想查看形如init/.main.o.d(一般化为$(depfile))文件内容的需要改下scripts/Kbuild.include,因为正常不使用它作为依赖根据,处理后$(depfile)就被删除了。为调试研究,我们将删除项去掉,如下:
xx@xx-vb:~/Downloads/linux$ git diff
diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include
index ce53639a864a..598489621347 100644
--- a/scripts/Kbuild.include
+++ b/scripts/Kbuild.include
@@ -271,7 +271,6 @@ ifndef CONFIG_TRIM_UNUSED_KSYMScmd_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;else
3.2.4 全局头文件autoconf.h改动的影响
上文描述了全局头文件autoconf.h被普遍引用的过程,如果使用-Wp,-MD,$(depfile)生成的$(depfile)中的依赖关系作为增量编译的判断依据,存在的问题是:A文件有个配置如CONFIG_HZ=250,B文件的配置CONFIG_INSTRUCTION_DECODER 1,两者可能不相关,但是A文件改动了配置为CONFIG_HZ=500,从而更新了autoconf.h。由于B也引用了autoconf.h,且B的依赖关系中也依赖autoconf.h文件,从而导致几乎所有的依赖关系包含autoconf.h的文件需要重新编译,这个显然不符合增量编译的设计。
3.2.5 fixdep登场了
fixdep就是来处理这个问题的,它需要把A:autoconf.h、B:autoconf.h的方式改为A:CONFIG_HZ、B:CONFIG_INSTRUCTION_DECODER的方式,这样互相就不会扯到蛋了。
具体改成依赖小写空文件的形式:
GNU -MD生成式 | fixdep调整 |
---|---|
A:autoconf.h(CONFIG_HZ) | A: include/config/hz.h |
B:autoconf.h(CONFIG_INSTRUCTION_DECODER) | B: include/config/instruction/decoder.h |
所有依赖CONFIG_HZ的文件都只依赖 include/config/hz.h,而不是依赖公共的autoconf.h,这样就做到了依赖的分离。
3. 3 fixdep怎么做
还是以init/main.c为例,就fixdep的参数depfile、target、cmdline及输出展开:
fixdep <depfile> <target> <cmdline>
fixdep使用实例:
cmd_and_fixdep = \
272 $(echo-cmd) $(cmd_$(1)); \
273 scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp; \
274 rm -f $(depfile); \
275 mv -f $(dot-target).tmp $(dot-target).cmd;
3.3.1 fixdep参数depfile
对应273行的$(depfile)
#ini/main.o => ini/.main.o
dot-target = $(dir $@).$(notdir $@)# 如果有逗号替换为_,这里返回的是ini/.main.o.d
depfile = $(subst $(comma),_,$(dot-target).d)
这里的$(depfile)=init/.main.o.d,这个文件哪里来的?它是272行执行cmd_cc_o_c,其中c_flags = -Wp,-MD,$(depfile) … 编译目标ini/main.o文件同时生成的。
3.3.2 fixdep参数target
对应273行的$@=ini/main.o
3.3.3 fixdep参数cmdline
对应273行的’$(make-cmd)’
make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1)))))
#--------------------------------------------------------------------------------
$(cmd_$(1))展开为:
177 cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
这里的’$(make-cmd)'=gcc -Wp,-MD,init/.main.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-linux-gnu/9/include -I./arch/x86/include -I./arch/x86/include/generated …很长。
3.3.4 fixdep输出
对应273行的输出$(dot-target).tmp后经275行的重命名得到$(dot-target).cmd=init/.main.o.cmd,有兴趣可以看看其内容([…]有省略):
cmd_init/main.o := gcc -Wp,-MD,init/.main.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-linux-gnu/9/include -I./arch/x86/include -I./arch/x86/include/generated -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -Wall -Wundef [...] -DKBUILD_BASENAME='"main"' -DKBUILD_MODNAME='"main"' -c -o init/main.o init/main.csource_init/main.o := init/main.cdeps_init/main.o := \$(wildcard include/config/init/env/arg/limit.h) \$(wildcard include/config/smp.h) \[...] $(wildcard include/config/strict/module/rwx.h) \include/linux/kconfig.h \$(wildcard include/config/cpu/big/endian.h) \$(wildcard include/config/booger.h) \$(wildcard include/config/foo.h) \include/linux/compiler_types.h \$(wildcard include/config/have/arch/compiler/h.h) \$(wildcard include/config/enable/must/check.h) \[...]include/trace/events/initcall.h \include/trace/define_trace.h \init/main.o: $(deps_init/main.o)$(deps_init/main.o):
3.3.5 过程总结
fixdep调整过的依赖关系保存文件$(depfile)中保存了:编译命令变量、源文件名称变量、fixdep修改后的依赖变量、声明fixdep修改后依赖关系。
#1.编译命令变量
cmd_$@ := '$(make-cmd)'#2.源文件名称变量
source_$@ := $(depfile)中C源文件#3.fixdep修改后的依赖变量
deps_$@ := $(depfile)中去除autoconf.h,并依据源码中CONFIG_X_Y增加$(wildcard include/config/x/y.h)依赖项#4.声明fixdep修改后依赖关系
$@: deps_$@
deps_$@:
如下图片看不清除,可以点击放大看
-
编译命令变量
-
源文件名称变量
-
fixdep修改后的依赖变量
生成依赖变量deps_$@ ,包含两部分:第一部分从$(depfile) (图中右侧绿色框),移除红框autoconf.h和main.c,剩下都包含进来;第二部分查找main.c中CONFIG_X_Y的宏,添加为$(wildcard include/config/x/y.h) \的依赖项。 -
声明了fixdep修改后依赖关系
使用fixdep修改依赖关系后,不再使用GNU -MD生成的依赖关系作为判断依赖是否更新的依据,从而避免了依赖共享autoconf.h导致依赖关联的问题。
4. 参考
- linux Kbuild详解系列(8)-Kbuild中其他通用函数与变量
- auto.conf, auto.conf.cmd, autoconf.h