u-boot的配置、編譯及鏈接

来源:http://www.cnblogs.com/CrazyCatJack/archive/2016/11/12/6056564.html
-Advertisement-
Play Games

第一次寫技術博客,還有些興奮呢。我是CrazyCatJack,大家可以叫我CCJ或者瘋貓。我即將成為一名嵌入式Linux的驅動工程師,現在還是一枚大四狗,呼呼~大學期間做了一些項目和比賽,都是基於32位的MCU(例如STM32、Freescale K60),這些呢都是根據網上的視頻,PDF自學的。現 ...


  第一次寫技術博客,還有些興奮呢。我是CrazyCatJack,大家可以叫我CCJ或者瘋貓。我即將成為一名嵌入式Linux的驅動工程師,現在還是一枚大四狗,呼呼~大學期間做了一些項目和比賽,都是基於32位的MCU(例如STM32、Freescale K60),這些呢都是根據網上的視頻,PDF自學的。現在想更進一步,學習一下嵌入式Linux、UCOS-II等嵌入式系統。因為給板子加系統是一個必然趨勢,控制會越來越複雜,內容也越來越多的。有一個系統統籌管理是非常棒的選擇。好了,廢話少說,今天開始我的第一篇技術博客:u-boot的配置、編譯和鏈接^_^

1.u-boot的配置

  首先,我們要想瞭解u-boot,最好是從Makefile開始看,就能知道u-boot要執行的操作了。就像C語言中的main函數一樣。在Makefile文件里,和配置相關的語句如下:

 

OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE        := $(CURDIR)
TOPDIR        := $(SRCTREE)
LNDIR        := $(OBJTREE)
export    TOPDIR SRCTREE OBJTREE

MKCONFIG    := $(SRCTREE)/mkconfig
export MKCONFIG


CCJ_config    :    unconfig
    @$(MKCONFIG) $(@:_config=) arm arm920t CCJ NULL s3c24x0

  根據Makefile中ReadMe文件中的描述,我們要配置u-boot,就要執行"make <board name>_config"這條指令。所以從Makefile中要查找一下你所用的開發板型號的相關配置信息。一般廠家都會給你配置好u-boot,你也可以自己寫。在這裡就假設我們使用的開發板名字為“CCJ”。分析最後這條語句:

 

@$(MKCONFIG) $(@:_config=) arm arm920t CCJ NULL s3c24x0

  第一個MKCONFIG在Makefile文件中已有定義,可以看到MKCONFIG:=$(SRCTREE)/mkconfig,也就是說我們要用源代碼樹下的mkconfig替換這條語句中的MKCONFIG。

  第二個$(@的含義是用前面板子的名字替換掉後面的內容,而且不要“_config”,也就是說替換之後為CCJ。

  現在經過替換,這條語句變成了:

 

./mkconfig CCJ arm arm920t CCJ NULL s3c24x0

  後面的5個參數其實分別代表著你現在用到的硬體平臺的架構、CPU、開發板型號名稱、供應商、SOC名稱。這個我們後面還會有講解。所以能夠看出這裡我用到的是ARM架構,CPU為ARM920T,開發板名稱為CCJ,SOC為S3C24X0。

  那現在我們已經分析了這條配置語句所代表的含義,但它具體是怎麼工作的呢?是怎樣將我們寫好的這些硬體相關的信息進行具體配置賦值的呢?其實這裡我們調用了根目錄下的./mkconfig這一文件,就像C語言中的函數調用一樣,調用mkconfig這個文件,並傳入參數(arch,CPU,boardname,vendor,soc)為(arm arm920t CCJ NULL s3c24x0),來執行相應的操作。這麼說大家應該就理解了吧。所以接下來我們看mkconfig文件中是怎樣使用這些參數進行配置的。

  現在我們打開mkconfig文件,先分析第一段代碼:

 

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

[ "${BOARD_NAME}" ] || BOARD_NAME="$1"

[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1

echo "Configuring for ${BOARD_NAME} board..."

  首先我們看到兩個參數的賦值,APPEND為“no”,這個參數我們後面會用到,BOARD_NAME為“”,非空。這裡大家會看到很多“$1,$2,$3...”等等,大家不要暈,其實很簡單的,他們都分別對應我們剛剛所說傳入的參數:

 

Makefile命令:             ./mkconfig CCJ arm arm920t CCJ NULL s3c24x0
mkconfig對應符號表示:           $0    $1   $2   $3     $4  $5     $6

  所以這裡用到的$1其實就是"CCJ",$2就是"arm",以此類推。進入while語句,由於這句Makefile命令中沒有“--”,“-a”,“-n”。所以這個while其實什麼都沒乾。如果大家在寫Makefile命令的時候,用到了“--”,“-a”,“-n”,則這裡會改變APPEND和BOARD_NAME兩個參數的值。我們這裡沒有改變。

  接下來看下一句話 [ "${BOARD_NAME}" ] || BOARD_NAME="$1" 這句話是說如果BOARD_NAME不為空,則BOARD_NAME等於$1,即“CCJ”。然後是 echo "Configuring for ${BOARD_NAME} board..." 這句話的含義就是列印“Configuring for CCJ board”,所以在我們執行“make CCJ_config”命令時,一定會列印出這句話來。其中CCJ是你開發板的型號。

  

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

rm -f asm-$2/arch

  接著往下看,上面這段代碼主要完成鏈接工作。首先if判斷 $SRCTREE 是否等於 $OBJTREE,這裡要註意了,這兩個參數是我們在Makefile文件中定義的。看我發的第一個代碼片中就有對它們的賦值。

 

OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE        := $(CURDIR)

  第一條語句的意思是如果我們定義了BUILD_DIR,則OBJTREE等於 $BUILD_DIR,否則等於 $CURDIR。那麼這裡我沒有定義BUILD_DIR,所以OBJTREE= $CURDIR。所以現在OBJTREE=SRCTREE,則不執行if語句中的內容,而轉去執行else分支的內容。

  首先進入./include目錄下,刪除原來生成的asm文件,重新建立asm文件,並鏈接到asm-$2文件。經過前面的講解,相信你已經知道asm-$2經過轉換,得到asm-arm。也就是說現在asm->asm-arm。最後刪除asm-arm/arch文件。同理,為後面重新建立連接做準備。

 

if [ -z "$6" -o "$6" = "NULL" ] ; then
    ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
    ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi

if [ "$2" = "arm" ] ; then
    rm -f asm-$2/proc
    ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi

  接著往下分析,如果$6不為空,或$6等於NULL,則執行if內的語句,我們$6=s3c24x0。所以執行分支語句,建立文件asm-am/arch,並指向arch-sac24x0。

  第二個if中,因為$2等於arm,所以執行語句,刪除asm-arm/proc,建立文件asm-arm/proc,並指向proc-armv。

 

#
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk
echo "CPU    = $3" >> config.mk
echo "BOARD  = $4" >> config.mk

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk

#
# Create board specific header file
#
if [ "$APPEND" = "yes" ]    # Append to existing config file
then
    echo >> config.h
else
    > config.h        # Create new config file
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h

exit 0

  現在是不是有點累了?我們開始分析mkconfig的最後一大段^_^。堅持就是勝利!!!

  根據註釋,第一段代碼的作用是創建包含的文件,為配置做準備。這裡我們主要是創建config.mk文件。這裡說明一下">"表示創建某文件,">>"表示將內容添加到某文件。所以接下來幾行的含義就分別是:創建config.mk文件,並添加"ARCH  = arm",添加"CPU  = arm920t",添加"BOARD  = CCJ"語句。$5為NULL,不執行語句。$6為s3c24x0,添加"SOC  =s3c24x0"所以,如果你嘗試著執行config.mk文件一定會出現如下信息:

 

ARCH  = arm
CPU  = arm920t
BOARD  = CCJ
SOC = s3c24x0

  接下來看第二段代碼,根據作者提供的註釋我們知道要創建開發板指定頭文件了。根絕最開始mkconfig中我們定義的APPEND值,執行語句,由於這裡我定義的是APPEND=no,所以執行else分支,創建config.h頭文件,並添加  /* Automatically generated - do not edit */    #include <configs/$1.h>  語句到config.h頭文件中。 最後執行 exit 0 退出mkconfig文件。

 

2.u-boot的編譯

  接下來我們要做的就是編譯了,即make。我們回到Makefile文件,看與你所使用的硬體平臺相關的代碼,這裡我使用的是ARM9。根據註釋,可以得知這段代碼用於載入架構,開發板信息,CPU的配置。

 

# load ARCH, BOARD, and CPU configuration
include $(OBJTREE)/include/config.mk
export    ARCH CPU BOARD VENDOR SOC

ifndef CROSS_COMPILE
ifeq ($(HOSTARCH),ppc)
CROSS_COMPILE =
else
ifeq ($(ARCH),ppc)
CROSS_COMPILE = powerpc-linux-
endif
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
endif
ifeq ($(ARCH),i386)
ifeq ($(HOSTARCH),i386)
CROSS_COMPILE =
else
CROSS_COMPILE = i386-linux-
endif
endif

  在包含了我們之前所配置的config.mk文件後,Makefile代碼就會根據我們配置的具體架構信息選擇合適的交叉編譯,很明顯,因為我的是ARM架構,這裡CROSS_COMPILE=arm-linux-。

 

export    CROSS_COMPILE

# load other configuration
include $(TOPDIR)/config.mk

  接著往下看,最下麵我們包含了頂層目錄下的config.mk文件。其實這裡我發現有一個問題,就是之前我們不是也生成了一個config.mk嗎?這其實是兩個文件。之前那個完完全全是我們自己創建的config.mk,它的作用就是為了給接下來包含的這個原作者書寫的config.mk傳值,傳遞CPU、SOC、ARCH、BOARD、VENDOR的參數值。如果你手頭上有下載好的u-boot源文件,打開u-boot文件夾,你就會看到一個config.mk。打開它就明白我剛剛所說的,傳遞的參數進入這個config,mk配置交叉編譯選項、選擇結構依賴規則。如下圖:

 

ifeq ($(ARCH),arm)
ifeq ($(CROSS_COMPILE),powerpc-netbsd-)
PLATFORM_CPPFLAGS+= -D__ARM__
endif
ifeq ($(CROSS_COMPILE),powerpc-openbsd-)
PLATFORM_CPPFLAGS+= -D__ARM__
endif
endif

ifeq ($(ARCH),blackfin)
PLATFORM_CPPFLAGS+= -D__BLACKFIN__ -mno-underscore
endif

ifdef    ARCH
sinclude $(TOPDIR)/$(ARCH)_config.mk    # include architecture dependend rules
endif
ifdef    CPU
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk    # include  CPU    specific rules
endif
ifdef    SOC
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk    # include  SoC    specific rules
endif

  在這個文件中,也有調用結構依賴規則,就是你所用的硬體架構對應的結構規則config文件,這裡不再詳述。可以自己打開看看。

 

  

  

  接下來接著看Makefile的命令:

  

# U-Boot objects....order is important (i.e. start must be first)

OBJS  = cpu/$(CPU)/start.o
OBJS := $(addprefix $(obj),$(OBJS))

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 

  首先是目標文件start.o,然後是給LIBS變數指定平臺/開發板相關的庫。這些庫是由各個模塊自身編譯生成的。(.a文件)

 

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

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

usb:
    $(MAKE) -C drivers/usb

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

  這些.o文件和.a文件就是由上面的語句編譯生成的。其中庫文件是由每個模塊子目錄自己make後生成的。編譯過程就此結束。

 

3.u-boot的鏈接

  通過連接,我們可以得到最終的u-boot的hex文件,srec文件,二進位文件,img文件。

 

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

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

  

  LDFLAGS確定了程式地址和代碼段、數據段的排列位置。下圖是board/CCJ/u-boot.lds文件

SECTIONS
{
    . = 0x00000000;

    . = ALIGN(4);
    .text      :
    {
      cpu/arm920t/start.o    (.text)
          board/CCJ/boot_init.o (.text)
      *(.text)
    }

    . = ALIGN(4);
    .rodata : { *(.rodata) }

    . = ALIGN(4);
    .data : { *(.data) }

    . = ALIGN(4);
    .got : { *(.got) }

    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;

    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}

  首先定義了基地址0x00000000,這個地址並不是我們實際存儲程式的地址,而是在board目錄下我們還要定義一個config.mk,包含內容TEXT_BASE = 0x33F80000。這個地址是我們自己定義的,基地址+偏移地址,才是u-boot程式最終存儲的地址(0+0x33F80000=0x33F80000)。首先存儲start.o的代碼段,初始化代碼段,然後是只讀數據段,數據段。就此,u-boot鏈接完畢。

  

  第一次寫博客,感覺真挺累的,但是一寫就停不下來。還是很高興的,將自己學到的知識分享給大家,其實嵌入式的學習路程真的很長,需要不斷地努力,還要有興趣。我很喜歡喬布斯說的那句話:“人類追求極致,並分享給同類,然後才能共同進步。”在實際的編程、比賽、項目中,我們都需要合作和互相學習,尤其是不同領域的人們合作更會創造出令人驚嘆的事物,會讓我們感嘆人類創造力的同時讓生活更美好。這大概也是GPL協議的初衷吧,開源、但又尊重人們的自由和所屬權。

  今天寫的博客都是基於這段時間的學習。希望能幫到大家。有寫的不對的地方也希望大家給我指正,我會非常高興,因為我喜歡交流^_^。

  最後謝謝u-boot的作者,能夠將u-boot開源,並提供下載以供全世界的人們學習使用。我尊重u-boot的作者的版權:Wolfgang Denk, DENX Software Engineering, [email protected].

                                    

                                    CCJ

                               2016-11-12 23:43:13

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

  

  

 

 

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一種方式: javabean: 1 public class BusLoanInfoShop { 2 private Integer id; 3 private Integer bid; 4 private String shopName; 5 private String platformNam ...
  • 一.通過Socket實現TCP編程 1.1 TCP編程 TCP協議是面向連接,可靠的,有序的,以位元組流的方式發送數據。基於TCP協議實現網路通信的類有客戶端的Socket類和伺服器端的ServerSocket類。 1.2 伺服器端套路 1.創建ServerSocket對象,綁定監聽埠。 2.通過a ...
  • [java編程思想中文(第4版)] 2007 練習2答案 (1)參照本章的HelloDate.java這個例子,創建一個“Hello,World”程式,改程式 只要輸出這句話即可。你所編寫的類里只需一個方法(即“main”方法,在程式啟動時被執行)。 記住要把它設位static形式,並指定參數表 即 ...
  • 項目背景:spring、hibernate、以及JSF(xhtml頁面)和springmvc(JSP頁面) 1.項目中使用springmvc框架的每個JSP頁面都會使用<%@include file="../resource.jsp"%>來引用一個模板頁面;在resource.jsp頁面中放置所有引 ...
  • 題目大意:有3個整數 x[1], a, b 滿足遞推式x[i]=(a*x[i-1]+b)mod 10001。由這個遞推式計算出了長度為2T的數列,現在要求輸入x[1],x[3],......x[2T-1], 輸出x[2],x[4]......x[2T]. T<=100,0<=x<=10000. 如果 ...
  • 2016年9月20日至2016年11月12日,從學校圖書館借來的《明解c語言》看完了。 大三第一個學期,前8周,有c語言程式設計的課。課本是學校里的老師編寫出版的,為了壓縮空間,減少頁面,書中的代碼都擠成了一團,一點兒美感都沒有。課後習題的參考代碼輸入電腦後,運行錯誤,仔細看一遍,是最基本的邏輯問題 ...
  • 第一步:入口文件增加 第二步:修改config文件,我這裡路由模式設置為2 效果展示: ...
  • gvim的菜單亂碼解決方法: (亂碼是由於系統內碼不相容導致,系統內碼包括gb2312 gb18030 utf-8 utf-16[unicode]等) 生成文件 ~/.gvimrc 並添加如下語句:set encoding=chineseset langmenu=zh_CN.GBKset imcmd... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...