TQ2440之uboot---1.u-boot Makefile分析
當(dāng)我們編譯u-boot的時(shí)候,大家鍵入make smdk2410_config,make的時(shí)候都作了那些動(dòng)作呢,這里我先大概介紹一下Makefile的內(nèi)容,然后在大概理解一下命令執(zhí)行的流程。如果有錯(cuò)的地方,希望大家指正,謝謝。
1.u-boot頂層目錄的Makefile分析:
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/)
首先執(zhí)行uname -m查看機(jī)器硬件名,得到i686,然后sed將i686替換成i386,最后HOSTARCH=i386
39 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' |
40 sed -e 's/(cygwin).*/cygwin/')
首先執(zhí)行uname -s查看開發(fā)平臺(tái)的內(nèi)核,結(jié)果為Linux,然后tr將大寫符串轉(zhuǎn)化為小寫字符linux ,最后的結(jié)果是HOSTOS=linux
export HOSTARCH HOSTOS
export是Makefile的語法關(guān)鍵詞,將這些變量傳遞給下一層的Makefile.總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會(huì)覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數(shù)。
如果你要傳遞變量到下級Makefile中,那么你可以使用這樣的聲明:
export ;
如果你不想讓某些變量傳遞到下級Makefile中,那么你可以這樣聲明:
unexport ;
69 ifdef O
70 ifeq ("$(origin O)", "command line")
71 BUILD_DIR := $(O)
72 endif
73 endif
上面意思是:如果在make命令中有O選項(xiàng)內(nèi)把BUILD_DIR設(shè)為O的指定的值
這里主要說明origin的語法:
origin函數(shù)不像其它的函數(shù),他并不操作變量的值,他只是告訴你你的這個(gè)變量是哪里來的?其語法是:$(origin ;)
注意,;是變量的名字,不應(yīng)該是引用。所以你最好不要在;中使用“$”字符。Origin函數(shù)會(huì)以其返回值來告訴你這個(gè)變量的“出生情況”,下面,是origin函數(shù)的返回值:
“undefined”:如果;從來沒有定義過,origin函數(shù)返回這個(gè)值“undefined”。
“default”: 如果;是一個(gè)默認(rèn)的定義
“environment” :如果;是一個(gè)環(huán)境變量,并且當(dāng)Makefile被執(zhí)行時(shí),“-e”參數(shù)沒有被打開。
“file”: 如果;這個(gè)變量被定義在Makefile中。
“command line” : 如果;這個(gè)變量是被命令行定義的。
“override” : 如果;是被override指示符重新定義的。
“automatic” : 如果;是一個(gè)命令運(yùn)行中的自動(dòng)化變量。
75 ifneq ($(BUILD_DIR),)
76 saved-output := $(BUILD_DIR)
ifneq如果BUILD_DIR不為空,則把saved-output設(shè)為BUILD_DIR
79 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
-d檢查BUILD_DIR文件夾是否存在,不存在則創(chuàng)建
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),)
確認(rèn)BUILD_DIR是否己經(jīng)創(chuàng)建.假設(shè)BUILD_DIR不存在,cd $(BUILD_DIR)為假不執(zhí)行后面/bin/pwd,$(shell cd $(BUILD_DIR) && /bin/pwd)返回值為空, 下面的if為假則會(huì)打印outpud directory … does not exist, 同時(shí)make退出。
這里主要說明if的語法:
$(if ,,)
如果為真(非空字符串) ,那個(gè)會(huì)是整個(gè)函數(shù)的返回值,如果為假(空字符串) ,那么會(huì)是整個(gè)函數(shù)的返回值,此時(shí)如果沒有被定義,那么,整個(gè)函數(shù)返回空字串。
86 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
87 SRCTREE := $(CURDIR)
88 TOPDIR := $(SRCTREE)
89 LNDIR := $(OBJTREE)
90 export TOPDIR SRCTREE OBJTREE
91
92 MKCONFIG := $(SRCTREE)/mkconfig
93 export MKCONFIG
CURDIR是makefile中的一個(gè)關(guān)鍵字,指明當(dāng)前路徑。
最后TOPDIR SRCTREE OBJTREE這三個(gè)變量一樣,都是u-boot源碼目錄的根目錄路徑。然后設(shè)置MKCONFIG腳本的路徑。
95 ifneq ($(OBJTREE),$(SRCTREE))
96 REMOTE_BUILD := 1
97 export REMOTE_BUILD
98 endif
如果當(dāng)前路徑和生成OBJ目錄路徑不一樣,則會(huì)設(shè)置REMOTE_BUILD為1
103 ifneq ($(OBJTREE),$(SRCTREE))
104 obj := $(OBJTREE)/
105 src := $(SRCTREE)/
106 else
107 obj :=
108 src :=
109 endif
110 export obj src
如果當(dāng)前路徑和生成OBJ目錄路徑不一樣,則設(shè)置src與obj變量的值,代表了最終src與obj的路徑
114 ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
//通過wildcard文件名函數(shù)判斷是否有include/config.mk文件,也就是執(zhí)行make smdk2410_config以后產(chǎn)生的文件.
$(wildcard pattern)
參數(shù)pattern是一個(gè)文件名格式,包含有通配符。函數(shù)wildcard的結(jié)果是一列和格式匹配且真實(shí)存在的文件的名稱,文件名之間用一個(gè)空格隔開。
比如當(dāng)前目錄下有文件1.c,2.c,1.h,2.h則
c_src := $(wildcard *.c)
結(jié)果為:1.c 2.c
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk//包含這個(gè)文件.這里obj為空
export ARCH CPU BOARD VENDOR SOC//將include/config.mk里的變量申明給其他的Makefile使用.
# load other configuration
include $(TOPDIR)/config.mk//然后包含根目錄的config.mk文件.
這些config.mk將在以后介紹
ifndef CROSS_COMPILE//確實(shí)沒有定義CROSS_COMPILE變量
ifeq ($(HOSTARCH),ppc)//HOSTARCH為i386,CROSS_COMPILE所以不為空
CROSS_COMPILE =
else
ifeq ($(ARCH),ppc)
CROSS_COMPILE = powerpc-linux-
endif
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
endif
......
首先沒有定義CROSS_COMPILE,然后我們的HOSTARCH=i386,然后在判斷ARCH,由于在前面已經(jīng)指定ARCH=arm.所以CROSS_COMPILE=arm-linux-.通過這個(gè)可以選擇不同平臺(tái)下的交叉編譯器.
include $(TOPDIR)/config.mk//包含根目錄下的config.mk文件,這個(gè)文件以后會(huì)分析到。
OBJS = cpu/$(CPU)/start.o
ifeq ($(CPU),i386)
OBJS = cpu/$(CPU)/start16.o
OBJS = cpu/$(CPU)/reset.o
endif ........
OBJS := $(addprefix $(obj),$(OBJS)) //將OBJS賦值給OBJ
$(addprefix src/,foo bar)
結(jié)果:src/foo src/bar
由于start.S是我們啟動(dòng)代碼,所以首先編譯.OBJ=cpu/arm920t/start.o
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
........
.PHONY : $(LIBS)
添加相應(yīng)的靜態(tài)庫.
__OBJS := $(subst $(obj),,$(OBJS)
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
(1) $(subst from,to,text).
在文本“text”中使用to替換每一處的from。
比如:
$(subst ee,EE,feet on the street)
結(jié)果為:fEET on the strEET
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) //這個(gè)是最后要生成的文件。$(U_BOOT_NAND) $(U_BOOT_ONENAND)要添加相應(yīng)的宏定義即可。
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@將u-bootELF格式文件生成16進(jìn)制格式的文件
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@將u-bootELF格式文件生成另一種S-Record格式的文件
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
//刪除以前的配置文件
以上是一些Makefile的大概信息,這里就說到這里。感興趣的可以再深入了解。
//當(dāng)我們執(zhí)行make smdk2410_config的時(shí)候,要作的事情如下:
Makefile文件里面可以看出支持好多種體系結(jié)構(gòu),并有相應(yīng)開發(fā)板的配置信息。這里主要研究的是ARM,開發(fā)板是smdk2410.
當(dāng)我們執(zhí)行:make smdk2410_config的時(shí)候,首先執(zhí)行:
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
首先執(zhí)行unconfig這個(gè)標(biāo)簽,主要是刪除以前的配置信息。
然后執(zhí)行$(MKCONFIG),也就是mkconfig腳本,并傳遞6個(gè)參數(shù)。
$(@:_config=)他的作用就是將smdk2410_config中的_config設(shè)置為空,結(jié)果為smdk2410.
這個(gè)命令也就是:./mkconfigsmdk2410 arm arm920t smdk2410 NULL s3c24x0.
$0 $1 $2 $3 $4 $5 $6
接下來看看mkconfig的源代碼:
1.確定開發(fā)板的名稱
11 APPEND=no # Default: Create new config file
12 BOARD_NAME="" # Name to print in make output
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
由于參數(shù)里沒有-- -a -n等參數(shù),所以這個(gè)while沒有執(zhí)行。然后APPEND BOARD_NAME沒有改變。
23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1"
這個(gè)時(shí)候BOARD_NAME的值就等于"smdk2410".
25 [ $# -lt 4 ] && exit 1//參數(shù)的個(gè)數(shù)小于4退出
26 [ $# -gt 6 ] && exit 1//參數(shù)的個(gè)數(shù)大于6退出
2.創(chuàng)建開發(fā)板相關(guān)的頭文件的連接
//判斷源代碼目錄和目標(biāo)文件目錄是否一樣,由于直接我們都是在源代碼目錄編譯,所以將執(zhí)行else分之的代碼。
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
進(jìn)入include目錄,刪除asm文件(這是上一次的配置時(shí)建立的連接文件),然后再次建立asm文件,并令它連接向asm-$2目錄,也就是asm-arm目錄。
rm -f asm-$2/arch //刪除asm-$2即asm-arm目錄
if [ -z "$6" -o "$6" = "NULL" ] ; then //-z表示:[ -zSTRING ] “STRING”的長度為零則為真。
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
//對于$6就是s3c24x0,不為空,也不是NULL,所以將執(zhí)行else分之。LNPREFIX為空,所以連接的命令就是ln -s arch-$6 asm-$2/arch,也就是ln -s arch-s3c24x0 asm-arm/arch
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi
重新建立asm-arm/proc文件,并讓它連接向proc-armv目錄。
3.創(chuàng)建頂層Makefile包含的文件include/config.mk
echo "ARCH = $2" > config.mk //“>”,“>>”如果有config.mk文件,并將ARCH輸入到config.mk文件里。如果沒有首先創(chuàng)建然后將ARCH輸入。
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
//將ARCH,CPU,BOARD變量重定向到include/config.mk文件里
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
//將VENDOR,SOC變量重定向到include/config.mk文件里
這樣include/config.mk文件里的內(nèi)容如下:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
#
# Create board specific header file
#
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file //創(chuàng)建include/config.h文件
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include " >>config.h //將#include
exit 0
這樣include/config.h里的內(nèi)容如下:
/* Automatically generated - do not edit */
#include
3.u-boot的編譯和連接過程
首先在Makefile里包含了include/config.mk和根目錄的config.mk兩個(gè)文件。第一個(gè)主要是那6個(gè)參數(shù)。第二個(gè)config.mk文件的內(nèi)容如下:
BOARDDIR = $(BOARD)
endif
ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif //包含board/smdk2410/config.mk,里面主要定義了TEXT_BASE=0x33f80000
........
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
。。。。
LDFLAGS = -Bstatic -T $(LDSCRIPT) $(PLATFORM_LDFLAGS)//加入連接文件為以后使用。LDFLAGS有“-T board/smdk2410/u-boot.lds -Ttext 0x33f80000”字樣。首先我們的u-boot.lds告訴我們的代碼的分布狀況,而-Ttext 0x33f80000告訴我們text段放在0x33f80000.待會(huì)會(huì)講到u-boot.lds的內(nèi)容。對于OBJS,LIBS的每個(gè)成員,都將進(jìn)入相應(yīng)的子目錄執(zhí)行make命令。當(dāng)所有的OBJS,LIBS所表示的.o,.a文件生成后,就剩下最后的連接了,這對應(yīng)Makefile的如下幾行:
$(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: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) |
sed -n -e 's/.*($(SYM_PREFIX)__u_boot_cmd_.*)/-u1/p'|sort|uniq`;
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS)
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS)
-Map u-boot.map -o u-boot
首先使用下面的語句連接得到ELF格式的u-boot.最后轉(zhuǎn)化為二進(jìn)制格式的u-boot.bin,S-Record格式的u-boot.srec。LDFLAGS確定了連接的方式,其中“-T board/smdk2410/u-boot.lds -Ttext 0x33f80000”字樣指定了程序的布局和地址。u-boot.lds的文件如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
/*指定輸出可執(zhí)行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm)
/*指定輸出可執(zhí)行文件的平臺(tái)為ARM*/
ENTRY(_start)
/*指定輸出可執(zhí)行文件的起始代碼段為_start*/
(.globl _start _start: b start_code//cpu/arm920t/start.S)
SECTIONS
{
/*指定可執(zhí)行image文件的全局入口點(diǎn),通常這個(gè)地址都放在ROM(flash)0x0位置。必須使編譯器知道這個(gè)地址,通常都是修改此處來完成*/
. = 0x00000000; /*;從0x0位置開始*/
. = ALIGN(4);/*代碼以4字節(jié)對齊*/
.text :
{
cpu/arm920t/start.o (.text) /*代碼的第一個(gè)代碼部分*/
*(.text) /*其它代碼部分*/
}
. = ALIGN(4);
.rodata : { *(.rodata) } /*指定只讀數(shù)據(jù)段*/
. = ALIGN(4);
.data : { *(.data) }/*指定讀/寫數(shù)據(jù)段*/
. = ALIGN(4);
.got : { *(.got) } /*指定got段, got段是uboot自定義的一個(gè)段,非標(biāo)準(zhǔn)段*/
. = .;
/*把__u_boot_cmd_start賦值為當(dāng)前位置,即起始位置*/
__u_boot_cmd_start = .;
/*指定u_boot_cmd段, uboot把所有的uboot命令放在該段.*/
.u_boot_cmd : { *(.u_boot_cmd) }
/*把__u_boot_cmd_end賦值為當(dāng)前位置,即結(jié)束位置*/
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .; /*把__bss_start賦值為當(dāng)前位置,即bss段的開始位置*/
.bss (NOLOAD) : { *(.bss) } /*指定bss段,告訴加載器不要加載這個(gè)段*/
_end = .; /*把_end賦值為當(dāng)前位置,即bss段的結(jié)束位置*/
}
這樣代碼的都是以0x33f80000 0x0為基準(zhǔn)開始,如果你從nandflash啟動(dòng),測試前4K的代碼的地址都是在0x0,那么4K的代碼的實(shí)現(xiàn)可以通過位置無關(guān)指令b來實(shí)現(xiàn)。b指令的程序不依賴代碼存儲(chǔ)的位置-即不管這條代碼放在什么位置,B指令都可以跳轉(zhuǎn)到正確的位置。
bootloader,內(nèi)核等程序剛開始運(yùn)行時(shí)。他們所處的地址通常不等于運(yùn)行地址