u-boot Makefile整體解析


一、概述   

1、理解u-boot的makefile需要的准備

  linux常用命令、shell腳本基礎知識、makefile腳本基礎知識

2、Makefile的元素

    萬變不離其宗,無論工程多么復雜,文件多么龐大,其實源於最簡單的makefile。Makefile典型的規則如下。 

目標:依賴1,依賴2••••••

       命令

     舉一個簡單的例子

nand.bin : head.o nand.o main.o arm-linux-ld -Tnand.lds -o nand_elf head.o main.o arm-linux-objcopy -O binary -S nand_elf nand.bin 
head.
o:head.S arm-linux-gcc -Wall -c -o head.o head.S
nand.
o:nand.c s3c2440_addr.h arm-linux-gcc -Wall -c -o nand.o nand.c
main.
o:main.c s3c2440_addr.h arm-linux-gcc -Wall -c -o main.o main.c
clean:
rm -f nand.bin nand_elf head.o nand.o main.o

     形象的表達:待實現一個產品(目標),這個目的需要確定的原材料才能實現(依賴),還需要一套加工手段來制作(編譯規則)。

      當最初的makefile設計完成后,為了實現可裁剪、自動化編譯等目的,不得不加入更多的makefile規則,當然make工具相應的功能也需要增加。可以肯定:這些增加的規則無非是更加方便、靈活的生成指定目標,更加方便的生成依賴關系,更加方便的生成編譯規則。 

舉例:

    更加方便靈活的的生成指定目標:通過選項指定生成目標的路徑

    更加方便的生成依賴:自動生成依賴文件、通過變量選擇編譯的源碼

    更加方便的生成指定規則:隱含規則、模式規則、通過變量選擇使用的編譯器及選項

3、u-boot Makefile體系的組成    

   Uboot是一個龐大的工程,需要從眾多的源文件中選擇部分源文件,采用合適的編譯手段,生成特定的目標文件。這不是一個簡單的任務,需要頂層的makefile,眾多的頂層makefile,makefile的include文件(config.mk、rules.mk)等來完成這個工作。

u-bootMakefile 體系
名    稱 描    述
頂層Makefile 從總體上控制着u-boot的編譯、連接,定義總目標u-boot.bin
頂層config.mk 規定了編譯的規則,被所有Makefile所調用
頂層rules.mk 生成依賴關系,被各級子目錄Makefile所調用
各級子目錄Makefile 決定當前目錄的編譯、連接
頂層mkconfig 在編譯之前運行,為編譯做准備

  mkconfig(shel l腳本)是在編譯之前做一些准備,筆者認為可以不算做Makefile集體中的一部分。

u-boot編譯過程中生成的與編譯相關的文件
名稱 描述
include/config.mk mkconfig所生成,被頂層Makefile所包含,定義ARCH、CPU等全局變量
各級子目錄.depend 各級子目錄makefile所生成,並被各級子目錄makefile所包含,定義依賴關系

 二、Makefile的目標

1、Makefile的總目標

        頂層Makefile的總目標all是一個偽目標,有幾個分目標(目標文件)組成:$(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)。其實,用戶也可以添加其他目標,如$(obj)u-boot.dis、$(obj)u-boot.img、$(obj)u-boot.hex等等。由於是一個偽目標,所以只要輸入命令make all,總會重建幾個分目標。 

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

2、Makefile的各級子目錄中的目標

  子目錄中的all定義了當前目錄所有要生成的目標,LIB定義了當前目錄要生成的靜態庫,OBJS定義了當前目錄由“.c”文件編譯生成的目標“.o”,SOBJS定義了當前目錄由“.S”文件編譯生成的目標“.o”。

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

 三、Makefile的依賴

1、總目標的依賴

$(obj)u-boot:        depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)

  總目標的依賴是由“depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)”構成,重要的元素是“$(OBJS) $(LIBS)”。下面,我們看看“$(OBJS) $(LIBS)”,它們又是什么構成的,從而查看u-boot是怎樣選擇參與編譯連接的文件的。

      編譯的過程是(*.c *.S) -- > (*.o *.a) --> (*.bin),但是make工具分析Makefile的過程是相反的。為了生成(*.bin) 需要依賴(*.o *.a),為了生成(*.o *.a)間接的又需要(*.c *.S),可以說(*.c *.S)是最初的依賴(原材料)。

     Uboot是一個通用的啟動代碼,可以在眾多的硬件平台上例如arm、powerpc,和操作系統上運行,例如vxworks、linux等等。源碼中有相應的源文件包,例如lib_ppc,lib_arm等等。其實,還有各種驅動,網卡、顯示器、串口、鍵盤等等。但是,對於一個特定的應用目標,它的硬件平台是確定的,所用的操作系統也是確定的,它的外設也是確定的。所以,需要對這些源文件進行裁剪。

      在執行make all之前,首先要執行make *_config例如make smdk2410_config,通過執行mkconfig腳本,可以確定所用cpu的架構(ARCH)例如arm ,cpu的種類(CPU)例如arm920t,開發板(BOARD)例如smdk2410 ,soc(SOC)例如 s3c24x0,這些信息保存在mkconfig腳本生成的makefile包含文件include/config.mk中。include/config.mk會被包含進入頂層makefile中,根據這個文件所定義的變量值,從而確定用那些文件(依賴),例如lib_arm/,board/smdk2410,cpu/arm920t等等。這是一種大方向上的裁剪方式。

      至於所用外設之類的裁剪方式,不是通過makefile來實現的,而是通過C語言的預處理實現的,配置文件是include/configs/<board_name>.h,例如include/configs/smdk2410.h。倘若smdk2410開發板上有CS8900網卡,可以通過在配置文件中定義一個宏來實現對網卡驅動的調用,例如“#define CONFIG_DRIVER_CS8900       1”。

    本文主要討論的是makefile的分析,所以就來看看makefile是怎樣裁剪源文件的。 

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),mpc83xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),mpc86xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
ifeq ($(CPU),bf533)
OBJS += cpu/$(CPU)/start1.o      cpu/$(CPU)/interrupt.o  cpu/$(CPU)/cache.o
OBJS += cpu/$(CPU)/cplbhdlr.o   cpu/$(CPU)/cplbmgr.o   cpu/$(CPU)/flush.o
endif

LIBS  = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).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 += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
View Code

       可以看到,通過調用ARCH、CPU、BOARD、SOC等變量來確定總目標依賴來的構成。

2、各級子母錄的依賴

     依賴關系對於更新了源文件重建目標文件來說是重要的。比如下邊這個依賴關系,我們希望在更改了nand.c,或者更改了s3c2440_addr.h后,執行make都能重建nand.o。 

nand.o:nand.c s3c2440_addr.h

     通常c源文件更新了,對應的目標文件需要重建,由於模式規則(%.o:%.c)的支持,是能自動實現的。如果c源程序包含的頭文件如果改變了,也需要重建此c源程序的目標文件。但是,困難在於描述c源程序依賴的頭文件相當困難,因為c源程序當前文件包含的頭文件可能本身還包含其他的頭文件,而且人工描述頭文件的依賴實在是麻煩的工作。

    gcc提供了一種自動產生依賴關系的方法,例如假設想查看test.c的依賴,執行命令gcc –M test.c就OK了。

    Uboot自動生成依賴關系的方法是在每個子文件夾的makefile中調用rules.mk生成的.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 $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
              done
#########################################################################

 四、makefile編譯規則

    源文件(*.c *.S) -- > 目標文件、庫文件(*.o *.a) -->總目標 (*.srec *.bin *.map)。

 1、 總目標的編譯規則 庫文件 (*.o *.a) -->總目標 (*.srec *.bin *.map)

all:          $(ALL)

$(obj)u-boot.hex:   $(obj)u-boot
              $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@

$(obj)u-boot.srec:   $(obj)u-boot
              $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@

$(obj)u-boot.bin:    $(obj)u-boot
              $(OBJCOPY) ${OBJCFLAGS} -O 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 $< $@

$(obj)u-boot.dis:    $(obj)u-boot
              $(OBJDUMP) -d $< > $@

$(obj)u-boot:         depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
              UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
              cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
                     --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
                     -Map u-boot.map -o u-boot

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

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

$(SUBDIRS):
              $(MAKE) -C $@ all

$(NAND_SPL):     version
              $(MAKE) -C nand_spl/board/$(BOARDDIR) all

$(U_BOOT_NAND):     $(NAND_SPL) $(obj)u-boot.bin
              cat $(obj)nand_spl/u-boot-spl-16k.bin $(obj)u-boot.bin > $(obj)u-boot-nand.bin

version:
              @echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
              echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
              echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
                      $(TOPDIR)) >> $(VERSION_FILE); \
              echo "\"" >> $(VERSION_FILE)

gdbtools:
              $(MAKE) -C tools/gdb all || exit 1

updater:
              $(MAKE) -C tools/updater all || exit 1

env:
              $(MAKE) -C tools/env all || exit 1

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

tags ctags:
              ctags -w -o $(OBJTREE)/ctags `find $(SUBDIRS) include \
                            lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) \
                            fs/cramfs fs/fat fs/fdos fs/jffs2 \
                            net disk rtc dtt drivers drivers/sk98lin common \
                     \( -name CVS -prune \) -o \( -name '*.[ch]' -print \)`

etags:
              etags -a -o $(OBJTREE)/etags `find $(SUBDIRS) include \
                            lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) \
                            fs/cramfs fs/fat fs/fdos fs/jffs2 \
                            net disk rtc dtt drivers drivers/sk98lin common \
                     \( -name CVS -prune \) -o \( -name '*.[ch]' -print \)`

$(obj)System.map: $(obj)u-boot
              @$(NM) $< | \
              grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
              sort > $(obj)System.map
View Code

 2、子目錄中的編譯規則   源文件(*.c *.S) -- > 目標文件、庫文件(*.o *.a) 

  子目錄中的編譯規則是在頂層目錄的config.mk中給出,如下所示。

$(obj)%.s:      %.S
       $(CPP) $(AFLAGS) -o $@ $<

$(obj)%.o:      %.S
       $(CC) $(AFLAGS) -c -o $@ $<

$(obj)%.o:      %.c
       $(CC) $(CFLAGS) -c -o $@ $<

     庫文件的編譯規則在各子目錄中的makefile中給出,舉例board/smdk2410/libsmdk2410.a的生成規則。   board/smdk2410文件夾中的makefile內容如下: 

$(LIB):   $(obj).depend $(OBJS) $(SOBJS)
       $(AR) $(ARFLAGS) $@ $(OBJS) $(SOBJS)

    注意:分析Makefile的細節,必須要分析它的包含文件config.mk,它確定了程序編譯、連接的選項,以及目標文件生成的規則等等內容。 

五、u-boot整個編譯過程

1、makefile整體解析過程

  為了生成u-boot.bin這個文件,首先要生成構成u-boot.bin的各個庫文件、目標文件。為了各個庫文件、目標文件就必須進入各個子目錄執行其中的Makefile。由此,確定了整個編譯的命令順序

2、makefile整體編譯過程

  首先,根據各個庫文件、目標文件出現的先后順序,依次進入各個子目錄編譯從而生成這些目標

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

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

  然后,回到頂層目錄,繼續執行頂層Makefile的總目標,最后生成u-boot.bin。

六、u-boot編譯的特點

1、編譯子目錄

  編譯子目錄的方法是進入子目錄,然后執行子目錄中的Makefile

2、子目錄Makefile的內容

  子目錄Makefile的編譯規則以及依賴是由頂層的config.mk、rules.mk構成,子目錄的Makefile在使用時是用include包含進來的。

 

附:uboot下載地址:ftp://ftp.denx.de/pub/u-boot/

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM