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內核的同學恰巧遇到了這個問題,希望本文能對你有所幫助。