u-boot(Makefile)


當我們編譯U-BOOT的時候,大家鍵入make smdk2410_config,make 的時候都作了那些動作呢,這里我先大概介紹一下Makefile的內容,然后在大概理解一下命令執行的流程。如果有錯的地方,希望大家指正,謝謝。

1.u-boot頂層目錄的Makefile分析:

HOSTARCH := $(shell uname -m | \
        sed -e s/i.86/i386/ \
            -e s/sun4u/sparc64/ \
            -e s/arm.*/arm/ \
            -e s/sa110/arm/ \
            -e s/powerpc/ppc/ \
            -e s/macppc/ppc/)
首先執行uname -m得到I686,通過管道傳送給sed命令,然后sed命令將執行sed -e s/i.86/i386/,將I686替換成i386,最后的結果是HOSTARCH=i386.

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
            sed -e 's/\(cygwin\).*/cygwin/')

首先執行uname -s 查看開發平台的系統,結果為Linux,然后通過管道傳送給tr命令,tr命令利用字符類[:lower:]和[:upper:]將LInux字符串轉化為linux,然后再利用sed命令.最后的結果是HOSTOS=linux

export HOSTARCH HOSTOS

export 是Makefile的語法關鍵詞,將這些變量傳遞給下一層的Makefile.總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數。
如果你要傳遞變量到下級Makefile中,那么你可以使用這樣的聲明:
     export <variable ...>;
如果你不想讓某些變量傳遞到下級Makefile中,那么你可以這樣聲明:
     unexport <variable ...>;

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)
export TOPDIR

得到U-BOOT的絕對路徑為TOPDIR.

ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
   else
   XECHO = :
endif
通過findstring函數來找MAKEFLAGS是否有匹配s的關鍵詞,如果沒有則ifeq就為真。那么變量XECHO就等於echo 反之亦然。

ifdef O
ifeq ("$(origin O)", "command line")
   BUILD_DIR := $(O)
   endif
   endif
這里主要說明origin的語法:

origin函數不像其它的函數,他並不操作變量的值,他只是告訴你你的這個變量是哪里來的?其語法是:
     $(origin <variable>;)
注意,<variable>;是變量的名字,不應該是引用。所以你最好不要在<variable>;中使用“$”字符。Origin函數會以其返回值來告訴你這個變量的“出生情況”,下面,是origin函數的返回值:

“undefined”
       如果<variable>;從來沒有定義過,origin函數返回這個值“undefined”。
“default”
       如果<variable>;是一個默認的定義
“environment”
       如果<variable>;是一個環境變量,並且當Makefile被執行時,“-e”參數沒有被打開。
“file”
       如果<variable>;這個變量被定義在Makefile中。
“command line”
       如果<variable>;這個變量是被命令行定義的。
“override”
       如果<variable>;是被override指示符重新定義的。
“automatic”
       如果<variable>;是一個命令運行中的自動化變量。

$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) //判斷當前是否有個{BUILD_DIR}目錄,如果沒有執行mkdir -p ${BUILD_DIR},創建{BUILD_DIR}目錄,這個變量為空。

# ifneq ($(BUILD_DIR),)
OBJTREE     := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
   SRCTREE     := $(CURDIR)
   TOPDIR      := $(SRCTREE)
LNDIR       := $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG
//最后 TOPDIR SRCTREE OBJTREE這三個變量一樣,都是u-boot源碼目錄的根目錄路徑。然后設置MKCONFIG變量,代表一個腳本,這個腳本以后用。
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src
//由以上可知obj,src都為空

ifeq (include/config.mk,$(wildcard include/config.mk)) //通過wildcard文件名函數判斷是否有include/config.mk文件,也就是執行make smdk2410_config以后產生的文件.

$(wildcard pattern)
參數pattern是一個文件名格式,包含有通配符。函數wildcard的結果是一列和格式匹配且真實存在的文件的名稱,文件名之間用一個空格隔開。
比如當前目錄下有文件1.c,2.c,1.h,2.h 則
c_src := $(wildcard *.c)
結果為:1.c 2.c

# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk //包含這個文件.這里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   //確實沒有定義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,由於在前面已經指定ARCH=arm.所以CROSS_COMPILE=arm-linux-.通過這個可以選擇不同平台下的交叉編譯器.

include $(TOPDIR)/config.mk //包含根目錄下的config.mk文件,這個文件以后會分析到。

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)
結果:src/foo src/bar

由於start.S是我們啟動代碼,所以首先編譯.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)

添加相應的靜態庫.

__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)
結果為:fEET on the strEET
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) //這個是最后要生成的文件。$(U_BOOT_NAND) $(U_BOOT_ONENAND) 要添加相應的宏定義即可。
$(obj)u-boot.hex:   $(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ 將u-boot ELF格式文件生成16進制格式的文件
$(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的大概信息,這里就說到這里。感興趣的可以再深入了解。
//當我們執行make smdk2410_config的時候,要作的事情如下:
Makefile文件里面可以看出支持好多種體系結構,並有相應開發板的配置信息。這里主要研究的是ARM,開發板是smdk2410.
當我們執行:make smdk2410_config的時候,首先執行:
smdk2410_config :   unconfig
    @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
可以看出。現執行unconfig這個標簽,以上可以看出主要是刪除以前的配置信息。
然后執行$(MKCONFIG),也就是mkconfig腳本,並傳遞6個參數。
$(@:_config=)他的作用就是將smdk2410_config中的_config設置為空,結果為smdk2410.
這個命令也就是:./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0.
接下來看看mkconfig的源代碼:
1.確定開發板的名稱
APPEND=no   # Default: Create new config file
BOARD_NAME=""   # Name to print in make output
while [ $# -gt 0 ] ; do
    case "$1" in
    --) shift ; break ;;
    -a) shift ; APPEND=yes ;;
    -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
    *) break ;;
    esac
done
由於參數里沒有-- -a -n等參數,所以這個while沒有執行。然后APPEND BOARD_NAME沒有改變。
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" //這個時候BOARD_NAME的值就等於"smdk2410".
[ $# -lt 4 ] && exit 1 //參數的個數小於4退出
[ $# -gt 6 ] && exit 1//參數的個數大於6退出
2.創建開發板相關的頭文件的連接
//判斷源代碼目錄和目標文件目錄是否一樣,由於直接我們都是在源代碼目錄編譯,所以將執行else分之的代碼。
if [ "$SRCTREE" != "$OBJTREE" ] ; then
    mkdir -p ${OBJTREE}/include
    mkdir -p ${OBJTREE}/include2
    cd ${OBJTREE}/include2
    rm -f asm
    ln -s ${SRCTREE}/include/asm-$2 asm
    LNPREFIX="../../include2/asm/"
    cd ../include
    rm -rf asm-$2
    rm -f asm
    mkdir asm-$2
    ln -s asm-$2 asm
else
    cd ./include
    rm -f asm
    ln -s asm-$2 asm
fi
//進入include目錄,刪除asm文件(這是上一次的配置時建立的連接文件),然后再次建立asm文件,並令它連接向asm-$2目錄,也就是asm-arm目錄。
rm -f asm-$2/arch //刪除asm-$2即asm-arm目錄
if [ -z "$6" -o "$6" = "NULL" ] ; then //-z表示:[ -z STRING ] “STRING” 的長度為零則為真。
    ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
    ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
//對於$6就是s3c24x0,不為空,也不是NULL,所以將執行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.創建頂層Makefile包含的文件include/config.mk
echo "ARCH   = $2" > config.mk //“>”,“>>”如果有config.mk文件,並將ARCH輸入到config.mk文件里。如果沒有首先創建然后將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文件里的內容如下:
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 //創建include/config.h文件
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h //將#include <configs/$1.h重定向到include/config.h文件里。
exit 0
這樣include/config.h里的內容如下:
/* Automatically generated - do not edit */
#include <configs/smdk2410.h>
3.u-boot的編譯和連接過程
首先在Makefile里包含了include/config.mk和根目錄的config.mk兩個文件。第一個主要是那6個參數。第二個config.mk文件的內容如下:
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.待會會講到u-boot.lds的內容。對於OBJS,LIBS的每個成員,都將進入相應的子目錄執行make命令。當所有的OBJS,LIBS所表示的.o,.a文件生成后,就剩下最后的連接了,這對應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_.*\)/-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
首先使用下面的語句連接得到ELF格式的u-boot.最后轉化為二進制格式的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")*/
/*指定輸出可執行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm)
/*指定輸出可執行文件的平台為ARM*/
ENTRY(_start)
/*指定輸出可執行文件的起始代碼段為_start*/
(.globl _start _start: b       start_code//cpu/arm920t/start.S)
SECTIONS
{
/*指定可執行image文件的全局入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成*/
    . = 0x00000000; /*;從0x0位置開始*/
    . = ALIGN(4);/*代碼以4字節對齊*/
    .text      :
    {
      cpu/arm920t/start.o   (.text) /*代碼的第一個代碼部分*/
      *(.text) /*其它代碼部分*/
    }
    . = ALIGN(4);
    .rodata : { *(.rodata) } /*指定只讀數據段*/
    . = ALIGN(4);
    .data : { *(.data) }/*指定讀/寫數據段*/
    . = ALIGN(4);
    .got : { *(.got) } /*指定got段, got段是uboot自定義的一個段, 非標准段*/
    . = .;
                                /*把__u_boot_cmd_start賦值為當前位置, 即起始位置*/
    __u_boot_cmd_start = .;
                                /*指定u_boot_cmd段, uboot把所有的uboot命令放在該段.*/
    .u_boot_cmd : { *(.u_boot_cmd) }
                                /*把__u_boot_cmd_end賦值為當前位置,即結束位置*/
    __u_boot_cmd_end = .;
    . = ALIGN(4);
    __bss_start = .;    /*把__bss_start賦值為當前位置,即bss段的開始位置*/
    .bss (NOLOAD) : { *(.bss) } /*指定bss段,告訴加載器不要加載這個段*/
    _end = .;                   /*把_end賦值為當前位置,即bss段的結束位置*/

這樣代碼的都是以0x33f80000+0x0為基准開始,如果你從nandflash啟動,測試前4K的代碼的地址都是在0x0,那么4K的代碼的實現可以通過位置無關指令b來實現。b指令的程序不依賴代碼存儲的位置-即不管這條代碼放在什么位置,B指令都可以跳轉到正確的位置。
bootloader,內核等程序剛開始運行時。他們所處的地址通常不等於運行地址,在程序的開頭,先使用b,bl.mov等位置無關的指令將代碼從flash等設備中復制到內存的運行地址處,然后跳轉到運行地址去執行。


免責聲明!

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



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