U-Boot顶层Makefile分析
參考:U-Boot頂層Makefile介紹
作者:一只青木呀
發布時間: 2020-10-22 16:22:17
網址:https://blog.csdn.net/weixin_45309916/article/details/109218569
目錄
- 0、學習目的
- 1、準備好uboot源碼
- 2、分析頂層Makefile
- 2.1、版本號
- 2.2、MAKEFLAGS 變量
- 2.3、命令輸出(參數V的效果)
- 2.4、靜默輸出
- 2.5、設置編譯結果輸出目錄
- 2.6、代碼檢查
- 2.7、模塊編譯
- 2.8、獲取主機架構和系統
- 2.9 、設置目標架構、交叉編譯器和配置文件
- 2.10、調用 scripts/Kbuild.include
- 2.11、交叉編譯工具變量設置
- 3.12、導出其他變量
- 2.13、make xxx_defconfig 過程
- 2.14、 Makefile.build 腳本分析(接上一節內容)
- 2.14.1、scripts_basic 目標對應的命令
- 2.14.2、 %config 目標對應的命令
- 總結make xxx_defconfig過程
- 2.15、 make 編譯過程
- 總結make命令的流程
0、學習目的
分析頂層Makefile主要是為了知道以后要改哪些東西。
Makefile配置好哪些C文件需要編譯,配置好以后通過make命令編譯即可。
1、準備好uboot源碼
這里分析的是2016.版本的uboot。
uboot源碼下載:https://blog.csdn.net/weixin_45309916/article/details/109176510
下載好后打開頂層Makefile。
一共是1600多行,接下來就開始分析啦
2、分析頂層Makefile
在閱讀 uboot 源碼之前,肯定是要先看一下頂層 Makefile,分析 gcc 版本代碼的時候一定是先從頂層 Makefile 開始的,然后再是子 Makefile,這樣通過層層分析 Makefile 即可了解整個工程的組織結構。頂層 Makefile 也就是 uboot 根目錄下的 Makefile 文件,由于頂層 Makefile 文件內容比較多,所以我們將其分開來看。
2.1、版本號
頂層 Makefile 一開始是版本號,內容如下(為了方便分析,頂層 Makefile 代碼段前段行號采用 Makefile 中的行號,因為 uboot 會更新,因此行號可能會與你所看的頂層 Makefile 有所不同):
VERSION 是主版本號, PATCHLEVEL 是補丁版本號, SUBLEVEL 是次版本號,這三個一起構成了 uboot 的版本號,比如當前的 uboot 版本號就是“2016.03”。 EXTRAVERSION 是附加版本信息, NAME 是和名字有關的,一般不使用這兩個。
2.2、MAKEFLAGS 變量
make 是支持遞歸調用的,也就是在 Makefile 中使用“make”命令來執行其他的Makefile文件,一般都是子目錄中的 Makefile 文件。假如在當前目錄下存在一個“subdir”子目錄,這個子目錄中又有其對應的 Makefile 文件,那么這個工程在編譯的時候其主目錄中的 Makefile 就可以調用子目錄中的 Makefile,以此來完成所有子目錄的編譯。主目錄的 Makefile 可以使用如下代碼來編譯這個子目錄:
$(MAKE) -C subdir$(MAKE)就是調用“make”命令, -C 指定子目錄。有時候我們需要向子 make 傳遞變量,這個時候使用“export”來導出要傳遞給子 make 的變量即可,如果不希望哪個變量傳遞給子make 的話就使用“unexport”來聲明不導出:
export VARIABLE …… //導出變量給子 make 。 unexport VARIABLE…… //不導出變量給子 make。有兩個特殊的變量:“SHELL”和“MAKEFLAGS”,這兩個變量除非使用“unexport”聲明,否則的話在整個make的執行過程中,它們的值始終自動的傳遞給子make。在uboot的主Makefile中有如下代碼:
MAKEFLAGS += -rR --include-dir=$(CURDIR)上述代碼使用“+=”來給變量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用內置的隱含規則和變量定義,“–include-dir”指明搜索路徑, ”$(CURDIR)”表示當前目錄。
2.3、命令輸出(參數V的效果)
uboot 默認編譯(就是輸入編譯命令make -j12)是不會在終端中顯示完整的命令,都是短命令,如下圖所示:
在終端中輸出短命令雖然看起來很清爽,但是不利于分析 uboot 的編譯過程。可以通過設置變量“V=1“來實現完整的命令輸出(就是輸入編譯命令make V=1 -j12),這個在調試 uboot 的時候很有用,結果如下圖所示:
頂層 Makefile 中控制命令輸出的代碼如下:
上述代碼中先使用 ifeq 來判斷"$(origin V)"和"command line"是否相等。這里用到了 Makefile中的函數 origin, origin 和其他的函數不一樣,它不操作變量的值, origin 用于告訴你變量是哪來的,語法為:
variable 是變量名, origin 函數的返回值就是變量來源,因此( o r i g i n V ) 就 是 變 量 V 的 來 源 。 如 果 變 量 V 是 在 命 令 行 定 義 的 那 么 它 的 來 源 就 是 " c o m m a n d l i n e " , 這 樣 " (origin V)就是變量 V 的來源。如果變量 V 是在命令行定義的那么它的來源就是"command line",這樣"(originV)就是變量V的來源。如果變量V是在命令行定義的那么它的來源就是"commandline",這樣"(origin V)"和"commandline"就相等了。當這兩個相等的時候變量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中輸 入 “ V=1 “ 的 話 那 么 KBUILD_VERBOSE=1 。 如 果 沒 有 在 命 令 行 輸 入 V 的 話KBUILD_VERBOSE=0。
第 80 行判斷 KBUILD_VERBOSE 是否為 1,如果 KBUILD_VERBOSE 為 1 的話變量 quiet和 Q 都為空,如果 KBUILD_VERBOSE=0 的話變量 quiet 為“quiet_“,變量 Q 為“@” ,綜上所述:
V=1 的話:
V=0 或者命令行不定義 V 的話:
KBUILD_VERBOSE=0 quiet= quiet_。 Q= @。Makefile 中會用到變量 quiet 和 Q 來控制編譯的時候是否在終端輸出完整的命令,在頂層Makefile 中有很多如下所示的命令:
$(Q)$(MAKE) $(build)=tools如果 V=0 的話上述命令展開就是“@ make $(build)=tools”, make 在執行的時候默認會在終端輸出命令,但是在命令前面加上“@”就不會在終端輸出命令了(shell腳本語法)。當 V=1 的時候 Q 就為空,上述命令就是“make $(build)=tools”,因此在 make 執行的過程,命令會被完整的輸出在終端上。
有些命令會有兩個版本,比如:
quiet_cmd_sym ?= SYM $@ cmd_sym ?= $(OBJDUMP) -t $< > $@sym 命令分為“quiet_cmd_sym”和“cmd_sym”兩個版本,這兩個命令的功能都是一樣的,區別在于 make 執行的時候輸出的命令不同。 quiet_cmd_xxx 命令輸出信息少,也就是短命令,而 cmd_xxx 命令輸出信息多,也就是完整的命令。
如果變量 quiet 為空的話,整個命令都會輸出。
如果變量 quiet 為“quiet_”的話,僅輸出短版本。
如果變量 quiet 為“silent_”的話,整個命令都不會輸出。
2.4、靜默輸出
設置 V=0 或者在命令行中不定義 V 的話,編譯 uboot 的時候終端中顯示的短命令,但是還是會有命令輸出,有時候我們在編譯 uboot 的時候不需要輸出任何命令,這個時候就可以使用 uboot 的靜默輸出功能。編譯的時候使用“make -s”即可實現靜默輸出,頂層 Makefile中相應的代碼如下:
2.5、設置編譯結果輸出目錄
uboot 可以將編譯出來的目標文件輸出到單獨的目錄中,在 make 的時候使用“O”來指定輸出目錄,比如“make O=out”就是設置目標文件輸出到 out 目錄中。這么做是為了將源文件和編譯產生的文件分開,當然也可以不指定 O 參數,不指定的話源文件和編譯產生的文件都在同一個目錄內,一般我們不指定 O 參數。頂層 Makefile 中相關的代碼如下:
# kbuild supports saving output files in a separate directory. # To locate output files in a separate directory two syntaxes are supported. # In both cases the working directory must be the root of the kernel src. # 1) O= # Use "make O=dir/to/store/output/files/" # # 2) Set KBUILD_OUTPUT # Set the environment variable KBUILD_OUTPUT to point to the directory # where the output files shall be placed. # export KBUILD_OUTPUT=dir/to/store/output/files/ # make # # The O= assignment takes precedence over the KBUILD_OUTPUT environment # variable.# KBUILD_SRC is set on invocation of make in OBJ directory # KBUILD_SRC is not intended to be used by the regular user (for now) ifeq ($(KBUILD_SRC),)# OK, Make called in directory where kernel src resides # Do we want to locate output files in a separate directory? ifeq ("$(origin O)", "command line") KBUILD_OUTPUT := $(O) endif# That's our default target when none is given on the command line PHONY := _all _all:# Cancel implicit rules on top Makefile $(CURDIR)/Makefile Makefile: ;ifneq ($(KBUILD_OUTPUT),) # Invoke a second make in the output directory, passing relevant variables # check that the output directory actually exists saved-output := $(KBUILD_OUTPUT) KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \&& /bin/pwd) $(if $(KBUILD_OUTPUT),, \$(error failed to create output directory "$(saved-output)"))PHONY += $(MAKECMDGOALS) sub-make$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make@:sub-make: FORCE$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))# Leave processing to above invocation of make skip-makefile := 1 endif # ifneq ($(KBUILD_OUTPUT),) endif # ifeq ($(KBUILD_SRC),) ifeq ("$(origin O)", "command line")判斷“O”是否來自于命令行,如果來自命令行的話條件成立, KBUILD_OUTPUT就為$(O),因此變量 KBUILD_OUTPUT 就是輸出目錄。
ifneq ($(KBUILD_OUTPUT),)判斷 KBUILD_OUTPUT 是否為空。
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \&& /bin/pwd)調用 mkdir 命令,創建 KBUILD_OUTPUT 目錄,并且將創建成功以后的絕對路徑賦值給 KBUILD_OUTPUT。至此,通過 O 指定的輸出目錄就存在了。
2.6、代碼檢查
uboot 支持代碼檢查,使用命令“make C=1”使能代碼檢查,檢查那些需要重新編譯的文件。“make C=2”用于檢查所有的源碼文件,頂層 Makefile 中的代碼如下:
第 176 行判斷 C 是否來源于命令行,如果 C 來源于命令行,那就將 C 賦值給變量KBUILD_CHECKSRC,如果命令行沒有 C 的話 KBUILD_CHECKSRC 就為 0。
2.7、模塊編譯
在 uboot 中允許單獨編譯某個模塊,使用命令“ make M=dir”即可,舊語法“ makeSUBDIRS=dir”也是支持的。頂層 Makefile 中的代碼如下:
第 186 行 判 斷 是 否 定 義 了 SUBDIRS , 如 果 定 義 了 SUBDIRS , 變 量KBUILD_EXTMOD=SUBDIRS,這里是為了支持老語法“make SUBIDRS=dir”
第 190 行判斷是否在命令行定義了 M,如果定義了的話 KBUILD_EXTMOD=$(M)。
第 197 行判斷 KBUILD_EXTMOD 時為空,如果為空的話目標_all 依賴 all,因此要先編譯出 all。否則的話默認目標_all 依賴 modules,要先編譯出 modules,也就是編譯模塊。一般情況下我們不會在 uboot 中編譯模塊,所以此處會編譯 all 這個目標。
第 203 行判斷 KBUILD_SRC 是否為空,如果為空的話就設置變量 srctree 為當前目錄,即srctree 為“.”,一般不設置 KBUILD_SRC。
第 214 行設置變量 objtree 為當前目錄。
第 215 和 216 行分別設置變量 src 和 obj,都為當前目錄。
第 218 行設置 VPATH。
第 220 行導出變量 scrtree、 objtree 和 VPATH。
2.8、獲取主機架構和系統
接下來頂層 Makefile 會獲取主機架構和系統,也就是我們電腦的架構和系統,代碼如下:
第 227 行定義了一個變量 HOSTARCH,用于保存主機架構,這里調用 shell 命令“uname -m”獲取架構名稱,結果如下圖所示
從上圖可以看出當前電腦主機架構為“x86_64”, shell 中的“|”表示管道,意思是將左邊的輸出作為右邊的輸入, sed -e 是替換命令,“sed -e s/i.86/x86/”表示將管道輸入的字符串中的“i.86”替換為“x86”,其他的“sed -s”命令同理。對于我的電腦而言, HOSTARCH=x86_64。第 237 行定義了變量 HOSTOS,此變量用于保存主機 OS 的值,先使用 shell 命令“name -s”來獲取主機 OS,結果如下圖所示:
從上圖可以看出此時的主機 OS 為“Linux”,使用管道將“Linux”作為后面“tr ‘[:upper:]’’[:lower:]’”的輸入,“tr ‘[:upper:]’ ‘[:lower:]’”表示將所有的大寫字母替換為小寫字母,因此得到“linux”。最后同樣使用管道,將“linux”作為“sed -e ‘s/(cygwin).*/cygwin/’”的輸入,用于將cygwin.*替換為 cygwin。因此, HOSTOS=linux。
第 240 行導出 HOSTARCH=x86_64, HOSTOS=linux。
2.9 、設置目標架構、交叉編譯器和配置文件
編 譯 uboot 的 時 候 需 要 設 置 目 標 板 架 構 和 交 叉 編 譯 器 ,“ make ARCH=armCROSS_COMPILE=arm-linux-gnueabihf-”就是用于設置 ARCH 和 CROSS_COMPILE,在頂層Makefile 中代碼如下:
第 245 行判斷 HOSTARCH 和 ARCH 這兩個變量是否相等,主機架構(變量 HOSTARCH)是x86_64,而我們編譯的是 ARM 版本 uboot,肯定不相等,所以 CROS_COMPILE= arm-linuxgnueabihf-。
第 249 行定義變量 KCONFIG_CONFIG, uboot 是可以配置的,這里設置配置文件為.config,前面樹莓派學習過: .config 默認是沒有的,需要使用命令“make xxx_defconfig”對 uboot 進行配置,配置完成以后就會在 uboot 根目錄下生成.config。默認情況下.config 和xxx_defconfig 內容是一樣的,因為.config 就是從 xxx_defconfig 復制過來的。如果后續自行調整了 uboot 的一些配置參數,那么這些新的配置參數就添加到了.config 中,而不是 xxx_defconfig。相當于 xxx_defconfig 只是一些初始配置,而.config 里面的才是實時有效的配置。
2.10、調用 scripts/Kbuild.include
主 Makefile 會調用文件 scripts/Kbuild.include這個文件,頂層 Makefile 中代碼如下:
scripts/Kbuild.include文件部分內容:
在 uboot 的編譯過程中會用到 scripts/Kbuild.include 中的這些變量
2.11、交叉編譯工具變量設置
上面我們只是設置了 CROSS_COMPILE 的名字,但是交叉編譯器其他的工具還沒有設置,頂層 Makefile 中相關代碼如下:
3.12、導出其他變量
接下來在頂層 Makefile 會導出很多變量,代碼如下:
這些變量中大部分都已經在前面定義了,我們重點來看一下下面這幾個變量:
修改好頂層 Makefile 以后,命令行輸入以下命令:
結果如圖下所示:
從上圖可以看到這 7 個變量的值,這 7 個變量是從哪里來的呢?在 uboot 根目錄下有個文件叫做 config.mk,這 7 個變量就是在 config.mk(在uboot源碼根目錄下) 里面定義的,打開 config.mk 內容如下:
-
第 25 行 定 義 變 量 ARCH(架構) , 值 為 $(CONFIG_SYS_ARCH:"%"=%) , 也 就 是 提 取CONFIG_SYS_ARCH 里面雙引號“”之間的內容。比如 CONFIG_SYS_ARCH=“arm”的話,ARCH=arm。
-
第 26 行定義變量 CPU,值為$(CONFIG_SYS_CPU:"%"=%)。
-
第 32 行定義變量 BOARD(板子),值為(CONFIG_SYS_BOARD:"%"=%)。
-
第 34 行定義變量 VENDOR,值為$(CONFIG_SYS_VENDOR:"%"=%)。
-
第 37 行定義變量 SOC,值為$(CONFIG_SYS_SOC:"%"=%)。
-
第 44 行定義變量 CPUDIR(CPU目錄),值為 arch/( A R C H ) / c p u (ARCH)/cpu(ARCH)/cpu(if ( C P U ) , / (CPU),/(CPU),/(CPU),)。
-
第 46 行 sinclude 和 include 的功能類似,在 Makefile 中都是讀取指定文件內容,這里讀取文件( s r c t r e e ) / a r c h / (srctree)/arch/(srctree)/arch/(ARCH)/config.mk 的內容。 sinclude 讀取的文件如果不存在的話不會報錯。
-
第 47 行讀取文件( s r c t r e e ) / (srctree)/(srctree)/(CPUDIR)/config.mk 的內容。
-
第 50 行讀取文件( s r c t r e e ) / (srctree)/(srctree)/(CPUDIR)/$(SOC)/config.mk 的內容。
-
第 54 行 定 義 變 量 BOARDDIR , 如 果 定 義 了 VENDOR 那 么BOARDDIR=( V E N D O R ) / (VENDOR)/(VENDOR)/(BOARD),否則的 BOARDDIR=$(BOARD)。
-
第 60 行讀取文件( s r c t r e e ) / b o a r d / (srctree)/board/(srctree)/board/(BOARDDIR)/config.mk
接下來需要找到 CONFIG_SYS_ARCH、 CONFIG_SYS_CPU、 CONFIG_SYS_BOARD、CONFIG_SYS_VENDOR 和 CONFIG_SYS_SOC 這 5 個變量的值。這 5 個變量在 uboot 根目錄下的.config 文件中有定義,定義如下:
根據示例代碼可知:
在 config.mk 中讀取的文件有:
arch/arm/config.mk arch/arm/cpu/armv7/config.mk arch/arm/cpu/armv7/mx6/config.mk (此文件不存在) board/ freescale/mx6ullevk/config.mk (此文件不存在)2.13、make xxx_defconfig 過程
在編譯 uboot 之前要使用“make xxx_defconfig”命令來配置 uboot,那么這個配置過程是如何運行的呢?在頂層 Makefile 中有如下代碼:
xxx_defconfig 對應代碼第476行,下面有詳解:
第 422 行定義了變量 version_h,這變量保存版本號文件,此文件是自動生成的。文件include/generated/version_autogenerated.h 內容如下圖所示:
第 423 行定義了變量 timestamp_h,此變量保存時間戳文件,此文件也是自動生成的。文件include/generated/timestamp_autogenerated.h 內容如下圖所示:
-
第 425 行定義了變量 no-dot-config-targets。
-
第 429 行定義了變量 config-targets,初始值為 0。
-
第 430 行定義了變量 mixed-targets,初始值為 0
-
第 431 行定義了變量 dot-config,初始值為 1。
-
第 433 行將 MAKECMDGOALS 中不符合 no-dot-config-targets 的部分過濾掉,剩下的如果不為空的話條件就成立。 MAKECMDGOALS 是 make 的一個環境變量,這個變量會保存你所指定的終極目標列表,比如執行“make mx6ull_alientek_emmc_defconfig”,那么 MAKECMDGOALS就為 mx6ull_alientek_emmc_defconfig。很明顯過濾后為空,所以條件不成立,變量 dot-config 依舊為 1。
-
第439行判斷KBUILD_EXTMOD是否為空,如果KBUILD_EXTMOD為空的話條件成立,經過前面的分析,我們知道 KBUILD_EXTMOD 為空,所以條件成立。
-
第 440 行將 MAKECMDGOALS 中不符合“config”和“%config”的部分過濾掉,如果剩下的部分不為空條件就成立,很明顯此處條件成立,變量 config-targets=1。
-
第 442 行統計 MAKECMDGOALS 中的單詞個數,如果不為 1 的話條件成立。此處調用Makefile 中的 words 函數來統計單詞個數, words 函數格式如下:
很明顯, MAKECMDGOALS 的單詞個數是 1 個,所以條件不成立, mixed-targets 繼續為0。綜上所述,這些變量值如下:
config-targets = 1 mixed-targets = 0 dot-config = 1-
第 448 行如果變量 mixed-targets 為 1 的話條件成立,很明顯,條件不成立。
-
第 465 行如果變量 config-targets 為 1 的話條件成立,很明顯,條件成立,執行這個分支。
-
第 473 行,沒有目標與之匹配,所以不執行。
-
第 476 行,有目標與之匹配,當輸入“make xxx_defconfig”的時候就會匹配到%config 目標,目標“%config”依賴于 scripts_basic、 outputmakefile 和 FORCE。
-
- FORCE 在頂層 Makefile的 1610 行有如下定義:
可以看出 FORCE 是沒有規則和依賴的,所以每次都會重新生成 FORCE。當 FORCE 作為其他目標的依賴時,由于 FORCE 總是被更新過的,因此依賴所在的規則總是會執行的。
- FORCE 在頂層 Makefile的 1610 行有如下定義:
-
- 依賴 scripts_basic 和 outputmakefile 在頂層 Makefile 中的內容如下:
- 依賴 scripts_basic 和 outputmakefile 在頂層 Makefile 中的內容如下:
-
- 第 408 行,判斷 KBUILD_SRC 是否為空,只有變量 KBUILD_SRC 不為空的時候outputmakefile 才有意義,經過下面的代碼分析 KBUILD_SRC 為空,所以 outputmakefile 無效。只有 scripts_basic 是有效的。
- 第 408 行,判斷 KBUILD_SRC 是否為空,只有變量 KBUILD_SRC 不為空的時候outputmakefile 才有意義,經過下面的代碼分析 KBUILD_SRC 為空,所以 outputmakefile 無效。只有 scripts_basic 是有效的。
-
- 第 396~398 行是 scripts_basic 的規則,其對應的命令用到了變量 Q、 MAKE 和 build,其中:
變量 build 是在 scripts/Kbuild.include 文件中有定義,定義如下:
從代碼可以看出 build=-f $(srctree)/scripts/Makefile.build obj,經過前面的分析可知,變量 srctree 為”.”,因此:
scripts_basic 展開以后如下:
scripts_basic: @make -f ./scripts/Makefile.build obj=scripts/basic //也可以沒有@,視配置而定 @rm -f . tmp_quiet_recordmcount //也可以沒有@scripts_basic 會調用文件./scripts/Makefile.build
接著回到示例代碼中的%config 處,內容如下:
%config: scripts_basic outputmakefile FORCE $(Q)$(MAKE) $(build)=scripts/kconfig $@將命令展開就是:
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig同樣也跟文件./scripts/Makefile.build 有關,使用如下命令配置 uboot,并觀察其配置過程:
make mx6ull_14x14_ddr512_emmc_defconfig V=1配置過程如下圖所示:
從上圖可以看出,我們的分析是正確的,接下來就要結合下面兩行命令重點分析一下文件 scripts/Makefile.build。
①、 scripts_basic 目標對應的命令
②、 %config 目標對應的命令
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig2.14、 Makefile.build 腳本分析(接上一節內容)
從上一小節可知,“ make xxx_defconfig“配置 uboot 的時候如下兩行命令會執行腳本scripts/Makefile.build:
@make -f ./scripts/Makefile.build obj=scripts/basic @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig依次來分析一下:
2.14.1、scripts_basic 目標對應的命令
scripts_basic 目標對應的命令為: @make -f ./scripts/Makefile.build obj=scripts/basic。打開文件 scripts/Makefile.build,有如下代碼:
第 9 行定義了變量 prefix 值為 tpl。
第 10 行定義了變量 src,這里用到了函數 patsubst,此行代碼展開后為:
$(patsubst tpl/%,%, scripts/basic)patsubst 是替換函數,格式如下:
$(patsubst <pattern>,<replacement>,<text>)此函數用于在 text 中查找符合 pattern 的部分,如果匹配的話就用 replacement 替換掉。pattenr 是可以包含通配符“%”,如果 replacement 中也包含通配符“%”,那么 replacement 中的這個“%”將是 pattern 中的那個“%”所代表的字符串。函數的返回值為替換后的字符串。因此,第 10 行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后將“tpl/”取消掉,但是“scripts/basic”沒有“tpl/”,所以 src= scripts/basic。
第 11 行判斷變量 obj 和 src 是否相等,相等的話條件成立,很明顯,此處條件成立。
第 12 行和第 9 行一樣,只是這里處理的是“spl”,“scripts/basic”里面也沒有“spl/”,所以src 繼續為 scripts/basic。
第 15 行因為變量 obj 和 src 相等,所以 prefix=.。
繼續分析 scripts/Makefile.build,有如下代碼:
將 kbuild-dir 展開后為:
因為沒有以“ /”為開頭的單詞,所以$(filter /%, scripts/basic)的結果為空, kbuilddir=./scripts/basic。
將 kbuild-file 展開后為:
因為 scrpts/basic 目錄中沒有 Kbuild 這個文件,所以 kbuild-file= ./scripts/basic/Makefile。最
后將 59 行展開,即:
也就是讀取 scripts/basic 下面的 Makefile 文件。
繼續分析 scripts/Makefile.build,如下代碼:
__build 是默認目標,因為命令“@make -f ./scripts/Makefile.build obj=scripts/basic”沒有指定目標,所以會使用到默認目標: __build。在頂層 Makefile 中, KBUILD_BUILTIN 為 1,KBUILD_MODULES 為 0,因此展開后目標__build 為:
可以看出目標__build 有 5 個依賴: builtin-target、 lib-target、 extra-y、 subdir-ym 和 always。這 5 個依賴的具體內容我們就不通過源碼來分析了,直接在 scripts/Makefile.build 中輸入圖所示內容,將這 5 個變量的值打印出來:
執行如下命令:
結果如下圖所示:
從上圖可以看出,只有 always 有效,因此__build 最終為:
__build 依賴于 scripts/basic/fixdep,所以要先 scripts/basic/fixdep.c 編譯,生成 fixdep,前面已經讀取了 scripts/basic/Makefile 文件。
綜上所述, scripts_basic 目標的作用就是編譯出 scripts/basic/fixdep 這個軟件(具體作用未知)。
2.14.2、 %config 目標對應的命令
%config 目 標 對 應 的 命 令 為 : @make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig,各個變量值如下:
src= scripts/kconfig kbuild-dir = ./scripts/kconfig kbuild-file = ./scripts/kconfig/Makefile include ./scripts/kconfig/Makefile可以看出, Makefilke.build 會讀取 scripts/kconfig/Makefile 中的內容,此文件有如下所示內容:
目標%_defconfig 剛好和我們輸入的 xxx_defconfig 匹配,所以會執行這條規則。依賴為$(obj)/conf,展開后就是 scripts/kconfig/conf。接下來就是檢查并生成依賴 scripts/kconfig/conf。conf 是主機軟件,到這里我們就打住,不要糾結 conf 是怎么編譯出來的,否則就越陷越深,太繞了,像 conf 這種主機所使用的工具類軟件我們一般不關心它是如何編譯產生的。如果一定要看是 conf 是怎么生成的,可以輸入如下命令重新配置 uboot,在重新配置 uboot 的過程中就會輸出 conf 編譯信息。
得到 scripts/kconfig/conf 以后就要執行目標%_defconfig 的命令:
相關的變量值如下:
silent=-s 或為空 SRCARCH=.. Kconfig=Kconfig將其展開就是:
@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig上述命令用到了 xxx_defconfig 文件,比如 mx6ull_alientek_emmc_defconfig。這里會將mx6ull_alientek_emmc_defconfig 中的配置輸出到.config 文件中,最終生成 uboot 根目錄下的.config 文件。
總結make xxx_defconfig過程
至此,make xxx_defconfig 就分析完了,接下來就要分析一下 u-boot.bin 是怎么生成的了。
2.15、 make 編譯過程
配置好 uboot 以后就可以直接 make 編譯了(也就是前面Makefile配置好哪些C文件需要編譯),因為沒有指明目標,所以會使用默認目標,主Makefile 中的默認目標如下:
目標_all 又依賴于 all,如下所示:
如 果 KBUILD_EXTMOD 為 空 的 話 _all 依 賴 于 all 。 這 里 不 編 譯 模 塊 , 所 以KBUILD_EXTMOD 肯定為空, _all 的依賴就是 all。在主 Makefile 中 all 目標規則如下:
從 802 行可以看出, all 目標依賴$(ALL-y),而在頂層 Makefile 中, ALL-y 如下:
從示例代碼代碼可以看出, ALL-y 包含 u-boot.srec、 u-boot.bin、 u-boot.sym、System.map、 u-boot.cfg 和 binary_size_check 這幾個文件。根據 uboot 的配置情況也可能包含其他的文件,比如:
CONFIG_ONENAND_U_BOOT 就是 uboot 中跟 ONENAND 配置有關的,如果我們使能了ONENAND,那么在.config 配置文件中就會有“CONFIG_ONENAND_U_BOOT=y”這一句。相當于 CONFIG_ONENAND_U_BOOT 是個變量,這個變量的值為“y”,所以展開以后就是:
ALL-y += u-boot-onenand.bin這個就是.config 里面的配置參數的含義,這些參數其實都是變量,后面跟著變量值,會在頂層 Makefile 或者其他 Makefile 中調用這些變量。
ALL-y 里面有個 u-boot.bin,這個就是我們最終需要的 uboot 二進制可執行文件,所作的所有工作就是為了它。在頂層 Makefile 中找到 u-boot.bin 目標對應的規則,如下所示:
第 825 行判斷 CONFIG_OF_SEPARATE 是否等于 y,如果相等,那條件就成立,在.config中搜索“CONFIG_OF_SEPARAT”,沒有找到,說明條件不成立。
第 832 行就是目標 u-boot.bin 的規則,目標 u-boot.bin 依賴于 u-boot-nodtb.bin,命令為$(callif_changed,copy) , 這 里 調 用 了 if_changed , if_changed 是 一 個 函 數 , 這 個 函 數 在scripts/Kbuild.include 中有定義,而頂層 Makefile 中會包含 scripts/Kbuild.include 文件,這個前面已經說過了。
if_changed 在 Kbuild.include 中的定義如下:
第 227 行為 if_changed 的描述,根據描述,在一些先決條件比目標新的時候,或者命令行有改變的時候, if_changed 就會執行一些命令。
第 257 行就是函數 if_changed, if_changed 函數引用的變量比較多,也比較繞,我們只需要知道它可以從 u-boot-nodtb.bin 生成 u-boot.bin 就行了。
既然 u-boot.bin 依賴于 u-boot-nodtb.bin,那么肯定要先生成 u-boot-nodtb.bin 文件,頂層Makefile 中相關代碼如下:
目標 u-boot-nodtb.bin 又依賴于 u-boot,頂層 Makefile 中 u-boot 相關規則如下:
目標 u-boot 依賴于 u-boot_init、 u-boot-main 和 u-boot.lds, u-boot_init 和 u-boot-main 是兩個變量,在頂層 Makefile 中有定義,值如下:
$ (head-y)跟 CPU 架構有關,我們使用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中被指定為:
根據分析,我們知道 CPU=armv7,因此 head-y 展開以后就是:
head-y := arch/arm/cpu/armv7/start.o因此:
u-boot-init= arch/arm/cpu/armv7/start.o$(libs-y)在頂層 Makefile 中被定義為 uboot 所有子目錄下 build-in.o 的集合,代碼如下:
從上面的代碼可以看出, libs-y 都是 uboot 各子目錄的集合,最后:
這里調用了函數 patsubst,將 libs-y 中的“/”替換為”/built-in.o”,比如“drivers/dma/”就變為了“drivers/dma/built-in.o”,相當于將 libs-y 改為所有子目錄中 built-in.o 文件的集合。那么 uboot-main 就等于所有子目錄中 built-in.o 的集合。
這個規則就相當于將以 u-boot.lds 為鏈接腳本,將 arch/arm/cpu/armv7/start.o 和各個子目錄下的 built-in.o 鏈接在一起生成 u-boot。
u-boot.lds 的規則如下:
接下來的重點就是各子目錄下的 built-in.o 是怎么生成的,以 drivers/gpio/built-in.o 為例,在drivers/gpio/目錄下會有個名為.built-in.o.cmd 的文件,此文件內容如下:
cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -o drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o從命令“cmd_drivers/gpio/built-in.o”可以看出, drivers/gpio/built-in.o 這個文件是使用 ld 命令由文件 drivers/gpio/mxc_gpio.o 生成而來的, mxc_gpio.o 是 mxc_gpio.c 編譯生成的.o 文件,這個是 NXP 的 I.MX 系列的 GPIO 驅動文件。這里用到了 ld 的“-r”參數,參數含義如下:
-r –relocateable: 產生可重定向的輸出,比如,產生一個輸出文件它可再次作為‘ld’ 的輸入,這經常被叫做“部分鏈接”,當我們需要將幾個小的.o 文件鏈接成為一個.o 文件的時候,需要使用此選項。
最終將各個子目錄中的 built-in.o 文件鏈接在一起就形成了 u-boot,使用如下命令編譯 uboot就可以看到鏈接的過程:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_ defconfig V=1 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- V=1編譯的時候會有如下圖所示內容輸出:
grep命令搜索ox87800000
將其整理一下,內容如下:
arm-linux-gnueabihf-ld.bfd -pie --gc-sections -Bstatic -Ttext 0x87800000 \ -o u-boot -T u-boot.lds \ arch/arm/cpu/armv7/start.o \ --start-group arch/arm/cpu/built-in.o \ arch/arm/cpu/armv7/built-in.o \ arch/arm/imx-common/built-in.o \ arch/arm/lib/built-in.o \ board/freescale/common/built-in.o \ board/freescale/mx6ull_alientek_emmc/built-in.o \ cmd/built-in.o \ common/built-in.o \ disk/built-in.o \ drivers/built-in.o \ drivers/dma/built-in.o \ drivers/gpio/built-in.o \ …… drivers/spi/built-in.o \ drivers/usb/dwc3/built-in.o \ drivers/usb/emul/built-in.o \ drivers/usb/eth/built-in.o \ drivers/usb/gadget/built-in.o \ drivers/usb/gadget/udc/built-in.o \ drivers/usb/host/built-in.o \ drivers/usb/musb-new/built-in.o \ drivers/usb/musb/built-in.o \ drivers/usb/phy/built-in.o \ drivers/usb/ulpi/built-in.o \ fs/built-in.o \ lib/built-in.o \ net/built-in.o \ test/built-in.o \ test/dm/built-in.o \ --end-group arch/arm/lib/eabi_compat.o \ -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linuxgnueabihf/4.9.4 -lgcc -Map u-boot.map可以看出最終是用 arm-linux-gnueabihf-ld.bfd 命令將 arch/arm/cpu/armv7/start.o 和其他眾多的 built_in.o 鏈接在一起,形成 u-boot。
目標 all 除了 u-boot.bin 以外還有其他的依賴,比如 u-boot.srec 、 u-boot.sym 、 System.map、u-boot.cfg 和 binary_size_check 等等,這些依賴的生成方法和 u-boot.bin 很類似
總結make命令的流程
make xxx_defconfig: 用于配置 uboot,這個命令最主要的目的就是生成.config 文件。
make:用于編譯 uboot,這個命令的主要工作就是生成二進制的 u-boot.bin 文件和其他的一些與 uboot 有關的文件,比如 u-boot.imx 等等
總結
以上是生活随笔為你收集整理的U-Boot顶层Makefile分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ACM配置指南
- 下一篇: U-Boot启动流程详解