Linux内核的Makefile中cmd-check是如何检查前后两次执行的命令是一致的?

  • 2020 年 2 月 17 日
  • 筆記

Linux内核的构建工具用的是GNU Make,在其相关的Makefile中,有一个变量叫做cmd-check,其定义如下:

# Check if both commands are the same including their order. Result is empty  # string if equal. User may override this check using make KBUILD_NOCMDDEP=1  cmd-check = $(filter-out $(subst $(space),$(space_escape),$(strip $(cmd_$@))),                            $(subst $(space),$(space_escape),$(strip $(cmd_$1))))

它在if_changed等相关命令中会被用到:

# Execute command if command has changed or prerequisite(s) are updated.  if_changed = $(if $(newer-prereqs)$(cmd-check),                                        $(cmd);                                                                        printf '%sn' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

再来看下if_changed命令的具体使用场景:

# Final link of vmlinux with optional arch pass after final link  cmd_link-vmlinux =                                                           $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ;              $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)    vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE          +$(call if_changed,link-vmlinux)

在该段内容中,定义了一个名为vmlinux的target,在它的recipe中,通过call指令调用了if_changed命令,传递的参数为link-vmlinux。

在if_changed命令中,如果$(newer-prereqs)$(cmd-check)部分展开后的结果不为空,则执行后面$(cmd)指向的命令,并将该命令用printf输出到特定的文件中。

对于vmlinux这个target来说,$(cmd)最终指向的就是上面cmd_link-vmlinux变量对应的命令,而printf输出的最终文件名为.vmlinux.cmd,内容为cmd_vmlinux := cmd_link-vmlinux变量对应的命令。

在if_changed的命令中,$(newer-prereqs)表示的是,是否有prerequisites比vmlinux这个target还新,$(cmd-check)表示的是,$(cmd_$@)是否和$(cmd_$1)相同。

对于vmlinux来说,$(cmd_$@)展开后的结果是cmd_vmlinux,$(cmd_$1)展开后的结果是cmd_link-vmlinux。

cmd_link-vmlinux在Makefile中是有明确定义的,但cmd_vmlinux在Makefile中却没法找到明确定义的地方,这个也是初次研究linux内核的Makefile的同学会感到困惑的地方。

为什么我找遍了所有相关的Makefile,就是没找到cmd_vmlinux的定义呢?

我们再来仔细想下,cmd-check的意图是什么?

是为了比较这次执行的命令和上次执行的命令是否相同。

如果两次命令相同,且$(newer-prereqs)结果为空,则此时if_changed后面的构建命令就不用执行了,因为在这两次构建过程中,不管是prerequisites还是构建命令,都没有发生任何变化。

这次的构建命令很容易获取,比如上面的cmd_link-vmlinux,是直接在Makefile中定义的,那上次的构建命令怎么获取呢?

对,肯定是保存到哪个文件里了。

再来回忆下if_changed命令,看下其中的printf部分,这不正是用来保存该次执行命令到特定文件的嘛。

知道了上次执行的命令被保存到了哪里,我们再来看下Makefile是如何使用它们的。

首先看下linux内核根目录里的Makefile,其中有如下定义:

targets := vmlinux

再来看下该变量是如何被使用的:

# read saved command lines for existing targets  existing-targets := $(wildcard $(sort $(targets)))    -include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)

看到没,最后是用了make的inlcude指令,将.vmlinux.cmd文件内容当作Makefile内容格式被读了进来。

而.vmlinux.cmd文件中的内容我们前文也提到了,就是cmd_vmlinux := cmd_link-vmlinux对应的命令。

这样,cmd-check中的$(cmd_$@)部分指向的内容我们也找到了。

我们来实际操作看下,先来构建vmlinux:

$ make mrproper defconfig vmlinux

看下该过程生成的文件.vmlinux.cmd文件:

cmd_vmlinux := sh scripts/link-vmlinux.sh ld -m elf_x86_64  -z max-page-size=0x200000 --emit-relocs --discard-none --build-id ;  true

该文件里的内容和我们上文分析的是一样的。

cmd-check里的命令比较逻辑,相对来说还是比较绕的,如果正在研究linux内核的同学恰巧遇到了这个问题,希望本文能对你有所帮助。