1. 程式人生 > >U-boot主Makefile分析

U-boot主Makefile分析

原文:http://blog.csdn.net/qq_28992301/article/details/51802885

U-boot主Makefile分析

主Makefile位於uboot原始碼的根目錄下,其內容主要結構為: 
1. 確定版本號及主機資訊 
2. 實現靜默編譯功能 
3. 設定各種路徑 
4. 設定編譯工具鏈 
5. 設定規則 
6. 設定與cpu相關的偽目標

需要注意的是,結構順序不代表程式碼執行順序,關於程式碼的執行順序以及推薦閱讀順序請移步

 [ U-boot配置及編譯階段流程巨集觀分析 ]

1.確定版本號及主機資訊

VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 這四個變數的含義依次為:主版本號、次版本號、再次版本號、附加的版本資訊(值為空,可以給使用者使用)
  • U-Boot_VERSION這個變數的值為真正的uboot版本號,易知其值為1.3.4
  • VERSION_FILE變數的值是一個.h檔案,注意=是並行賦值的意思,類似於Verilog語言中的<=。要注意:=(可以理解為序列賦值)和=的區別。$(obj)這個變數是在後面定義並賦值的,其值是編譯輸出路徑
  • version_autogenerated.h這個檔案是在make之後自動生成的,檔案內容是一條巨集,這條巨集給將其他.c檔案提供uboot的版本號
HOSTARCH := $(shell uname -m | \
    sed -e s/i.86/i386/ \
        -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/powerpc/ppc/ \ -e s/ppc64/ppc/ \ -e s/macppc/ppc/)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • makefile的函式呼叫與變數呼叫很類似,格式是$( function arguments),其實上面一大段的意思是變數HOSTARCH的值是一個函式的返回值
  • shell是makefile中的一個函式,$(shell XXX)會被解析成執行shell命令XXX;此處是執行了一條 uname -m |sed -e ……,符號 \是makefile的換行符
  • 其中,|是shell語法中的管道結構,例如:XXX | YYY ,表示式XXX 的輸出將作為表示式YYY的輸入,YYY的輸出才是整句表示式的輸出
  • uname -m 指令將輸出負責編譯的主機cpu架構,比如ixx86;sed -e是替換命令,比如把ixx86替換為i386
  • 由此可見這個HOSTARCH變數的值將得到負責編譯的主機cpu架構。大部分情況下我們得到的都是i386
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
        sed -e 's/\(cygwin\).*/cygwin/')
  • 1
  • 2
  • 3
  • 這個HOSTOS變數和上一句HOSTARCH變數的原理類似,管道第一部分uname -s會得到負責編譯的主機的OS,比如Linux
  • 管道第二部分是將大寫轉換成小寫
  • 管道第三部分的意思是如果前面一個部分得到了cygwin系統,則格式要轉換一下。不必深究,因為cygwin基本沒人用……
  • 由此可見這個HOSTOS變數的值將得到負責編譯的主機作業系統,大部分情況下我們得到的都是linux
export  HOSTARCH HOSTOS
  • 1
  • 匯出上面兩個變數到全域性,使其為環境變數,讓其他的檔案也可以使用架構和系統資訊

2.實現靜默編譯功能

# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 這整段是為了實現make的靜默選項功能,其中,findstring一個函式,$(findstrings, $ (MAKEFLAGS))功能是從$(MAKEFLAGS)中找出字元‘s’
  • $(MAKEFLAGS)是make的flag(選項),如果在控制檯中輸入make -s,則$(MAKEFLAGS)的值為‘s’
  • 如果$(findstring $(MAKEFLAGS))沒找到‘s’,這個表示式的值為空,則ifeq()為真,即make時無需靜默
  • 無需靜默的實現方法是令變數XECHO值為關鍵字echo,因為makefile中每次需要列印都會使用XECHO,而不是直接使用echo本身
  • 靜默make的實現方法是令變數XECHO值為空,當makefile需要列印時會呼叫XECHO,由於其為空,故無法列印make資訊
  • 注:編譯工具鏈的資訊是永遠列印的,和make的靜默選項無關

3.設定各種路徑

  • 這裡開始闡述了makefile所支援的“單獨外部路徑編譯”,它的原理和keil把.o、.l、.bin、.a等中間檔案放在單獨的資料夾內的原理相同,都是為了使原始碼更簡潔明瞭,防止被中間檔案汙染;以及為了同時維護多個配置編譯方式
  • 若要使用單獨外部路徑編譯,有兩種方法,方法一:例如在控制檯中輸入 make O=/tmp/build ,將輸出資料夾路徑作為引數
  • 方法二:匯出環境變數,即export BUILD_DIR=/tmp/build
  • 注:方法一的優先順序高,會覆蓋方法二。而且方法一必須每次輸入make時都要輸入引數(不論是make clean還是make config 的時候),格式如make O=/tmp/build disclean
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif

ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 上面這段是方法一的具體實現,如果make時輸入引數 O=/tmp/build,那麼makefile會認為定義了變數O,於是乎這段程式碼會開始執行
  • 其中,$(origin O)中origin是函式名,$(origin O)的功能是返回變數O的來源;由此可知,如果O的來源是控制檯命令,則變數BUILD_DIR的值就是變數O的值
  • 然後進行一個判斷,如果變數BUILD_DIR不為空,則變數saved-output的值為BUILD_DIR,即saved-output的值也是輸出資料夾路徑
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
  • 1
  • 2
  • 上面這句是shell語法中簡寫的if表示式,其作用是當輸出資料夾路徑不存在時就建立它
  • 其中包含兩個表示式,表示式1||表示式2,當表示式1為真時,表示式2不會被直譯器執行,因為總結果一定為真,(儘管總的結果沒有意義)。唯有表示式1為假時,直譯器才會去執行表示式2,這樣就能用邏輯表示式來實現if語句的功能了
  • 表示式1中,方括號是固定用法,是為了突出裡面表示式的作用是判斷語句。-p是shell中判斷路徑是否存在的符號,如果路徑存在則為表示式1為真;如果路徑不存在,表示式1為假,直譯器會執行表示式2,即建立輸出資料夾路徑。
# Verify if it was successful.
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif # ifneq ($(BUILD_DIR),)
  • 1
  • 2
  • 3
  • 4
  • 這整個一段功能是確保輸出資料夾路徑建立成功,&&的功能是連續執行兩句語句,當cd $(BUILD_DIR)執行完後,執行/bin/pwd,即列印當前路徑;
  • $(if xxx,yyy,zzz)是makefile的判斷函式,如果xxx為真,則執行yyy並返回值,否則執行zzz並返回值,由此可知如果未成功建立BUILD_DIR,就會輸出錯誤列印資訊;
  • 最後一句應該是被註釋掉了……
OBJTREE     := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE     := $(CURDIR)
TOPDIR      := $(SRCTREE)
LNDIR       := $(OBJTREE)
export  TOPDIR SRCTREE OBJTREE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 這段程式碼是設定並匯出了很多和路徑有關的環境變數
  • 如果BUILD_DIR不為空,OBJTREE(放產生的.o檔案)的值就為BUILD_DIR,如果為空,則OBJTREE的值就為CURDIR(即current direction,當前原始碼所在的目錄)
  • SRCTREE的值為當前原始碼所在目錄,TOPDIR的值為SRCTREE的值即當前原始碼所在目錄,LNDIR(應該是放連結產生的檔案)的值和OBJTREE相同
MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG
  • 1
  • 2
  • 3
  • 將變數MKCONFIG的值設定為當前原始碼目錄下的mkconfig檔案,並將其匯出為環境變數,這個shell指令碼檔案是正式make之前的配置指令碼,十分重要
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD    := 1
export REMOTE_BUILD
endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 判斷OBJTREE的值和SRCTREE的值是否不相等,即判斷是否使用了“單獨外部路徑編譯”,如果使用了這個功能,則REMOTE_BUILD值為1,並將其匯出至全域性
# $(obj) and (src) are defined in config.mk but here in main Makefile
# we also need them before config.mk is included which is the case for
# some targets like unconfig, clean, clobber, distclean, etc.
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 這一段的意思非常簡單,判斷OBJTREE的值和SRCTREE的值是否不相等,即判斷是否使用了“單獨外部路徑編譯”功能,如果使用了這個功能, 則將obj的值賦為OBJTREE的值,將src的值賦為SRCTREE的值;反之,值都為空。最後將他們全部匯出至全域性

4.設定編譯工具鏈(大部分在config.mk內)

ifeq ($(ARCH),powerpc)
ARCH = ppc
endif

ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 開頭三句是powerpc架構的名稱轉換。
  • 最後一句$(wildcard xxx) 引數xxx是一個檔名格式(可使用萬用字元),這個函式的返回值是一列和格式匹配且真實存在的檔案的名稱。但是這句應該沒什麼意義,因為連endif都沒有……
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export  ARCH CPU BOARD VENDOR SOC
  • 1
  • 2
  • 3
  • config.mk這個檔案其實uboot原始碼中是不存在的,它是由配置過程中由mkconfig這個指令碼建立的, 也就是make之前的一步——make x210_sd_config(這個目標在2600多行),它會去執行根目錄下mkconfig這個指令碼,指令碼中將建立include/config.mk並將引數填充進去。
  • config.mk的內容分別定義了ARCH CPU BOARD VENDOR SOC這幾個變數的值,通過把這個.mk檔案include進來,其內容將在本makefile中原地展開
  • 本Makefile得到這幾個變數值後再將它們匯出到環境變數
ifndef CROSS_COMPILE
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE =
else
ifeq ($(ARCH),ppc)
CROSS_COMPILE = ppc_8xx-
endif
ifeq ($(ARCH),arm)
#CROSS_COMPILE = arm-linux-
#CROSS_COMPILE = /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux-
#CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
endif
ifeq ($(ARCH),i386)
CROSS_COMPILE = i386-linux-
endif
ifeq ($(ARCH),mips)
CROSS_COMPILE = mips_4KC-
endif
#......中間略去

endif   # sparc
endif   # HOSTARCH,ARCH
endif   # CROSS_COMPILE

export  CROSS_COMPILE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 這上面一整段是配置交叉編譯工具鏈的字首,即arm-none-linux-gnueabi- ,一旦確定了字首,加上字尾就能定義在編譯過程中用到的各種工具,如ar、gcc等;
  • 字首的選擇是通過前面獲得的變數ARCH(目標cpu的架構)來判斷的
# load other configuration
include $(TOPDIR)/config.mk
  • 1
  • 2
  • 這裡包含了一個原始碼目錄下的config.mk檔案,和之前的那個x210_sd_config生成的include/config.mk不同,這個.mk檔案是原始碼自帶的,對編譯屬性和連結屬性進行了很多設定
  • 這個位於根目錄的config.mk被include進來,將在原地展開,但是由於程式碼量較大,故其註釋寫在了config.mk裡面,請移步 [ U-boot根目錄下的config.mk詳盡分析 ]

5.設定規則

OBJS  = cpu/$(CPU)/start.o
ifeq ($(CPU),i386)
OBJS += cpu/$(CPU)/start16.o
OBJS += cpu/$(CPU)/reset.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += cpu/$(CPU)/resetvec.o
endif

OBJS := $(addprefix $(obj),$(OBJS))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 這裡將眾多.o檔案賦給了OBJS變數,這個變數會成為後面目標u-boot的依賴
  • 其中用到了多次+=賦值符號,此賦值符號的含義其實是在變數原本的值後面在續上新的值,就是額外新增的意思
LIBS  = lib_generic/libgeneric.a
LIBS += $(shell if [ -f board/$(VENDOR)/common/Makefile ]; then echo \
    "board/$(VENDOR)/common/lib$(VENDOR).a"; fi)
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
ifeq ($(CPU),ixp)
LIBS += cpu/ixp/npe/libnpe.a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
    fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += drivers/bios_emulator/libatibiosemu.a
#......中間略去
ifeq ($(CPU),mpc83xx)
LIBS += drivers/qe/qe.a
endif
ifeq ($(CPU),mpc85xx)
LIBS += drivers/qe/qe.a
endif
LIBS += drivers/rtc/librtc.a
#......中間略去
LIBS += post/libpost.a

LIBS := $(addprefix $(obj),$(LIBS))
.PHONY : $(LIBS) $(VERSION_FILE)

LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
LIBBOARD := $(addprefix $(obj),$(LIBBOARD))

# Add GCC lib
PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 將眾多.a庫檔案賦給了LIBS變數,以及將和板子有關的.a檔案賦給了LIBBOARD變數,這兩個變數會成為後面目標u-boot的依賴
SUBDIRS = tools \
      examples \
      api_examples

.PHONY : $(SUBDIRS)

ifeq ($(CONFIG_NAND_U_BOOT),y)
NAND_SPL = nand_spl
U_BOOT_NAND = $(obj)u-boot-nand.bin
endif

ifeq ($(CONFIG_ONENAND_U_BOOT),y)
ONENAND_IPL = onenand_bl1
U_BOOT_ONENAND = $(obj)u-boot-onenand.bin
endif

__OBJS := $(subst $(obj),,$(OBJS))
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 根據autoconf.mk中nand和onenand的CONFIG做了一個判斷,是否使用相應的.bin檔案
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) $(obj)u-boot.dis
ifeq ($(ARCH),blackfin)
ALL += $(obj)u-boot.ldr
endif

all:        $(ALL)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 本段出現了頂層makefile的第一條規則(即目標-依賴-操作),故預設情況下將以all這個變數作為最終目標。但是由於本條規則沒有任何操作, 所以一旦把依賴(也就是$(ALL))實現了,整個makefile檔案的最終目標就達成了
  • 可以認為,本makefile的終極目標其實是$(ALL)所代表的那些檔案
$(obj)u-boot.hex:	$(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O ihex $< [email protected]

$(obj)u-boot.srec:	$(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O srec $< [email protected]

$(obj)u-boot.bin:	$(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O binary $< [email protected]

$(obj)u-boot.ldr:	$(obj)u-boot
        $(LDR) -T $(CONFIG_BFIN_CPU) -f -c [email protected] $< $(LDR_FLAGS)

$(obj)u-boot.ldr.hex:	$(obj)u-boot.ldr
        $(OBJCOPY) ${OBJCFLAGS} -O ihex $< [email protected] -I binary

$(obj)u-boot.ldr.srec:	$(obj)u-boot.ldr
        $(OBJCOPY) ${OBJCFLAGS} -O srec $< [email protected] -I binary

$(obj)u-boot.img:	$(obj)u-boot.bin
        ./tools/mkimage -A $(ARCH) -T firmware -C none \
        -a $(TEXT_BASE) -e 0 \
        -n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
            sed -e 's/"[     ]*$$/ for $(BOARD) board"/') \
        -d $< [email protected]

$(obj)u-boot.sha1:	$(obj)u-boot.bin


#           後面還有一大堆繁瑣但不是很重要的程式碼,就不貼了
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 這些都是為了產生最終檔案的規則
$(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h
    @$(XECHO) Generating [email protected] ; \
    set -e ; \
    : Generate the dependancies ; \
    $(CC) -x c -DDO_DEPS_ONLY -M $(HOST_CFLAGS) $(CPPFLAGS) \
        -MQ $(obj)include/autoconf.mk include/common.h > [email protected]

$(obj)include/autoconf.mk: $(obj)include/config.h
    @$(XECHO) Generating [email protected] ; \
    set -e ; \
    : Extract the config macros ; \
    $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
        sed -n -f tools/scripts/define2mk.sed > [email protected]

sinclude $(obj)include/autoconf.mk.dep
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 本段的功能是根據include/configs/x210_sd.h來生成autoconf.mk,makefile利用這些autoconf.mk中的變數來指導編譯過程的走向(條件編譯)

6.設定與cpu相關的偽目標

#由於這些程式碼都與cpu本身有關,有2000多行,且功能重複,故這裡挑選我們板子上的s5pv210為例子來分析,這行程式碼大概位於2600多行。

x210_sd_config :    unconfig
    @$(MKCONFIG)