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
相关推荐
chlk12312 小时前
Linux文件权限完全图解:读懂 ls -l 和 chmod 755 背后的秘密
linux·操作系统
阿巴斯甜12 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
舒一笑13 小时前
Ubuntu系统安装CodeX出现问题
linux·后端
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
改一下配置文件13 小时前
Ubuntu24.04安装NVIDIA驱动完整指南(含Secure Boot解决方案)
linux
xq952714 小时前
Andorid Google 登录接入文档
android
黄林晴15 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
深紫色的三北六号1 天前
Linux 服务器磁盘扩容与目录迁移:rsync + bind mount 实现服务无感迁移(无需修改配置)
linux·扩容·服务迁移
SudosuBash1 天前
[CS:APP 3e] 关于对 第 12 章 读/写者的一点思考和题解 (作业 12.19,12.20,12.21)
linux·并发·操作系统(os)
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读