linux Kbuild详解关于fixdep、Q、quiet、escsq

linux Kbuild详解关于if_changed_rule的any-prereq和arg-check原理及info调试关于fixdep没有展开,这里说下。

文章目录

  • [1. escsq](#1. escsq)
  • [2. Q、quiet](#2. Q、quiet)
    • [2. 1 make V=(0、1、2)](#2. 1 make V=(0、1、2))
    • [2. 2 make V=(0、1)来控制Q、quiet](#2. 2 make V=(0、1)来控制Q、quiet)
  • [3. fixdep](#3. fixdep)
    • [3. 1 fixdep是什么](#3. 1 fixdep是什么)
    • [3. 2 fixdep为什么](#3. 2 fixdep为什么)
      • [3.2.1 .config和autoconf.h的关系](#3.2.1 .config和autoconf.h的关系)
      • [3.2.2 autoconf.h被引用](#3.2.2 autoconf.h被引用)
      • [3.2.3 GNU产生的依赖均包含autoconf.h](#3.2.3 GNU产生的依赖均包含autoconf.h)
      • [3.2.4 全局头文件autoconf.h改动的影响](#3.2.4 全局头文件autoconf.h改动的影响)
      • [3.2.5 fixdep登场了](#3.2.5 fixdep登场了)
    • [3. 3 fixdep怎么做](#3. 3 fixdep怎么做)
      • [3.3.1 fixdep参数depfile](#3.3.1 fixdep参数depfile)
      • [3.3.2 fixdep参数target](#3.3.2 fixdep参数target)
      • [3.3.3 fixdep参数cmdline](#3.3.3 fixdep参数cmdline)
      • [3.3.4 fixdep输出](#3.3.4 fixdep输出)
      • [3.3.5 过程总结](#3.3.5 过程总结)
  • [4. 参考](#4. 参考)

1. escsq

bash 复制代码
quote   := "
squote  := '
# Escape single quote for use in echo statements
escsq = $(subst $(squote),'\$(squote)',$1)

escsq :转义(esc ape)单引号 (s ingle q uote)

(subst from, to, var)的用法:将var中的from部分替换成$to。

因此上述代码就很明确了:(squote)是单引号,'(squote)'是双引号,替换。

2. Q、quiet

2. 1 make V=(0、1、2)

V含义为verbose,表示详细的,即打印更多的信息 。

bash 复制代码
@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
bash 复制代码
ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
#如果V值来源于命令行,则KBUILD_VERBOSE从$(V)获取值

ifeq ($(KBUILD_VERBOSE),1)	#V=1
  quiet =
  Q =
else						#V=0
  quiet=quiet_
  Q = @
endif

示例如下:

bash 复制代码
%o: %c
	$(Q)$(CC) -c $< -o $@

如Q=@就能控制编译$(CC)命令不输出。那还需要quiet做什么?

  • 控制quiet
    quiet有什么神奇的力量?其实quiet并没有什么神通的力量,只是一个普通的变量,用于生成特定区分详细程度或参数不同(如quiet_cmd_xx、cmd_xx)的命令。
    看一个例子:
bash 复制代码
quiet_cmd_checksrc     = CHECK   $<
cmd_checksrc     = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ;

两条命令可化成一条命令为:$(quiet)cmd_checksrc 进行操作了。

3. fixdep

3. 1 fixdep是什么

fixdep是一个用Host主机从源文件fixdep.c编译得到的整理依赖的可执行工具:

bash 复制代码
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

执行提示如下:

bash 复制代码
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,逻辑不复杂,代码中更像详细点的介绍:

bash 复制代码
 * 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看作其他配置文件的来源,其形式为:

bash 复制代码
...
CONFIG_X86=y
CONFIG_HZ=250
CONFIG_INSTRUCTION_DECODER=y
...

而C文件不能使用这样的信息,kbuild conf从.config转换得到autoconf.h作为所有C文件公共配置项头文件,其形式为:

c 复制代码
...
#define CONFIG_X86 1
#define CONFIG_HZ 250
#define CONFIG_INSTRUCTION_DECODER 1
...

3.2.2 autoconf.h被引用

在scripts/Kbuild.include中:

bash 复制代码
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中:

bash 复制代码
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中定义如下:

bash 复制代码
 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中:

bash 复制代码
--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,如下第一行:

bash 复制代码
main.o: init/main.c include/linux/kconfig.h include/generated/autoconf.h \  #autoconf.h
 include/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)就被删除了。为调试研究,我们将删除项去掉,如下:

bash 复制代码
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_KSYMS
 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;
 
 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及输出展开:

bash 复制代码
fixdep <depfile> <target> <cmdline>

fixdep使用实例:

bash 复制代码
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)

bash 复制代码
#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)'

bash 复制代码
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,有兴趣可以看看其内容([...]有省略):

bash 复制代码
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.c

source_init/main.o := init/main.c

deps_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修改后依赖关系。

bash 复制代码
#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_$@:

如下图片看不清除,可以点击放大看

  1. 编译命令变量

  2. 源文件名称变量

  3. fixdep修改后的依赖变量

    生成依赖变量deps_@ ,包含两部分:第一部分从(depfile) (图中右侧绿色框),移除红框autoconf.h和main.c,剩下都包含进来;第二部分查找main.c中CONFIG_X_Y的宏,添加为$(wildcard include/config/x/y.h) \的依赖项。

  4. 声明了fixdep修改后依赖关系

    使用fixdep修改依赖关系后,不再使用GNU -MD生成的依赖关系作为判断依赖是否更新的依据,从而避免了依赖共享autoconf.h导致依赖关联的问题。

4. 参考

  1. linux Kbuild详解系列(8)-Kbuild中其他通用函数与变量
  2. auto.conf, auto.conf.cmd, autoconf.h
相关推荐
十五年专注C++开发5 小时前
cargs: 一个轻量级跨平台命令行参数解析库
linux·c++·windows·跨平台·命令行参数解析
Mr YiRan5 小时前
Android模拟简单的网络请求框架Retrofit实现
android·retrofit
三坛海会大神5555 小时前
Linux服务器资源自动监控与报警脚本详解
linux·运维·服务器
zh_xuan9 小时前
Android Looper源码阅读
android
向qian看_-_11 小时前
Linux 使用pip报错(error: externally-managed-environment )解决方案
linux·python·pip
yaoxtao12 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
2501_9200470314 小时前
git在Linux中的使用
linux·git·elasticsearch
程序设计实验室14 小时前
在鸡哥14x上安装Linux:Fedora 42 上手体验
linux
UNbuff_015 小时前
Linux bzip2 命令使用说明
linux·运维·服务器