Uboot1.1.6配置、編譯、鏈接分析

       Tiny6410上採用的處理器是S3C6410,我們首先要做的是從網上下載三星公司針對s3c6410修改過的s3c-u-boot-1.1.6的源碼,然後對這個源碼進行分析,最後再將源碼移植到tiny6410上。

1、uboot初見

 我從網上下載的源碼是s3c-u-boot-1.1.6_rel-4-3-2_20080917.tar.bz2,解壓後得到s3c-u-boot-1.1.6這個目錄。根據頂層的README文件我們可以獲取到如下信息:
1.1 頂層目錄結構
Board:和一些已有開發板相關的文件,比如Makefile和u-boot.lds等都和具體開發板的硬件和地址分配有關。
Common:與體系結構無關的文件,實現各種命令的C文件。
CPU:CPU相關文件,其中的子目錄都是以u-boot所支持的CPU爲名,比如有子目錄arm926ejs、mips、mpc8260和nios等。
Disk:硬盤接口程序
Doc:開發、試用文檔。
Drivers:通用設備驅動程序,比如各種網卡、支持CFI的flash、串口和USB總線等。
Dtt:數字溫度測量器或者傳感器的驅動
Examples:一些獨立運行的應用程序的例子。
Fs:支持文件系統的文件,u-boot現在支持cramfs、fat、fdos、jffs2、yaffs和registerfs。
Include:頭文件,還有對各種硬件平臺支持的會變文件,系統的配置文件和對文件系統支持的文件。
Lib_arm:存放對ARM體系結構通用的文件,主要用於實現ARM平臺通用的函數,與ARM體系結構相關的代碼。
Lib_*:某一架構通用的文件。
Lib_generic:通用的多功能函數實現。
Nand_spl:Uboot一般從ROM、NORFlash等設備啓動,現在開始執行NANDFlash啓動,但是支持的CPU種類還不多。
Net:與網絡有關的代碼,BOOTP協議、TFTP協議RARP協議和NFS文件系統的實現。
Post:上電自檢程序。
Rtc: 實時時鐘驅動。
Tools:創建S-Record格式文件和U-BOOT images的工具。
1.2 編譯程序
進入源碼目錄執行:
make distclean --清除所有生成的文件
make NAME_config --根據自己使用的開發板進行配置
make all --編譯源碼

2、uboot的配置過程
從README文件中我們知道了make NAME_config是用來對自己的開發板進行配置的,那就先來看下到底是怎麼配置的。
這邊假設我們的板子是smdk6410,那麼應該在源碼目錄執行make smdk6410_config
make調用的是頂層目錄下的Makefile文件,我們傳參數是smdk6410_config,
所以在Makefile中找到smdk6410_config的相應部分如下:
smdk6410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s3c64xx smdk6410 samsung s3c6410 
先分析下這條規則:
smdk6410_config:規則的目標名

unconfig:規則的依賴,這個在Makefile文件的342行可以找到,如下:
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
這個就不在分析,就是刪除一些文件。

第二行開頭的@:表示該行命令的輸出被抑制,即執行的時候不會在終端上打印出來。

$(MKCONFIG):MKCONFIG這個變量在92行定義MKCONFIG:=$(SRCTREE)/mkconfig,而變量SRCTREE在87行定義SRCTREE :=$(CURDIR),而CURDIR是Makefile自帶的變量表示當前目錄,所以$(MKCONFIG)就是當前目錄下的mkconfig文件,即頂層目錄下的mkconfig文件。
$(@:_config=):$@是make的自動變量,標識規則的目標名,即smdk6410_config。$(@:_config=)意思就是 $@ 中的“_config”替換爲空,也就是刪掉目標中“_config”這個子串。所以這邊$(@:_config=)爲smdk6410。
這樣看的話在配置時,執行make smdk6410_config實際上執行的是:
./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410
到這才真正地開始配置開發板,接下來帶着這條命令詳細分析下mkconfig這個文件。
1 #!/bin/sh -e 
//選項-e表示一個命令在執行後返回一個非0狀態值時,就退出
2
3 # Script to create header files and links to configure
4 # U-Boot for a specific board.
5 #
6 # Parameters: Target Architecture CPU Board [VENDOR] [SOC]
7 #
8 # (C) 2002-2006 DENX Software Engineering, Wolfgang Denk <[email protected]>
9 #
10
11 APPEND=no # Default: Create new config file
//APPEND=no這邊表示重新創建一個文件,APPEND=yes表示將內容追加到原來的文件中
12 BOARD_NAME="" # Name to print in make output
//BOARD_NAME爲開發板的名字
13
14 while [ $# -gt 0 ] ; do
15 case "$1" in
16 --) shift ; break ;;
17 -a) shift ; APPEND=yes ;;
18 -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
19 *) break ;;
20 esac
21 done
//"$#" 將擴展成傳遞給腳本的參數的數目
//"$*" 將擴展成傳遞給腳本的所有參數
//"shift" 將$*中的剩餘的參數向左移動一個位置並減少$#的值1
//14-21行判斷“./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410”命令中是否有“--”,“-a”,“-n”等符號,
//這邊沒有,所以不做任何事情,11行和12行的變量仍然維持原來的值。
22
23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1"
//先判斷BOARD_NAME是否爲空,爲空將命令中的第一個參數smdk6410賦值給BOARD_NAME,即這邊BOARD_NAME爲“smdk6410”
24 
25 [ $# -lt 4 ] && exit 1
26 [ $# -gt 6 ] && exit 1
//"$#" 表示參數的個數,如果參數個數小於4或者大於6就退出,這邊$#=6,所以可以繼續往下執行。
27
28 echo "Configuring for ${BOARD_NAME} board..."
//打印出開發板的名字
29
30 #
31 # Create link to architecture specific headers
//創建體系結構相關的頭文件鏈接
32 #
33 if [ "$SRCTREE" != "$OBJTREE" ] ; then
34 mkdir -p ${OBJTREE}/include
35 mkdir -p ${OBJTREE}/include2
36 cd ${OBJTREE}/include2
37 rm -f asm
38 ln -s ${SRCTREE}/include/asm-$2 asm
39 LNPREFIX="../../include2/asm/"
40 cd ../include
41 rm -rf asm-$2
42 rm -f asm
43 mkdir asm-$2
44 ln -s asm-$2 asm
45 else
46 cd ./include
47 rm -f asm
48 ln -s asm-$2 asm
49 fi
//33行判斷源碼目錄SRCTREE和目標文件目錄SRCTREE是否一樣,SRCTREE和SRCTREE在頂層Makefile中定義,
//這邊要判斷,是因爲可以選擇在其他目錄下編譯u-boot的源碼,這樣可以令源代碼目錄保存乾淨,這時SRCTREE和SRCTREE的值就不一樣了
//不過我們一般都習慣直接就在源碼目錄下編譯的,所以SRCTREE和OBJTREE都爲當前目錄,
//那麼33行條件不成立,將執行else分支的代碼。
//46-48行進入include目錄,刪除asm文件(這是上一次配置時建立的鏈接文件),
//然後再次建立asm文件,並令它鏈接向asm-$2目錄,即asm-arm($2表示第二個參數arm)
50
51 rm -f asm-$2/arch
//刪除asm-$2/arch目錄,即刪除asm-arm/arch
52
53 if [ -z "$6" -o "$6" = "NULL" ] ; then
54 ln -s ${LNPREFIX}arch-$3 asm-$2/arch
55 else
56 ln -s ${LNPREFIX}arch-$6 asm-$2/arch
57 fi
//$6表示第6個參數s3c6410,不爲空,也不爲“NULL”,53行條件不滿足,將執行else分支
//其中LNPREFIX爲空,$6表示第6個參數s3c6410,$2表示第2個參數arm
//所以“ln -s ${LNPREFIX}arch-$6 asm-$2/arch”即ln -s arch-s3c6410 asm-arm/arch
//前面有進入include目錄,這時還在include目錄下,
//所以意思是在asm-arm下創建符號鏈接arch指向arch-s3c6410
58
59 # create link for s3c24xx SoC
60 if [ "$3" = "s3c24xx" ] ; then
61 rm -f regs.h
62 ln -s $6.h regs.h
63 rm -f asm-$2/arch
64 ln -s arch-$3 asm-$2/arch
65 fi
66
67 # create link for s3c64xx SoC
68 if [ "$3" = "s3c64xx" ] ; then
69 rm -f regs.h
70 ln -s $6.h regs.h
71 rm -f asm-$2/arch
72 ln -s arch-$3 asm-$2/arch
73 fi
//$3表示第三個參數s3c64xx,所以滿足68行的if語句,注意的是,這時還在include目錄下操作
//刪除regs.h文件,$6表示第6個參數s3c6410,所以“ln -s $6.h regs.h”表示“ln -s s3c6410.h regs.h”
//$2表示第2個參數arm,$3表示第三個參數s3c64xx,所以後面兩句分別是
//rm -f asm-arm/arch
//ln -s arch-s3c6xx asm-arm/arch
74
75 if [ "$2" = "arm" ] ; then
76 rm -f asm-$2/proc
77 ln -s ${LNPREFIX}proc-armv asm-$2/proc
78 fi
//$2表示第2個參數arm,75行條件滿足,還是在include目錄下操作
//“rm -f asm-$2/proc”表示“rm -f asm-arm/proc”
//${LNPREFIX}爲空,“ln -s ${LNPREFIX}proc-armv asm-$2/proc”表示“ln -s proc-armv asm-arm/proc”
79
80 # create link for s3c64xx-mp SoC
81 if [ "$3" = "s3c64xx-mp" ] ; then
82 rm -f regs.h
83 ln -s $6.h regs.h
84 rm -f asm-$2/arch
85 ln -s arch-$3 asm-$2/arch
86 fi
//$3表示第三個參數s3c64xx,81行條件不滿足
87 
88 #
89 # Create include file for Make
90 #
91 echo "ARCH = $2" > config.mk
92 echo "CPU = $3" >> config.mk
93 echo "BOARD = $4" >> config.mk
94
95 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
96 
97 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
//91-97行創建config.mk文件,並定義變量ARCH、CPU、BOARD、VENDOR、SOC
//91行用了'>'會創建config.mk文件,如果之前存在config.mk文件則將其清空,然後將變量輸入到文件中,
//其他行的'>>'會將變量追加到文件中
//最後config.mk文件中的內容爲:
//ARCH = arm
//CPU = s3c64xx
//BOARD = smdk6410
//VENDOR = samsung
//SOC = s3c6410
98
99 #
100 # Create board specific header file
101 #
102 if [ "$APPEND" = "yes" ] # Append to existing config file
103 then
104 echo >> config.h
105 else
106 > config.h # Create new config file
107 fi
//創建開發板相關的頭文件include/config.h
//前面定義了APPEN的值爲“no”,102行條件不成立,執行else分支
//'>'會創建config.h文件,如果原來存在config.h文件,則會覆蓋其中的內容清空。
108 echo "/* Automatically generated - do not edit */" >>config.h
109 echo "#include <configs/$1.h>" >>config.h
//108行表示向config.h中追加數據“/* Automatically generated - do not edit */”
//$1表示第一個參數smdk6410,所以109行表示向config.h文件中追加數據“#include <configs/smdk6410.h>”
110
111 exit 0
//執行完正常退出

現在總結下,配置命令“make smdk6410_config”,
實際的作用就是執行“./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410”命令,
其產生的結果如下:
1、設置開發板名稱BOARD_NAME爲smdk6410 
2、創建體系結構相關頭文件鏈接,如下所示:
ln -s asm-arm asm
ln -s arch-s3c64xx asm-arm/arch
ln -s proc-armv asm-arm/proc
3、創建頂層Makefile包含的頭文件include/config.mk,如下所示:
ARCH = arm
CPU = s3c64xx
BOARD = smdk6410
VENDOR = samsung
SOC = s3c6410
4、創建開發板相關頭文件include/config.h,如下所示:
/* Automatically generated - do not edit */
#include <configs/smdk6410.h>

3、uboot的編譯、鏈接過程
分析完了配置的命令make smdk6410_config,接下來分析下make all是如何進行編譯和鏈接的。
首先對整個Makefile進行分析,如下:
24 VERSION = 1 //主版本號
25 PATCHLEVEL = 1 //次版本號
26 SUBLEVEL = 6 //修正版本號
27 EXTRAVERSION = //擴展版本號
28 U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
//設置了這個uboot的版本U_BOOT_VERSION = 1.1.6
29 VERSION_FILE = $(obj)include/version_autogenerated.h
//$(obj)在這邊還未定義,所以VERSION_FILE = include/version_autogenerated.h,VERSION_FILE在289有用到,用於記錄uboot的版本信息。
30
31 HOSTARCH := $(shell uname -m | \
32 sed -e s/i.86/i386/ \
33 -e s/sun4u/sparc64/
34 -e s/arm.*/arm/ \
35 -e s/sa110/arm/ \
36 -e s/powerpc/ppc/ \
37 -e s/macppc/ppc/)
//首先執行shell命令uname -m得到i686/,通過管道傳送給sed命令,然後sed命令執行sed -e s/i.86/i386/,意思就是將i686替換成i386,可以自己查下sed命令的用法,因爲uname -m得到的只有i686,所以後面幾個-e選項就不用看了。之所以將i686改成i386,是因爲i686也屬於i386體系。再ubuntu-11.10xia執行uname -p可以查看到處理器類型爲i686,用uname -i可以查看到硬件平臺類型爲i386。最後的結果是HOSTARCH=i386,表示主機體系結構爲i386。
38
39 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
40 sed -e 's/\(cygwin\).*/cygwin/')
//首先執行shell命令uname -s得到Linux,表示linxu系統,然後通過管道傳送給tr命令,tr命令利用特殊字符類[:upper:]和[:lower:]將大寫字母轉化成小寫字母,即將Linux轉化爲linux,後面再執行sed命令,不過uname -s得到只有Linux,沒有cygwin之類的東西,所以sed可以不看了,最好的結果是HOSTOS=linux,表示主機操作系統爲linux。
41
42 export HOSTARCH HOSTOS
//用export 增加了HOSTARCH和HOSTOS這兩個環境變量,供下層的Makefile用
43
44 # Deal with colliding definitions from tcsh etc.
45 VENDOR=
//VENDOR表示芯片廠商,這邊設置爲空,不過在include/config.mk有設置成samsung,下面的程序會把include/config.mk這個文件包含到這個Makefile文件中include/config.mk是在make smdk6410_config的時候創建並設置變量的。

69 ifdef O
70 ifeq ("$(origin O)", "command line")
71 BUILD_DIR := $(O)
72 endif
73 endif
//Uboot支持將目標文件生成在外部的文件夾中,有兩種命令可以實現:一是加O選項,比如make O=/tmp/build all將目標文件放在/tmp/build;二是設置環境變量BUILD_DIR,比如export BUILD_DIR=/tmp/build將目標文件放在/tmp/build。如果以上兩種方式都沒有定義,那麼它將會被存放在源碼目錄下。
//69-73的意思如果定義可O選項,並且並且O=指定的目錄和command line指定的目錄一樣,BUILD_DIR就爲O=定義的目錄。origin是Makefile中自帶的函數,他並不操作

變量的值,只是告訴你這個變量從哪裏來自己可以去查下具體的用法。command line就是我們編譯的時候輸入的命名,如make O=/tmp/build all。
//實際上我們編譯的時候用的是make all,沒有指定O選項,所以69-73不執行。

74
75 ifneq ($(BUILD_DIR),)
76 saved-output := $(BUILD_DIR)
77
78 # Attempt to create a output directory.
79 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
80
81 # Verify if it was successful.
82 BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
83 $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
84 endif # ifneq ($(BUILD_DIR),)
//75行 判斷BUILD_DIR是否爲0,若不爲0,則save-output保存BUILD_DIR指定的輸出目錄
//79行 判斷BUILD_DIR這個目錄是否存在,如果不存在,就創建這個目錄
//82行 先進入這個目錄,再調用pwd顯示當前路徑,在將這個路徑值賦給BUILD_DIR
//83行 如果BUILD_DIR還不存在的話,則輸出saved-output中的目錄does not exist
//實際上我們編譯的時候用的是make all,沒有設置BUILD_DIR,所以75-84不執行。
85
86 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
//判斷變量BUILD_DIR是否爲空,如果不爲空,則OBJTREE等於BUILD_DIR的值,如果爲空,則OBJTREE等於CURDIR的值,CURDIR是Makefile自帶的變量,表示當前目錄。實際上這邊我們沒有設置BUILD_DIR變量,所以OBJTREE=$(CURDIR),即OBJTREE爲當前目錄。OBJTREE表示目標文件目錄。
87 SRCTREE := $(CURDIR)
88 TOPDIR := $(SRCTREE)
89 LNDIR := $(OBJTREE)
//OBJTREE和LNDIR爲存放生成文件的目錄,TOPDIR與SRCTREE爲源碼頂層目錄,都設置成了當前目錄。
90 export TOPDIR SRCTREE OBJTREE
//用export 增加了TOPDIR SRCTREE OBJTREE這三個環境變量,供下層的Makefile用
91
92 MKCONFIG := $(SRCTREE)/mkconfig
93 export MKCONFIG
//定義變量MKCONFIG,$(SRCTREE)爲源碼頂層目錄,所以MKCONFIG爲源碼頂層目錄的mkconfig,這個在配置開發板的時候有用到,上篇分析make smdk6410_config的時候,有詳細分析過這個文件。
94
95 ifneq ($(OBJTREE),$(SRCTREE))
96 REMOTE_BUILD := 1
97 export REMOTE_BUILD
98 endif
//如果輸出目錄與源碼目錄不等,則設定REMOTE_BUILD項,並導出。

103 ifneq ($(OBJTREE),$(SRCTREE))
104 obj := $(OBJTREE)/
105 src := $(SRCTREE)/
106 else
107 obj :=
108 src :=
109 endif
110 export obj src
//如果輸出目錄OBJTREE與源碼頂層目錄SRCTREE不等的話,對obj和src進行賦值,否則則obj,src爲空。
//obj src會在主目錄中的config.mk定義,但在主makefile包含config.mk之前也需要,譬如unconfig, clean, clobber, distclean
//這邊OBJTREE和SRCTREE相等,所以obj src都爲空。

114 ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
115
116 # load ARCH, BOARD, and CPU configuration
117 include $(OBJTREE)/include/config.mk
118 export ARCH CPU BOARD VENDOR SOC
119
120 ifndef CROSS_COMPILE
121 ifeq ($(HOSTARCH),ppc)
122 CROSS_COMPILE =
...
...
155 ifeq ($(ARCH),avr32)
156 CROSS_COMPILE = avr32-
157 endif
158 endif
159 endif
//114行判斷config.mk是否存在,config.mk是在執行make smdk6410_config的時候生成的,裏面設置了ARCH CPU BOARD VENDOR SOC這幾個變量。
//117行include/config.mk中的東西都包含在這個Makefile中了
//118行用export 增加了ARCH CPU BOARD VENDOR SOC這五個環境變量,可以供下層的Makefile用
//120-159行根據體系結構設置CROSS_COMPILE變量,即編譯的時候使用的編譯器。
160
161 CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-
162 export CROSS_COMPILE
//直接修改CROSS_COMPILE變量,設置自己的編譯器,這邊加這兩句的話,120-159的設置就沒用了,以後我們要修改編譯器的時候,也可以直接在這邊改就行了。

165 include $(TOPDIR)/config.mk
//包含頂層目錄下的config.mk文件,這個下面會用到,下面會說明。

170 OBJS = cpu/$(CPU)/start.o
171 ifeq ($(CPU),i386)
172 OBJS += cpu/$(CPU)/start16.o
173 OBJS += cpu/$(CPU)/reset.o
174 endif
...
...
187 endif
//170行開始就是設置需要的目標文件了,170行表示需要目標文件start.o
//由於我們CPU爲s3c64xx,在前面包含的config.mk中設置的,所以171-187都沒用到。

189 OBJS := $(addprefix $(obj),$(OBJS))
//addprefix是Makefile中的一個加前綴函數函數,將$(obj)加到$(OBJS),不過這邊$(obj)爲空,107行設置的

191 LIBS = lib_generic/libgeneric.a
...
...
215 LIBS += $(BOARDLIBS)
//191-215行設置鏈接時需要這些文件,這些文件涉及到的目錄有lib_generic,board,cpu,lib_arm,fs,net,disk,rtc,dtt,drivers,post,common

217 LIBS := $(addprefix $(obj),$(LIBS))
218 .PHONY : $(LIBS)
//217行和189行的意思一樣的,218行用到.PHONY定義了一個僞目標,僞目標挺簡單的,可以自己去查下。

221 PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
//加入GCC的庫,CC和CFLAGS都是make的隱含變量,CC表示C編譯器,CFLAGS表示執行CC時的命令行參數

225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
229 .PHONY : $(SUBDIRS)
//tools、examples等這個目錄可以單獨編譯一些工具,所以把這些目錄都包含在SUBDIRS這個變量中。
230
231 ifeq ($(CONFIG_NAND_U_BOOT),y)
232 NAND_SPL = nand_spl
233 U_BOOT_NAND = $(obj)u-boot-nand.bin
234 endif
//支持nandflash啓動,我們之前執行make smdk6410_config進行配置,這樣的配置默認狀態是不支持nandflash啓動的,以後要支持nandflash啓動的話要進行修改,具體改的時候再分析把。
235 
236 __OBJS := $(subst $(obj),,$(OBJS))
237 __LIBS := $(subst $(obj),,$(LIBS))
//subst是一個替換函數,這個函數有三個參數,第一個參數是被替換字串,第二個參數是替換字串,第三個參數是替換操作的字串,中間兩個逗號中間什麼也沒有,所以這句的意思就是直接去掉$(LIBS)變量中的$(obj)。

242 ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
243
244 all: $(ALL)
//我們在編譯的時候用的是make all,所以會找到244行的all,$(ALL)爲all的依賴,在242行有定義,因爲$(obj)爲空,$(U_BOOT_NAND)默認狀態未定義,所以ALL=u-boot.srec u-boot.bin System.map,即all的依賴爲u-boot.srec u-boot.bin System.map,那麼就會先去檢測u-boot.srec u-boot.bin System.map這三個文件是否需要重新生成,而這個三個文件取決於其各自的依賴是否發生改變。

u-boot.srec是Motorola的S-Record格式的U-Boot映像,該映像來自於ELF格式的U-boot映像u-boot文件。通過以下命令生成:
249 $(obj)u-boot.srec: $(obj)u-boot
250 $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
OBJCOPY和OBJCFLAGS在頂層config.mk中定義,因此,實際上是調用objcopy命令生成的,arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec。objcopy將進行映像格式的轉化,其中-O srec說明輸出映像的格式爲serc的格式。

u-boot.bin表示原始二進制格式的鏡像文件,是我們最後需要下載到板子上的二進制文件,該映像也是通過objcopy命令對ELF映像u-boot轉化而成,只是轉化的參數不同而已,在252行可以找到。
252 $(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(OBJDUMP) -d $< > $<.dis
//-O binary即讓其生成二進制格式的映像。

System.map是索引文件,是uboot的符號表,即該文件中給出內存地址和相應的符號的映射關係,通過System.map文件可以知道函數,全局變量和靜態變量的地址。當然有地址也可以查出對應的符號。和Linux內核中System.map生產一樣,System.map是通過nm工具從目標文件u-boot中提取對應的符號表:
320 $(obj)System.map: $(obj)u-boot
321 @$(NM) $< | \
322 grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
323 sort > $(obj)System.map
而實際上調用nm工具從u-boot中提取符號表,然後調用grep打印相匹配的行,而sort則是對這些符號排序後輸出到System.map中。

當然,其中這些映像和符號表都取決於ELF映像的u-boot,這是這節的重點。u-boot映像取決於以下語句:
266 $(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
267 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
268 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
269 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
270 -Map u-boot.map -o u-boot
即u-boot依賴於depend,version,$(SUBDIRS),$(OBJS),$(LIBS),$(LDSCRIPT)

其中depend定義了文件的依賴關係,而這些依賴關係最終將被包含進同級的Makefile中,以便編譯的時候使用,比如當執行make omap1510inn_config 進行配置後,執行Make 命令將首先生成對應的.depend 文件,比如對於tools目錄下的.depend文件如下:
environment.o: environment.c ../include/config.h \ 
../include/configs/omap1510inn.h ../include/cmd_confdefs.h \ 
../include/configs/omap1510.h ../include/asm/arch/sizes.h \ 
../include/environment.h 
通過這種方式,把相應的配置結果給用起來了,即把相應的config.h使用起來了。

version主要在設置U-boot的版本
287 version:
288 @echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
289 echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
290 echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
291 $(TOPDIR)) >> $(VERSION_FILE); \
292 echo "\"" >> $(VERSION_FILE)

而宏SUBDIRS的命令如下:
225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
278 $(SUBDIRS):
279 $(MAKE) -C $@ all
即進入到各個列舉出來的子目錄比如tools,example目錄,並執行make all操作。

$(OBJS)的定義如下: 
170 OBJS = cpu/$(CPU)/start.o 
        而CPU 的類型取決於配置的結果,前一節“U-Boot的配置”中提到,最終把ARCH, CPU, BOARD 的配置寫入了config.mk文件,比如對於smdk6410而言,ARM爲s3c64xx,因此OBJS =cpu/s3c64xx/start.o這也是整個u-boot映像的第一個文件。


而$(LIBS)將會生成LIBS 定義的所有文件,具體可參看U-Boot頂層目錄的Makefile文件191行,此處不再贅述。

 
$(LDSCRIPT)的定義在頂層目錄下的config.mk中,如下: 
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds 
        因此根據config.mk 的定義,將包含具體目標板所在目錄的u-boot.lds鏈接腳本文件


//268行的LDFLAGS確定了連接方式,LDFLAGS在頂層config.mk中定義,165行有將這個文件包含進來。
//在config.mk中的189行有
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
在board/smdk6410/config.mk中,定義了“TEXT_BASE=0x33F80000”。所以,最終結果如下:LDFLAGS中有-T board/smdk6410/U-boot.lds -Ttext 0x33F80000的字樣,這些字樣指定了程序的佈局、地址。

board/smdk6410/U-boot.lds文件如下
24 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
25 /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
26 OUTPUT_ARCH(arm)
27 ENTRY(_start)
28 SECTIONS
29 {
//指定可執行image文件的全局入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成
30 . = 0x00000000;
//從0x0位置開始
31 
//代碼段
32 . = ALIGN(4);
//代碼以4字節對齊
33 .text :
34 {
35 cpu/s3c64xx/start.o (.text)
//代碼段的第一個代碼部分爲start.o
//按順序依次存儲下面的部門
36 cpu/s3c64xx/s3c6410/cpu_init.o (.text)
37 cpu/s3c64xx/onenand_cp.o (.text)
38 cpu/s3c64xx/nand_cp.o (.text)
39 cpu/s3c64xx/movi.o (.text)
40 *(.text)
41 lib_arm/div0.o
42 }
43
//const只讀常量數據段
44 . = ALIGN(4);
45 .rodata : { *(.rodata) }
46
//static/global 常量段
47 . = ALIGN(4);
48 .data : { *(.data) }
49
//指定got段, got段式是uboot自定義的一個段, 非標準段
50 . = ALIGN(4);
51 .got : { *(.got) }
52
//命令定義段
//把__u_boot_cmd_start賦值爲當前位置, 即起始位置
53 __u_boot_cmd_start = .;
//指定u_boot_cmd段, uboot把所有的uboot命令放在該段
54 .u_boot_cmd : { *(.u_boot_cmd) }
//指定u_boot_cmd段, uboot把所有的uboot命令放在該段
55 __u_boot_cmd_end = .;
56
//mmudata段
57 . = ALIGN(4);
58 .mmudata : { *(.mmudata) }
59
//堆棧段/內部臨時變量段-bbs
60 . = ALIGN(4);
//把__bss_start賦值爲當前位置,即bss段的開始位置
61 __bss_start = .;
//指定bss段
62 .bss : { *(.bss) }
//把_end賦值爲當前位置,即bss段的結束位置
63 _end = .;
64 }
從35行可知,cpu/s3c64xx/start.o被放在程序的最前面,所以U-boot的入口點在cpu/s3c64xx/start.S中。
現在總結下U-boot的編譯流程:
1、首先編譯cpu/$(CPU)/start.S,對於不同的CPU還可能編譯cpu/$(CPU)下的其他文件。
2、然後,對於平臺/開發板相關的每個目錄,每個通用目錄都使用他們各自的Makefile生成相應的庫。
3、將1、2步驟生產的.o、.a文件按照board/$(BOARD)/config.mk文件中指定的代碼段起始地址、board/$(BOARD)/U-boot.lds連接腳本進行連接。
4、第三步得到的是ELF格式的U-boot,後面Makefile還會將它轉化爲二進制格式、S-Record格式。

//326行else對應114行的ifeq,因爲條件滿足,所以這邊else後面的語句就不執行了。
#########################################################################
326 else
all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \
$(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \
$(SUBDIRS) version gdbtools updater env depend \
dep tags ctags etags $(obj)System.map:
@echo "System not configured - see README" >&2
@ exit 1
endif

.PHONY : CHANGELOG
CHANGELOG:
git log --no-merges U-Boot-1_1_5.. | \
unexpand -a | sed -e 's/\s\s*$$//' > $@

參考:http://kmoving.blog.163.com/blog/static/205049197201241503125388/  謝謝作者!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章