UBOOT編譯--- make xxx_deconfig過程詳解(一)

2022-11-03 06:01:49

 

 

1. 前言

 UBOOT版本:uboot2018.03,開發板myimx8mmek240。

2. 概述

Ubootb編譯第一步通常是執行make xxx_config,在編譯指定頂層目錄生成.config檔案,這種方式要求廠商提供一個基礎的xxx_config檔案(通常來說開發者不會通過執行make menuconfig從零開始設定,這個工作過量太大了)。本文接下來的章節主要解析這條指令背後主要做了什麼。我是用的開發板執行命令為:make myimx8mmek240-8mm-2g_defconfig

3. build變數的定義

在scripts/Kbuild.include 中定義:

###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

4. 目標%config的定義

在頂層Makefile中定義:

# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
include scripts/Kbuild.include            //注意這個參照

......

config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

4.1 依賴 scripts_basic

(參考:linux核心Makefile中的變數build— 過渡篇(五)

# 頂層Makefile
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

展開變數build

# 頂層Makefile
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount
 make -f $(srctree)/scripts/Makefile.build obj=scripts/basic的解析如下:
這是一種不指定目標的情況,由於未指定目標,這時會使用Makefile.build中的預設目標__build。然後更進一步,會使用$(obj)/Makefile(scripts/basic/Makefile)中定義的變數來進行目標匹配。

__build在Makefile.build中的構建規則為:

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)
	@:

4.1.1 語句$(if $ (KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y))

在頂層Makefile中,KBUILD_BUILTIN的定義如下:

# note:頂層Makefile
  KBUILD_BUILTIN := 1
  export KBUILD_MODULES KBUILD_BUILTIN

該語句展開為:

$(builtin-target) $(lib-target) $(extra-y)

(1)lib-target

# note:頂層Makefile
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
	lib-target := $(obj)/lib.a
endif  

在此語句之前obj-y := ;obj-m := ;obj-未定義。因此lib-target為空

(2)builtin-target ​​​​​​​

# note:頂層Makefile
ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
	builtin-target := $(obj)/built-in.o
endif

在此語句之前obj-y := ; obj-m := ;obj-未定義 ; subdir-m := ;並且在所包含的檔案中也沒有給這些變數增加值。lib-target 為空。因此builtin-target為空

(3)extra-y未定義
綜上:語句$(if $ (KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y))為空

4.1.2 語句 $(if $ (KBUILD_MODULES),$(obj-m) $(modorder-target))*

在頂層Makefile中,KBUILD_BUILTIN的定義如下:

# note:頂層Makefile
KBUILD_MODULES :=

綜上:語句$(if $ (KBUILD_MODULES),$(obj-m) $(modorder-target))為空

4.1.3 $(subdir-ym)

在scripts/Makefile.lib中,subdir-ym的定義如下:

# note:scripts/Makefile.lib
# Subdirectories we need to descend into
subdir-ym	:= $(sort $(subdir-y) $(subdir-m))
......
subdir-ym	:= $(addprefix $(obj)/,$(subdir-ym))

subdir-y與subdir-m都為空。
綜上:語句$(subdir-ym)為空

4.1.4 $(always) 重點關注

在scripts/Makefile.build有如下定義

# note:scripts/Makefile.build
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

4.1.4.1 src的定義

在scripts/Makefile.build有如下定義

# note:scripts/Makefile.build
# Modified for U-Boot
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif

在命令make -f ./scripts/Makefile.build obj=scripts/basic我們傳入了obj=scripts/basic
所以src = obj = scripts/basic, prefix := .

4.1.4.2 kbuild-dir的定義

在scripts/Makefile.build有如下定義

kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))

所以 kbuild-dir = ./scripts/basic

4.1.4.3 kbuild-file的定義

在scripts/Makefile.build有如下定義

kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)

所以kbuild-file = ./scripts/basic/Makefile

所以include $(kbuild-file)即為include ./scripts/basic/Makefile

在./scripts/basic/Makefile中:

# note:scripts/basic/Makefile
hostprogs-y	:= fixdep
always		:= $(hostprogs-y)

# fixdep is needed to compile other host programs
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

所以always = fixdep

4.1.4.4 又在Makefile.build中包含include scripts/Makefile.lib

而在scripts/Makefile.lib中

# note:scripts/Makefile.lib
always        := $(addprefix $(obj)/,$(always))

所以最終 always = scripts/basic/fixdep


故__build 展開如下:
__build: scripts/basic/fixdep
@:

1. 在Makefile.build中有如下定義:

# note:scripts/Makefile.build
ifneq ($(hostprogs-y)$(hostprogs-m),)
      include scripts/Makefile.host
endif 

在./scripts/basic/Makefile中hostprogs-y = fixdep, 所以scripts/Makefile.host被包含進Makefile.build;


在scripts/Makefile.host中

# note:scripts/Makefile.host
__hostprogs := $(sort $(hostprogs-y) $(hostprogs-m))

hostprogs-m為空,所以__hostprogs = fixdep。


在scripts/Makefile.host中

# note:scripts/Makefile.host
# C code
# Executables compiled from a single .c file
host-csingle	:= $(foreach m,$(__hostprogs), \
			$(if $($(m)-objs)$($(m)-cxxobjs)$($(m)-sharedobjs),,$(m)))
......
host-csingle	:= $(addprefix $(obj)/,$(host-csingle))			

因為fixdep-objs與fixdep-cxxobjs都不存在,所以host-csingle = fixdep; 又 host-csingle := $ (addprefix $ (obj)/,$(host-csingle)),所以host-csingle = scripts/basic/fixdep


host-csingle規則如下:

# note:scripts/Makefile.host
$(host-csingle): $(obj)/%: $(src)/%.c FORCE
	$(call if_changed_dep,host-csingle)		

等價於:

scripts/basic/fixdep:scripts/basic/fixdep.c FORCE
  $(call if_changed_dep,host-csingle)	

2. if_changed_dep在scripts/Kbuild.include中定義

# note:scripts/Kbuild.include
# Execute the command and also postprocess generated .d dependencies file.
if_changed_dep = $(if $(strip $(any-prereq) $(arg-check) ),                  \
	@set -e;                                                             \
	$(echo-cmd) $(cmd_$(1));                                             \
	scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
	rm -f $(depfile);                                                    \
	mv -f $(dot-target).tmp $(dot-target).cmd)

2.1 $(strip $(any-prereq) $(arg-check) )
(1) any-prereq在scripts/Kbuild.include中定義

# note:scripts/Kbuild.include
 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)

$ ? 表示所有比目標還要新的依賴檔案;$ ^ 表示所有的依賴檔案,$(filter-out $ (PHONY), $?)就是過濾到比目標還要新的依賴檔案中的偽目標,即為 scripts/basic/fixdep.c, $ (filter-out $ (PHONY) $ (wildcard $ ^ ), $^)表示過濾掉所有的依賴檔案中的偽目標與存在的依賴檔案,這裡為空,所以any-prereq = scripts/basic/fixdep.c


(2) arg-check在scripts/Kbuild.include中定義:

# note:scripts/Kbuild.include
ifneq ($(KBUILD_NOCMDDEP),1)
    arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
     $(filter-out $(cmd_$@),   $(cmd_$(1))) )
else
    arg-check = $(if $(strip $(cmd_$@)),,1)
endif

KBUILD_NOCMDDEP是在make命令列中定義,我們並沒有定義,所以:
arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) $(filter-out $(cmd_$@), $(cmd_$(1))) )

<1> $ (filter-out $ (cmd_ $ (1)), $ (cmd_ $@)) 表示過濾掉 $(cmd_ $@)中符合 $(cmd_ $(1))的項。 $(1)表示if_changed_dep函數的第一個引數host-csingle, $@表示目標檔案scripts/basic/fixdep。
<2> cmd_scripts/basic/fixdep並沒有定義,所以 $(filter-out $(cmd_ $(1)), $(cmd_ $@))為空;
<3> cmd_host-csingle 在Makefile.host中定義:

cmd_host-csingle    = $(HOSTCC) $(hostc_flags) -o $@ $<  $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))

所以arg-check = $ (filter-out $ (cmd_$ @), $ (cmd_$ (1))) = $ (HOSTCC) $ (hostc_flags) -o $@ $< $(HOST_LOADLIBES) $(HOSTLOADLIBES_ $(@F))

$(any-prereq) $(arg-check)都為非空,所以:

if_changed_dep = @set -e;   \ /如果任何語句的執行結果不是true則應該退出
	$(echo-cmd) $(cmd_$(1));                                             \
	scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\
	rm -f $(depfile);                                                    \
	mv -f $(dot-target).tmp $(dot-target).cmd)

2.2 $(echo-cmd) $(cmd_ $ (1))等價於$(echo-cmd) $(cmd_host-csingle)

echo-cmd = $(if $( $(quiet)cmd_$(1)),echo '  $(call escsq, $( $(quiet)cmd_ $(1))) $(echo-why)';

quiet=quiet_,在頂層Makefile分析過(當然如果你想看到更詳細的列印,您可以通過傳入V值,來改變), $(cmd_host-csingle)上面分析過,存在,所以:

echo-cmd = echo '  $(call escsq,$(cmd_host-csingle))$(echo-why)';

在scripts/Kbuild.include中:

# Escape single quote for use in echo statements
escsq = $(subst $(squote),'\$(squote)',$1)
ifeq ($(KBUILD_VERBOSE),2)
why =                                                                        \
    $(if $(filter $@, $(PHONY)),- due to target is PHONY,                    \
        $(if $(wildcard $@),                                                 \
            $(if $(strip $(any-prereq)),- due to: $(any-prereq),             \
                $(if $(arg-check),                                           \
                    $(if $(cmd_$@),- due to command line change,             \
                        $(if $(filter $@, $(targets)),                       \
                            - due to missing .cmd file,                      \
                            - due to $(notdir $@) not in $$(targets)         \
                         )                                                   \
                     )                                                       \
                 )                                                           \
             ),                                                              \
             - due to target missing                                         \
         )                                                                   \
     )

echo-why = $(call escsq, $(strip $(why)))
endif

KBUILD_VERBOSE一般我們會採用預設值0(需要偵錯編譯除外),所以 echo-why 為空。

quiet_cmd_host-csingle 	= HOSTCC  $@ //用來列印
cmd_host-csingle =  '$(HOSTCC) $(hostc_flags) -o $@ $<  $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))'

$(HOSTCC)為cc,此處不再深入解釋,hostc_flags在Makefile.host中定義:

#####
# Handle options to gcc. Support building with separate output directory

_hostc_flags   = $(HOSTCFLAGS)   $(HOST_EXTRACFLAGS)   \
            $(HOSTCFLAGS_$(basetarget).o) //-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer 
_hostcxx_flags = $(HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) \
                 $(HOSTCXXFLAGS_$(basetarget).o)
ifeq ($(KBUILD_SRC),) //KBUILD_SRC在make命令列定義,此處未定義
__hostc_flags	= $(_hostc_flags)
__hostcxx_flags	= $(_hostcxx_flags)
else
__hostc_flags	= -I$(obj) $(call flags,_hostc_flags)
__hostcxx_flags	= -I$(obj) $(call flags,_hostcxx_flags)
endif
hostc_flags    = -Wp,-MD,$(depfile) $(__hostc_flags) 

在scripts/Kbuild.include中:

comma   := ,
dot-target = $(dir $@).$(notdir $@)               //scripts/basic/.fixdep
depfile = $(subst $(comma),_,$(dot-target).d)     //scripts/basic/.fixdep.d
hostc_flags   =  -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer

綜上
cmd_host-csingle = cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o scripts/basic/fixdep scripts/basic/fixdep.c

所以echo-cmd 的作用是列印quiet_cmd_host-csingle(HOSTCC $ @ )或者cmd_host-csingle(根據頂層MakefileV值決定),$ (cmd_$(1))即為執行cmd_host-csingle生成fixdep同時生成fixdep的依賴檔案.fixdep.d

(2.3) scripts/basic/fixdep $(depfile) $@ ’ $(make-cmd)’ > $(dot-target).tmp
等價於:scripts/basic/fixdep scripts/basic/.fixdep.d scripts/basic/fixdep ‘cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o scripts/basic/fixdep scripts/basic/fixdep.c’ > scripts/basic/.fixdep.tmp

(2.4) rm -f $(depfile)
刪除scripts/basic/.fixdep.d

(2.5) mv -f $(dot-target).tmp $(dot-target).cmd)
將scripts/basic/.fixdep.tmp重新命名為scripts/basic/.fixdep.cmd

總結:生成scripts/basic/fixdep的過程中會先列印cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o scripts/basic/fixdep scripts/basic/fixdep.c同時執行該語句生成fixdep,再用fixdep生成.fixdep.cmd

4.2 依賴 outputmakefile

# 頂層Makefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
	$(Q)ln -fsn $(srctree) source
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

上面批註已經說的很清楚了,outputmakefile 在輸出目錄中生成一個 Makefile,如果使用單獨的輸出目錄。 這允許在輸出目錄中方便地使用 make。當KBUILD_SRC不為空時,才會編譯到這裡。

4.3 依賴 FORCE

# 頂層Makefile
PHONY += FORCE
FORCE:

實際上它是一個偽目標,從上面看到,FORCE 既沒有依賴的規則,其底下也沒有可執行的命令。如果一個規則沒有命令或者依賴,而且它的目標不是一個存在的檔名,在執行此規則時,目標總會被認為是最新的。也就是說,這個規則一旦被執行,make 就認為它所表示的目標已經被更新過。當將這樣的目標(FORCE)作為一個規則的依賴時(如上),由於依賴總被認為是被更新過的,所以作為依賴所在的規則定義的命令總會被執行。


4.4 規則 $ (Q)$(MAKE) $(build)=scripts/kconfig $@

等價於:make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig myimx8mmek240-8mm-2g_defconfig

在scripts/kconfig/Makefile中(至於為什麼會參照scripts/kconfig/Makefile,參見4.1小節)

# note:scripts/kconfig/Makefile
ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := Kconfig
endif

%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig) //$< = $(obj)/conf

# Added for U-Boot (backward compatibility)
%_config: %_defconfig
	@:

編譯的流程為:
(1)先編譯scripts/kconfig/conf可執行檔案;
(2)再執行scripts/kconfig/conf --defconfig=arch/…/configs/myimx8mmek240-8mm-2g_defconfig Kconfig語句

編譯列印如下

make -f ./scripts/Makefile.build obj=scripts/kconfig myimx8mmek240-8mm-2g_defconfig
  cc -Wp,-MD,scripts/kconfig/.conf.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer    -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>"  -DNCURSES_WIDECHAR=1 -DLOCALE   -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
  cat scripts/kconfig/zconf.tab.c_shipped > scripts/kconfig/zconf.tab.c
  cat scripts/kconfig/zconf.lex.c_shipped > scripts/kconfig/zconf.lex.c
  cat scripts/kconfig/zconf.hash.c_shipped > scripts/kconfig/zconf.hash.c
  cc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer    -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>"  -DNCURSES_WIDECHAR=1 -DLOCALE  -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
  cc  -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
scripts/kconfig/conf  --defconfig=arch/../configs/myimx8mmek240-8mm-2g_defconfig Kconfig
#
# configuration written to .config
#

5 總結

經過前面的分析可知:當執行make xxx_deconfig時,最終會執行以下兩個語句:
(1)make -f $(srctree)/scripts/Makefile.build obj=scripts/basic
(2)make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_deconfig
在這裡插入圖片描述