天天看點

uboot筆記之makefile分析

   開始學習uboot,對于linux我還是個新手,在這隻是對學習uboot做下筆記,文中錯誤之處請諒解。使用的uboot版本是2009.11。

  要了解一個linux工程,一般要大緻看懂它的makefile檔案,我在學習uboot時也是先從其Makefile檔案看起的,uboot的主Makefile就有三千多行,還有其他子檔案夾中的Makefile。如果想我一樣對linux還是個新手,那麼一開始接觸Makefile可能會很頭痛。可以先看一下于鳳昌翻譯的《GUN Make使用手冊》,如果你英語好可以參考原版。

  首先,uboot第一次編譯,make順序是1.make mini2440_config 2.make. 這裡假設已經為mini2440移植好了。在主Makefile中找到目标mini2440_config,如下:

mini2440_config : unconfig

         @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 gc5084 s3c24x0

mini2440依賴于unconfig項,是以會去看unconfig目标,而此目标沒有依賴項,是以它是最新的,unconfig一定會執行

unconfig:

    @rm -f $(obj)include/config.h $(obj)include/config.mk \

    $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \

    $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep

這将删除所列出的這些檔案,這些檔案都是make mini2440_config後會産生的檔案。這裡有一個$(obj)變量,這個變量是在指定了輸出目錄時會有的,否則為空值。指定輸出目錄有兩種方法,在README檔案中有說明,第一種定義環境變量,如export BUILD_DIR=/tmp/build,然後在make mini2440_config.第二種是加在make指令中,如make O=/tmp/build/ mini2440_config (是O不是0)下面看下如何由O或者BUILD_DIR變量産生obj變量,主makefile中,大約88行,

ifdef O

ifeq ("$(origin O)", "command line")

BUILD_DIR := $(O)

endif

origin函數給出相應變量的原始類型,參考GUN Make使用手冊,如果定義了O變量且類型是command line 則BUILD_DIR變量等于變量O。這順便可以看出如果同時定義了環境變量和O變量,O變量會重寫環境變量。接下來看

 ifneq ($(BUILD_DIR),)    #如果BUILD_DIR變量不能于空

 saved-output := $(BUILD_DIR) #定義saved-output變量等于BUILD_DIR變量

 # Attempt to create a output directory.

 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) #[]代表test,-d是否是檔案夾。如果沒有BUILD_DIR檔案  夾,則建立。-p為強制。

 # Verify if it was successful.

 BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) #進入目錄并列印出路徑

 $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) #如果不存在BUILD_DIR則    輸出錯誤資訊

 endif # ifneq ($(BUILD_DIR),)

 #接下來都是定義根據BULID_DIR依次定義各個變量。

 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))

 SRCTREE := $(CURDIR) #CURDIR是make中的标準變量 不指定-C就是目前目錄。

 TOPDIR := $(SRCTREE)

 LNDIR := $(OBJTREE)

 export TOPDIR SRCTREE OBJTREE

 MKCONFIG := $(SRCTREE)/mkconfig

 export MKCONFIG

 ifneq ($(OBJTREE),$(SRCTREE))

 REMOTE_BUILD := 1

 export REMOTE_BUILD

 endif

 # $(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.

 # 上面的英文注釋,說明這時還沒有包含config.mk檔案,但是因為要用到是以定義了obj src變量

 ifneq ($(OBJTREE),$(SRCTREE)) #根據上面,如果定義了BULID_DIR,一般這個了變量就不等了

 obj := $(OBJTREE)/

 src := $(SRCTREE)/

 else                

 obj :=

 src :=

 export obj src

根據上面詳細的分析,已經解釋清楚了 obj src變量的來源。

接着回到這句

            @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 gc5084 s3c24x0

@起始的行将不回顯指令,MKCONFIG變量代表mkconfig腳本檔案

$(@:_config=) 的結果是mini2440。它來源于$(var:xx=yy)此句表示将變量var中以xx結尾的部分替換成yy。$@代表目标檔案mini2440_config.注意在$()的括号中的變量是不需要再加$的。

再看mkconfig腳本,簡要說明一下。

一開始的while循環是處理帶-參數的,一般不會有。檢查參數等。

 if [ "$SRCTREE" != "$OBJTREE" ] ; then

這段是如果源檔案和目标檔案不是在一起和在一起,分别用不同的方法,進入include檔案夾,删除舊的asm連接配接檔案夾,并建立新的asm連接配接到asm-$2檔案夾,$2變量在這是arm。

然後删除asm-$2的連接配接,然後在建立連接配接。

再在include檔案夾下建立一個config.mk檔案,這個檔案内容按照本例應該為ARCH = arm CPU = arm920t BOARD = mini2440 VENDOR = gc5084 SOC = s3c24x0。最後建立一個config.h,這個檔案也隻有兩個,分别為#include 和#include 。

這樣所有make mini2440_config所做的事情就都做完了。接着回看主makefiel.

在主Makefle中,有一個條件語句很重要,大約在148行有如下一句,

 ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))

此句判斷有沒有生成include/config.mk檔案,有這個檔案則makefile認為是配置過了make mini2440_config。是以再make時就會包含其下面的部分,這部分很大,為了友善描述稱這部分為A部分,否則則包含else部分(稱為B部分),else大約在483行,else後隻是簡單報錯退出。

下面就繼續看,配置後,make所包含的這個A部分。

在這個條件語句後緊接着,包含include/autoconf.mk.dep include/autoconf include/config.mk

這兩句也很重要,它是生成autoconf.mk和autoconf.mk.dep的起因。makefile在包含其他makefiel時即有(s)include語句時,會嘗試更新它,即以被包含的makefile作為一個目标去make。這樣隻要是運作過make mini2440_config。在make任何目标,都會進入A部分。A部分中有這兩個.mk檔案為目标的規則,如下:

$(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h

         @$(XECHO) Generating $@ ; \

         set -e ; \

         : Generate the dependancies ; \

         $(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) \

                 -MQ $(obj)include/autoconf.mk include/common.h > $@

 $(obj)include/autoconf.mk: $(obj)include/config.h

         : Extract the config macros ; \

         $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \

                 sed -n -f tools/scripts/define2mk.sed > [email protected] && \

         mv [email protected] $@

這個規則看起來稍微有點複雜,但是主要就是根據include/common.h其中包含了config.h等檔案,利用define2mk.sed腳本。處理這些頭檔案中的宏部分,生成一個整體的可被此主Makefil識别的autoconfi.mk檔案。autoconfig.mk.dep檔案内容是autoconfig.mk檔案産生時所依賴的有那些檔案。這部分詳細可以參考gcc手冊和編寫sed腳本的資料。

介紹完autoconfig.mk檔案部分。繼續看A部分中的其他部分。

 然後接着一般要添加一句CROSS_COMPILE := arm-linux-,定義你已經安裝好的交叉編譯工具,這裡是arm-linux-。在包含頂層config.mk。這個檔案中有配置了很多編譯相關的變量,此mk檔案内容大緻如下:

ifneq ($(OBJTREE),$(SRCTREE)) #是否有定義輸出目标,分别生成obj和src變量的值。

 ifeq ($(CURDIR),$(SRCTREE)) #目前檔案夾是否和源檔案檔案夾相同。一般是相同的

 dir :=

 else

 dir := $(subst $(SRCTREE)/,,$(CURDIR))

 obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)

 src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)

 $(shell mkdir -p $(obj))

 else            #未定義輸出目标檔案夾,則為空。

然後是主機編譯器HOSTCC等。

cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \

> /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)

上面這句需要看看,之後很多call函數會調用它,~~

再聲明一些編譯工具變量

 AS = $(CROSS_COMPILE)as

 LD = $(CROSS_COMPILE)ld

 CC = $(CROSS_COMPILE)gcc

 CPP = $(CC) -E

 AR = $(CROSS_COMPILE)ar

 NM = $(CROSS_COMPILE)nm

 LDR = $(CROSS_COMPILE)ldr

 STRIP = $(CROSS_COMPILE)strip

 OBJCOPY = $(CROSS_COMPILE)objcopy

 OBJDUMP = $(CROSS_COMPILE)objdump

 RANLIB = $(CROSS_COMPILE)RANLIB

然後根據配置項包含各個檔案夾下的config.mk檔案。

ifdef ARCH    #此.mk檔案内容主要是指定一些和體系結構特定的編譯選項,最後的LDSCRIPT定義了uboot鏡像的位址配置設定檔案

 sinclude $(TOPDIR)/lib_$(ARCH)/config.mk # include architecture dependend rules

#CPU 和 SOC 的.mk檔案定義了浮點數等編譯選項,~~

 ifdef CPU    

 sinclude $(TOPDIR)/cpu/$(CPU)/config.mk # include CPU specific rules

 ifdef SOC

 sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk # include SoC specific rules

 ifdef VENDOR

 BOARDDIR = $(VENDOR)/$(BOARD)

 BOARDDIR = $(BOARD)

 ifdef BOARD    #此.mk檔案僅僅定義了TEXT_BASE,鏡像加載到記憶體的起始位址。

 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules

然後配置了CPPFLAGS,CFLAGS等編譯選項。編譯選項部分可參考其他資料。~~

最後有指定編譯規則,如下

BCURDIR := $(notdir $(CURDIR)) #notdir是去掉路徑部分。

#以下都是編譯各種類型檔案時的規則,主要是第一個變量定義的規則。(将以下檔案類型注明!)

 $(obj)%.s: %.S

         $(CPP) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $

 $(obj)%.o: %.S

         $(CC) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $ -c

 $(obj)%.o: %.c

         $(CC) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c

 $(obj)%.i: %.c

         $(CPP) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c

 $(obj)%.s: %.c

         $(CC) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c -S

再回到主Makefile中。

定義OBJS和LIBS變量,OBJS代表目标檔案,本例中隻會包含start.o。LIBS是庫檔案,主要包含以下檔案夾lib_generic,cpu,fs,driver,commom等,下的一些.a庫檔案。

然後會添加gcc的庫。

下面兩句是定義all目标的規則,all目标是make的第一個目标即預設目标。它依賴變量ALL,ALL為各種格式的鏡像。

ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)

all: $(ALL)

在下面是一堆各種鏡像生成的規則,這裡看一下其中uboot.bin的規則。

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

                 $(OBJCOPY) ${OBJCFLAGS} -O binary $ $@ #OBJCOPY是在

頂層config.mk檔案中定義的轉換工具.

在看它的依賴項$(obj)uboot的生成規則。

$(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds

                 $(GEN_UBOOT)

依次看其依賴項,

*****目标depend*****

depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk

                 for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done

 其中依賴項$(TIMESTAMP)是一個時間标志檔案,$(VERSION_FILE)是個版本标志檔案,并且被聲明為假想目标是以一定會被執行,執行的結果是更新這兩個檔案,autoconf.mk上面已經說過。這個規則中的指令是依次循環進入$(SUBDIRS)目錄中執行make _depend,子目錄中的makefile稍後分析。$(SUBDIRS)包含tools example/standalone example/api.

******假想目标$(SUBDIRS)******

$(SUBDIRS): depend

                 $(MAKE) -C $@ all

$@代表規則中的目标,是以這條指令會在$(SUBDIRS)下執行的make all,$(SUBDIRS)的值上面已經說過。依賴目标depend參考上面。即會在tool,example等目錄下make。

******目标$(OBJS)******

$(OBJS): depend

                 $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))

make這個目錄cpu/arm920t

******目标$(LIBS)******

$(LIBS): depend $(SUBDIRS)

                 $(MAKE) -C $(dir $(subst $(obj),,$@))

這個LIBS變量中的檔案比較多,依次進入這些目錄make。這樣可以産生相應的庫檔案

******目标LDSCRIPT******

$(LDSCRIPT): depend

                  $(MAKE) -C $(dir $@) $(notdir $@)

這個目标的變量是一個連接配接的腳本,上文有提到。make它。

現在看以下各個子目錄中Makefile檔案,以cpu/arm920t目錄為例.

 include $(TOPDIR)/config.mk    #包含頂層config.mk,這個檔案内容上面有提及。

 LIB = $(obj)lib$(CPU).a

 START = start.o

 COBJS-y += cpu.o

 COBJS-$(CONFIG_USE_IRQ) += interrupts.o

 SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c) #所有源檔案變量

 OBJS := $(addprefix $(obj),$(COBJS-y) $(SOBJS))

 START := $(addprefix $(obj),$(START))

  #make的目标,這個規則是靠隐含規則執行的。依賴項.depend,此檔案包含本目錄所有源檔案所依賴的檔案。稍後分析如何産生這個檔案。

 all: $(obj).depend $(START) $(LIB)

 $(LIB): $(OBJS)        #庫檔案靠目标檔案通過AR轉換而來

         $(AR) $(ARFLAGS) $@ $(OBJS)

 #########################################################################

 # defines $(obj).depend target

 include $(SRCTREE)/rules.mk    #産生.depend檔案的makefile

 sinclude $(obj).depend        #包含.depend檔案

再貼出rules.mk

_depend: $(obj).depend

 $(obj).depend: $(src)Makefile $(TOPDIR)/config.mk $(SRCS)

                 @rm -f $@

                 @for f in $(SRCS); do \

                         g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \

                         $(CC) -M $(HOSTCFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \

                 done

整體看一下make的流程,make 找到預設目标all 它首先依賴.depend檔案。而這個目标在此Makefile最後用sinclude包含,這個目标會利用編譯器的功能列出$(SRCS)所有源檔案的依賴。則all的依賴.depend ,$(START)和 $(LIB) 組成所有依賴的檔案,然後隐含規則編譯所有檔案。

然後我們回到主makefile,uboot的makefile主要的内容也就差不多完了,剩下的很多行就是各個_config項,如mini2440_config,最後有clean等清除作用的目标。