一、概述
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)等來完成這個工作。
名 稱 | 描 述 |
頂層Makefile | 從總體上控制着u-boot的編譯、連接,定義總目標u-boot.bin |
頂層config.mk | 規定了編譯的規則,被所有Makefile所調用 |
頂層rules.mk | 生成依賴關系,被各級子目錄Makefile所調用 |
各級子目錄Makefile | 決定當前目錄的編譯、連接 |
頂層mkconfig | 在編譯之前運行,為編譯做准備 |
mkconfig(shel l腳本)是在編譯之前做一些准備,筆者認為可以不算做Makefile集體中的一部分。
名稱 | 描述 |
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)
可以看到,通過調用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
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/