Linux内核的构建工具用的是GNU Make,在其相关的Makefile中,有一个变量叫做cmd-check,其定义如下:
代码语言:javascript复制# 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等相关命令中会被用到:
代码语言:javascript复制# 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命令的具体使用场景:
代码语言:javascript复制# 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,其中有如下定义:
代码语言:javascript复制targets := vmlinux
再来看下该变量是如何被使用的:
代码语言:javascript复制# 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:
代码语言:javascript复制$ make mrproper defconfig vmlinux
看下该过程生成的文件.vmlinux.cmd文件:
代码语言:javascript复制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内核的同学恰巧遇到了这个问题,希望本文能对你有所帮助。