U-Boot啓動流程(Linux內核)的分析

http://www.360doc.com/content/12/0816/10/7775902_230452499.shtml
 
  前面一段時間一直在移植U-Boot,Linux內核和構建根文件系統,其中有些地方還不是很明白,現在回過頭來,理解一下U-boot的啓動流程,以及 u-Boot是如何加載引導內核啓動的。這裏的分析也都是以U-Boot-2009.08版本爲基礎的,可能會和以前的版本有所不同。在這裏也不打算一句 句分析U-Boot的源碼,只是想把U-Boot一步一步怎麼最終能夠加載Linux內核的過程,分析一下。
   首先,我們應該理解Bootloader是什麼?它有什麼作用?其實它就是系統上電後運行的和小段程序。
1 BootLoader的概念
   在系統上電後,需要一段程序來進行初始化:關閉WATCHDOG,改變系統時鐘,初始化存儲控制器,將更多的代碼複製到內存中。並將操作系統內核複製到內 存中運行,這就段程序代碼就叫做Bootloader。沒有一個Bootloader完全支持所有CPU,所以我們要想使用Bootloaser一般情況 下要自己進行修改,我們可以增強Bootloader的功能,讓它具有網絡功能,可以通過NFS遠程下載Linux內核和根文件系統,可以燒寫Linux 內核和根文件系統到NandFlash中,而這些功能對於最終的用戶來說是沒有什麼意義的,它們看到的只是Bootloader引導Linux內核啓動這 一個功能,而其餘的功能只對開發人員很有用處。也就是說在開發期間這些功能是必不可少的。
(1)啓動加載模式:這種模式也稱爲“自主”模式。也就是Bootloader從目標機上的某個固態存儲設備上將操作系統加載到RAM中運行,整個過程並沒有用戶的介入,這種模式是在嵌入式產品發佈裏的通用模式。
(2) 下載模式:在這種模式下,目標機上的Bootloader將通過串口連接或網絡連接等通信手段從主機下載文件,例如:下載內核映像和根文件系統映像等。從 主機下載的文件 通常首先被Bootloader保存到目標機的RAM中,然後再被Bootloader寫到目標上的Flash類的固態存儲設備中,Bootloader 的這種模式是在在開發時使用的工作於這種模式的Bootloader通常都 會向它的終端用戶提供一個簡單的命令行接口。
在嵌入式Linux系統中從軟件的角度通常可以分爲4個層次:
(1)引導加載程序,包括固化在固件中的boot代碼(可選)和Bootloader兩大部分。
    有些CPU在運行Bootloader之前運行一段固化的程序 ,比如x86結構的CPU就是先運行BIOS中的固件,然後才運行硬盤的第一個分區中的BootLoader。在大多數的嵌入式系統中並沒有固件,Bootloader是上電後第一個執行的程序。
(2)Linux內核
     嵌入式定製的內核以及啓動參數,啓動參數可以是Bootloader傳遞給內核的,也可以是內核默認的。
(3)文件系統
   包括根文件系統和建立於Flash內存設備之上的文件系統。裏面包括了Linux系統能夠運行所必要的應用程序和庫文件等。比如可以給用戶提供操作Linux的控制shell程序。
(4)用戶應用程序
特定於用戶的應用程序,它們也存儲在文件系統中,有時在用戶應用程序和內核層之間可以還會包括一個嵌入式圖形用戶界面。
2. Bootloader啓動的兩個階段
 從固態存儲設備上啓動的Bootloader大多都是兩階段的啓動過程,第一階段使用匯編來實現。它完成一些依賴於CPU體系結構的初始化,並調用第二階段的代碼,第二階段則通常使用C語言來實現,這樣可以實現更復雜的功能,而且代碼會有更好的可讀性和可移植性。
(1) Bootloader第一階段的功能
   1)硬件設備初始化
   2)爲加載Bootloader的第二階段準備RAM空間。
   3)複製Bootloader的第二階段代碼到RAM空間中。
   4)設置好棧
   5)跳轉到第二階段代碼的C入口點。
在第一階段進行的硬件初始化一般包括:關閉WATCHDOG,關中斷,設置 CPU的速度和時鐘頻率RAM初始化等。這些不都是必需的。
(2)Bootloader第二階段的功能
   1)初始化本階段要使用的硬件設備
   2)檢測系統內存映射
   3)將內核映像和根文件系映象從Flash望到RAM空間中
   4)爲內核設置啓動參數
   5)調用內核
將內核存放在適當的位置後,直接跳到它的入口點即可調用內核,調用內核之前,下列條件要滿足
(1)CPU寄存器的設置
  R0=0.
  R1=機器類型ID;對於ARM結構的CPU,其機器類型ID在linux/arch/arm/tools/mach-types
 R2=啓動參數標記列表在RAM中起始基地址
(2)CPU工作模式
     必須禁止中斷(IRQs和FIQs)
     CPU必須爲SVC模式
(3)Cach和MMU的設置
     MMU必須關閉
      指令Cach可以打開也可以關閉
     數據Cach必須關閉
分類: Bootloader


     這 一篇主要就是U-Boot的config.mk進行了分析。如果要使用開發板board/<board_name>,就先執行 “make<board_name>_config”命令進行配置,然後執行”make all“,就可以生成 如下3個文件。
U-boot.bin:二進制可執行文件,它就是可以直接燒入ROM,NORFlash的文件
u-Boot:ELF格式的可執行文件,
U-Boot.srec:Motorla S-Record格式的可執行文件
    對於S3C2410的開發板,執行”make smdk2410_config“."make all"後生成的U-Boot.bin可以燒入NOR Flash中運行,啓動後可以看到串口輸出一些信息後進行控制界面。
1。 U-boot的配置過程
在頂層Makefile中可以看到如下代碼:

...........
MKCONFIG    := $(SRCTREE)/mkconfig
........
smdk2410_config    :    unconfig
    @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0

 這是在根目錄下的MAKEFILE文件中的兩個語句,其中的MKCONFIG就是根目錄下的mkconfi文件。$(@:_config=)的結 果就是將”smdk2410_config“中的_config去掉,結果爲“smdk2410”.所以“make smdk2410_config”實際上就是執行如下命令: 

./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

mkconfig的作用,在mkconfig文件開頭第6行給出了它的用法

# Parameters: Target Architecture CPU Board [VENDOR] [SOC]

   對於S3C2410 S3C2440,它們被稱爲Soc(systme on chip),上面除CPU外,還集成了包括UART,USB控制器,NANDFlash控制器等設備,稱爲片上外 設。                   下面看一下makeconfig的作用。
(1)確定開發板名稱BOARD_NAME,相關代碼如下:

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

   對於./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0命令,其中沒有 "--","-a","-n"等符號,所以上面幾行不會執行。

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

執行完上面的這句後,BOADR_NAME的值等於第1個參數,即"s3ck2410"
(2)創建到平臺開發板相關折頭文件的鏈接

if [ "$SRCTREE" != "$OBJTREE" ] ; then //判斷源代碼目錄和目標文件目錄是否是一樣
    mkdir -p ${OBJTREE}/include
    mkdir -p ${OBJTREE}/include2
    cd ${OBJTREE}/include2
    rm -asm
    ln -s ${SRCTREE}/include/asm-$asm
    LNPREFIX="../../include2/asm/"
    cd ../include
    rm -rf asm-$2
    rm -asm
    mkdir asm-$2
    ln -asm-$asm
else
    cd ./include
    rm -asm
    ln -asm-$asm
fi

   直接在源代碼目錄下編譯時,條件不滿足,將執行else分支的代碼,在else分支中,進入include目錄,刪除asm文件,然後再次建立 asm文件,並令它鏈接向asm-$2目錄,即asm-arm。

rm -asm-$2/arch //刪除asm-$2/arch目錄,即asm-arm/arch

if [ -"$6" -"$6" = "NULL" ] ; then //$6="s3c24x0"不爲空,也不爲NULL,執行else分支

    ln -s ${LNPREFIX}arch-$asm-$2/arch //LNPREFIX 爲空,這個命令實際上等同於"ln - s arch-s3c24x0 asm-arm/arch"

else
    ln -s ${LNPREFIX}arch-$asm-$2/arch
fi

if [ "$2" = "arm" ] ; then //重新建立/asm-arm/proc文件,並讓它鏈接向proc-armv目錄 

    rm -asm-$2/proc
    ln -s ${LNPREFIX}proc-armv asm-$2/proc
fi

(3)創建頂層MAKEFILE包含的文件include/config.mk

#
# 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

   對於./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0命令,上面幾行創建的config.mk文件的內容如下

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

(4)創建開發板相關的頭文件include/config.h

#
# 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
echo "#include <asm/config.h>" >>config.h

exit 0

APPEND維持原值"NO",所以config.h被重新建立,也就是執行echo "#include <configs/$1.h>" >>config.h
#include <configs/smdk2410.h>
總之,當你執行make smdk2410_config ,實際的作用就是執行./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0,它將產生如下的

幾種作用
(1) 開發板的名稱 BOARD_NAME等於 $1
(2)創建到平臺,開發板相關的頭文件的鏈接,如下所示
ln -s asm-$2 asm
ln -s arch-$6 asm-S2/arch
ln - s proc-armv asmn-$2/proc 如果$2不是arm的話,此行沒有
(3)創建頂層Makefile包含的incldue /config.mk,如下所示
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $ $5  爲空,或者NULL的話,些行沒有
SOC = $6
(4) 創建開發板相關的頭文件include/config.h,如下 所示
#include <config.h/$1.h>
   從上面執行完命令後的結果,可以看出來,如果要在board目錄下新建一個開發板<board_name>的目錄,則在 include/configs 目錄下也要建立一個文件<board_name>.h,裏面存放的就是開發板<board_name>的配置信息。
3.U-Boot的編譯,連接過程

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

# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif

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

這是根目錄下的Makefile中與ARM相關的代碼。
第 一行中包含的config.mk文件,就是在第一開始配置過程中製作出來的include/conifg.mk文件,我們在一開始配置U-boot時執行 過mkconfig。mini2440 時生成的文件,其中定義了ARCH,CPU,BOARD,SOC等。4個變量的值爲arm,arm920t,smdk2410,s3c24x0.我們在執 行mkconfig。mini2440時,其實執行的是如下的命令:

./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

   最後一句話include $(TOPDIR)/config.mk 包含頂層目錄的config.mk文件。它根據上面4個變量的值確定了編譯器。編譯選項等。在頂層的config.mk中可以看到:

fdef    VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
ifdef    BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk    # include board specific rules
endif

LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds

LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)
ifneq ($(TEXT_BASE),)
LDFLAGS += -Ttext $(TEXT_BASE)
endif

   在u-boot-2009.08\board\samsung\smdk2410\config.mk中定義了“TEXT_BASE = 0x33F80000”.所以最終結果是:BOARDDIR爲smdk2410;LDFLAGS中有“-T \cpu\arm920t\u-boot.lds -Ttext 0x33f80000”.其中的-Ttext $(TEXT_BASE),這句指明瞭代碼段的起始地址。爲什麼是0x33F8 0000呢?這是將NAND中Uboot拷貝到RAM中的起始地址,所以在代碼拷貝到RAM之前不能使用絕對地址來尋址數據,只能用相對地址,在以下將用 虛擬地址來指Uboot在RAM中的地址,也就是0x33F80000
繼續分析MAKEFIle文件:

OBJS = cpu/$(CPU)/start.o
LIBS = lib_generic/libgeneric.a
LIBS += lib_generic/lzma/liblzma.a
LIBS += lib_generic/lzo/liblzo.a
LIBS += $(shell if [ -f board/$(VENDOR)/common/Makefile ]; then echo \
    "board/$(VENDOR)/common/lib$(VENDOR).a"; fi)
LIBS += cpu/$(CPU)/lib$(CPU).a

    從上面的第一行我們可以看到OBJS的第一個值爲"cpu/$(CPU)/start.o",即"cpu/arm920t/start.o"。下面的幾行 指定了LIBS變量,也就是平臺,開發板相關的各個目錄,通用目錄下相應的庫。OBJS LIBS所代表的.o,.a文件構成了U-Boot,它們通過下面相應的源文件編譯得到。

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

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

    對於OBJS中的每個成員,都將進入cpu/$(CPU)目錄編譯它們,現在的OBJS爲cpu/arm920t/start.o。它由cpu /arm920t/start.S編譯得到。對於LIBS中的每個成員,都將進入相應的子目錄執行"make命令"。當所有的OBJS,LIBS所表示 的.o .a文件都生成後,就剩最後的連接了,這對應MAKEFILE中的下面幾行:

$(obj)u-boot.srec:    $(obj)u-boot
        $(OBJCOPY) -O srec $< $@

$(obj)u-boot.bin:    $(obj)u-boot
        $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

$(obj)u-boot.ldr:    $(obj)u-boot
        $(obj)tools/envcrc --binary > $(obj)env-ldr.o
        $(LDR) -T $(CONFIG_BFIN_CPU) -c $@ $< $(LDR_FLAGS)

$(obj)u-boot.ldr.hex:    $(obj)u-boot.ldr
        $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ -I binary

$(obj)u-boot.ldr.srec:    $(obj)u-boot.ldr
        $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ -I binary

$(obj)u-boot.img:    $(obj)u-boot.bin
        ./tools/mkimage -A $(ARCH) -T firmware -none \
        -a $(TEXT_BASE) -e 0 \
        -n $(shell sed --'s/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
            sed -'s/"[     ]*$$/ for $(BOARD) board"/') \
        -d $< $@
................

GEN_UBOOT = \
        UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
        sed --'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
$(obj)u-boot:    depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT)$(obj)u-boot.lds
        $(GEN_UBOOT)

   先使用$(obj)u-boot:規則連接得到ELF格式的U-Boot,最後轉換爲二進制格式u-boot.bin.S-Record格式u- Boot.srec.其中LDFLAGS確定了連接方式,也就是-T \cpu\arm920t\u-boot.lds -Ttext 0x33f80000指定了程序的佈局地址,\cpu\arm920t\U-Boot.lds文件如下:

UTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x00000000;

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

    . = ALIGN(4);
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.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 (NOLOAD) : { *(.bss) . = ALIGN(4); }
    _end = .;
}

    從cpu/arm920t/start.o (.text) 被放在程序的最前面,所以U-Boot的入口點在cpu/arm920t/start.s中,
總結一下U-Boot的編譯流程:
(1)首先編譯cpu/$(CPU)/start.s,對於不同的CPU,還可能編譯cpu/$(CPU)下面的其他文件。
(2)然後,對於平臺開發板相關的每個目錄,每個通用目錄都使用它們各自的MAKEFILE生成相應和庫。
(3)將1,2步驟生成的.o.a文件按照$(BOARDDIR)/config.mk 文件中指定的代碼段起始地址。$(obj)u-boot.lds 連接腳本進行連接。
(4)第3步得到的是ELF格式的U-Boot,後面MAKEFILE還會將它轉換爲二進制格式 S-Record格式。

分類: Bootloader


    U-boot屬於兩階段的Bootloader,第一階段的文件爲cpu/arm920t/start.S 和board\samsung\smdk2410/lowlevel_init.S,前者是平臺相關的,後者是開發板相關的。
1.U-Boot第一階段代碼分析
(1)硬件設備初始化
依次完成如下設置:將CPU的工作模式設爲管理模式(SVC),關閉WATCHDOG,設置FCLK,HCLK,PCLK的比例,關閉MMU,CACHE。代碼在cpu/arm920t/start.S中,
(2)爲加載Bootloader的第二階段代碼準備RAM空間。
所謂準備RAM空間,就是初始化內存芯片,使它可用,對於S3C24x0,通過在Start.S中調用lowlevel_init函數來設置存儲控制器,使得外接
SDRAM可用,lowlevel_init.S,文件是與開發板相關的,這表示如果外接的設備不一樣,可以修改lowlevel_init.S文件中的相關的宏。

_TEXT_BASE:
 .word TEXT_BASE //這裏是獲得代碼段的起始地址,我的是0x33F80000(在board/xxx/config.mk中
                  //可到找到“TEXT_BASE=0x33F80000”
.globl lowlevel_init //這裏相當於定義一個全局的lowlevel_init以方便調用

lowlevel_init:
 /* memory control configuration */
 /* make r0 relative the current location so that it */
 /* reads SMRDATA out of FLASH rather than memory ! */
 ldr r0, =SMRDATA //SMDATA表示這 13個寄存器的值存放的開始地址,值爲0x33F8xxxx,處於內
                       //存中,這一句的作用是把其值加載到r0中
 
 ldr r1, _TEXT_BASE // 把代碼的起始地址(0x33F80000)加載到r1中

 sub r0, r0, r1 //r0減去r1其結果存入r0,也即SMDATA中的起始地址0x33F8xxxx減去
                       //0x33F80000,其結果就是13個寄存器的值在NOR Flash存放的開始地址

 ldr r1, =BWSCON /* Bus Width Status Controller */ //存儲控制器的基地址
 add r2, r0, #13*//在計算出來的存放地址加上#13*4,然後其結果保存在r2中
                           //13 個寄存器,每個寄存器佔4個字節

0:
 ldr r3, [r0], #//內存中r0的值加載到r3中,然後r0加4,即下一個寄存器的

 str r3, [r1], #//讀出寄存器的值保存到r1中,然後r1也偏移4

 cmp r2, r0 //比較r0與r2的值,如果不等繼續返回0:執行,也即13個寄存器的值
                           // 是否讀完
 bne 0b
 /* everything is fine now */
 mov pc, lr //程序跳轉,返回到cpu_init_crit中
 .ltorg
/* the literal pools origin */
SMRDATA:
...................

(3)複製Bootloader的第二階段代碼到RAM空間中
這裏將整個U-Boot代碼都複製到SDRAM中,這在cpu/arm920t/start.s中實現

relocate:                /* 將U-Boot複製到RAM中     */
    adr    r0, _start        /* r0:當前代碼的開始地址 */
    ldr    r1, _TEXT_BASE        /* r1:代碼段的連接地址*/
    cmp r0, r1 /* 測試現在是在FLash中,還在是RAM中,如果要從NandFlash啓動的話,這裏要根據需要修改 */
    beq stack_setup /*如果已經在RAM中,則不需要複製*/

    ldr    r2, _armboot_start /*_armboot_start在前面定義,是第一條指令的運行地址*/
    ldr    r3, _bss_start /*在連接腳本U-Boot.lds中定義,是代碼段的結束地址*/
    sub    r2, r3, r2        /* r2 <- 代碼段長度 */
    add    r2, r0, r2        /* r2 <-代碼段的結束地址 */

copy_loop:
    ldmia     {r3-r10}        /* 從地址[r0]處獲得數據 */
    stmia     {r3-r10}        /* 複製到地址[r1]處 */
    cmp    r0, r2            /* 判斷是否複製完畢 */
    ble    copy_loop /*沒有複製完,則繼續*/
#endif    /* CONFIG_SKIP_RELOCATE_UBOOT */

上面這段程序,在使用NANDFlash啓動時,需要修改。
(4)設置好棧
/*棧的設置靈活性很大,只要讓sp寄存器指向一段沒有使用的內存即可*/

stack_setup:
    ldr    r0, _TEXT_BASE        /* _TEXT_BASE 爲代碼段的開始地址,值爲0x33F80000 */
    sub    r0, r0, #CONFIG_SYS_MALLOC_LEN    /* 代碼段下面,留出一段內存以實現malloc */
    sub    r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 再留出一段內存,存一些全局參數 */
#ifdef CONFIG_USE_IRQ
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
    sub    sp, r0, #12        /* 最後,留出12字節的內存給abort異常 */

clear_bss:
    ldr    r0, _bss_start        /* 下面的都是棧 */
    ldr    r1, _bss_end        /* stop here */
    mov    r2, #0x00000000        

(5)跳轉到第二階段代碼的C入口點
在跳轉之前,還要清除BSS段(初始值0,無初始值的全局變量,靜態變量放在BSS段。

clear_bss:
    ldr    r0, _bss_start        /* BSS段的開始地址,它的值在連接腳本中U-Boot.lds中確定 */
    ldr    r1, _bss_end        /* BSS段的結束地址,它的值在連接腳本u-Boot.lds中確定 */
    mov    r2, #0x00000000        /* clear */

clbss_l:str    r2, [r0]        /* 向BSS段中寫入0值 */
    add    r0, r0, #4
    cmp    r0, r1
    ble    clbss_l

   現在,C函數的運行環境已經完全準備好,通過如下命令直接跳轉,這之後在內存中執行,原先在NorFlash中,它將調用lib_arm/boadr.c中的star_armboot函數,這是第二階段的入口。

ldr    pc, _start_armboot

_start_armboot:    .word start_armboot

2 U-Boot第二階段代碼分析
    U-boot在啓動內核之前可以讓用戶決定是否進入下載模式,即進入U-Boot的控制界面。
第二階段從lib_arm/borad.c中的start_armboot函數開始,程序的流程如下 :
(1)初始化本階段要使用到的硬件設備
最主要的是設置系統時鐘,初始化串口,只要這兩個設置好了,就可以從串口看到打印信息。
board_init 函數設置MPLL,改變系統時鐘,它是開發板相關函數。board\samsung\smdk2410/smdk2410.c中實現。串口的初始化函數主 要是serial_init,它設置UART控制器,是CPU相關的函數,在cpu/arm920t/s3c24x0/serial.c中實現。
(2)檢測系統內存映射
對於特定的開發板,其內存的分佈是明確的,所以可以直接設置,board\samsung\smdk2410\smdk2410.c中的dram_init函數指定了本開發板的內存起始地址爲0x30000000,大小爲0x40000000。
 

int dram_init (void)
{
    gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

    return 0;
}

(3) 爲內核設置啓動參數
   在start_armboot()函數的最後,調用main_loop()函數,進行一個無限循環,該函數在common/main.c文件中定義。

u-boot-2009.08\lib_arm\bootm.c文件中,定義了引導Linux內核的 do_bootm_linux()函數,U_Boot也是通過標記列表向內核傳遞參數的,一般而言,設置這以下兩個標記就可以了,在配置文件 include/configs/smdk2410.h中,增加如下兩個配置項即可:

#define CONFIG_SETUP_MEMORY_TAGS 1
#define CONFIG_CMDLINE_TAG 1

   對於ARM架構的CPU來說,都是通過u-boot-2009.08\lib_arm\bootm.c中的do_bootm_linux函數來啓動內核 的,這個函數中,設置標記列表,最後通過“theKernel (0, machid, bd->bi_boot_params);”調用內核,其中,這裏第1、2、3個參數就分別存儲在r0、r1、r2中。theKernel指向內核 存放的地址,(對於ARM架構的CPU,通常是0x30008000),bd->bi_boot_params就是在board_init函數設置 的機器類型ID,而bd->bi_boot_params就是標記列表的地址


分類: Bootloader


   在上一篇中分析到u-Boot啓動Linux內核的函數do_bootm_linux,這一篇則着重分析,U-boot是如果一步一步啓動內核的。
    我們可以看到在,start_armboot()函數的最後,在一個無限循環中調用了函數main_loop(),該函數在common/main.c文件中被定義,我們可以看到下面的一段代碼:

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    s = getenv ("bootdelay");   //得到環境變量中bootdelay
    bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);

  如果定義了CONFIG_BOOTDELAY,則在沒有CONFIG_BOOTDELAY秒中,串口上沒有輸入,則會進行自動的引導Linux內核。也就是執行bootcmd命令。

#ifdef CONFIG_BOOTCOUNT_LIMIT //啓動次數的限制功能,如果到達一定次數,將不能啓動u-boot.
    if (bootlimit && (bootcount > bootlimit)) {//檢查是否超出啓動次數限制
        printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
         (unsigned)bootlimit);
        s = getenv ("altbootcmd");//啓動延時
    }
    else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
        s = getenv ("bootcmd");// 獲得啓動參數

    debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
// 這裏如果bootdelay大於0,並且中間沒有被中斷的話,執行命令行參數
    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
  int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CONFIG_SYS_HUSH_PARSER
  run_command (s, 0); //運行啓動的命令行,例如 可以使用tftp命令
# else
  parse_string_outer(s, FLAG_PARSE_SEMICOLON |
        FLAG_EXIT_FROM_LOOP);
# endif

   到這裏我們就可以看到是怎麼調用設置的命令行參數的,在這裏還要使用到bootm命令,先來看看bootm命令的實現,在common/cmd_bootm.c

#define CONFIG_BOOTM_LINUX 1
#define CONFIG_BOOTM_NETBSD 1
#define CONFIG_BOOTM_RTEMS 1

#ifdef CONFIG_BOOTM_LINUX
extern boot_os_fn do_bootm_linux;
#endif
#ifdef CONFIG_BOOTM_NETBSD
static boot_os_fn do_bootm_netbsd;
#endif

   可 以看出如果定義了CONFIG_BOOTM_LINUX這個宏的話,就會使用外部文件定義的do_bootm_linux函數,在arm體系結構中,就是 在lib_arm/bootm.c文件中,可以從lib_arm/bootm.c文件中的59行看到do_bootm_linux()的定義。其中第64 行聲明瞭這樣一個函數指針theKernel

void (*theKernel)(int zero, int arch, uint params);

   看看它的名字和參數的命名我們也可以猜到這個其實就是內核的入口函數的指針了。幾個參數的命名也說明了下文提到的ARM Linux內核啓動要求的第一條,因爲根據ACPS(ARM/Thumb Procedure Call Standard)的規定,這三個參數就是依次使用r0,r1和r2來傳遞的。接下來第73行就是給這個函數指針賦值: 

theKernel = (void (*)(int, int, uint))images->ep;

    可 以看到theKernel被賦值爲images->ep,這個image指使用tools/mkimage工具程序製作uImage時加在 linux.bin.gz前面的一個頭部,而ep結構體成員保存的就是使用mkimage時指定的-e參數的值,即內核的入口點(Entry Point)。知道了images->ep的意義之後,給theKernel賦這個值也就是理所當然的了。
     image是bootm_headers結構體的指針,可以在inlcude/image.h文件中看到這個結構體的定義如下:

typedef struct bootm_headers {
     ............................

    int        fit_noffset_fdt;/* FDT blob subimage node offset */
#endif

#ifndef USE_HOSTCC
    image_info_t    os;        /* os image info */
    ulong        ep;        /* entry point of OS */

    ulong        rd_start, rd_end;/* ramdisk start/end */
...............

}

最後是對內核入口函數的調用,發生在第128行:

theKernel (0, machid, bd->bi_boot_params);

   調用的時候對參數進行賦值,r0=0,r1=bd->bi_arch_number,r2=bd-> bi_boot_params,一個都不少。至此U-Boot的使命完成,開始進入ARM Linux的世界。
   要知道哪個地址是啓動內核,哪個地址啓動文件系統,要分析common/cmd_bootm.c中的函數 do_bootm,因爲引導kernel就是bootm這條命令的工作,do_bootm是命令bootm的執行函數現在我們來分析一下 common/cmd_bootm.c中的函數do_bootm,這是bootm命令的處理函數.do_bootm()函數中的很多功能都是分成了函數的 形式,而在以前的版本中沒有這麼有結構層次,這裏我們也只是分析對引導Linux內核有作用的部分,因爲這是一個在common文件夾下的文件,也就意味 着,在引導別的操作系統時也會用到這個函數,而不單單是Linux操作系統.

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    ulong        iflag;
    ulong        load_end = 0;
    int        ret;
    boot_os_fn    *boot_fn;
#ifndef CONFIG_RELOC_FIXUP_WORKS
    static int relocated = 0;
    /* relocate boot function table */
    if (!relocated) {
        int i;
        for (= 0; i < ARRAY_SIZE(boot_os); i++)
            if (boot_os[i] != NULL)
                boot_os[i] += gd->reloc_off;
        relocated = 1;
    }
#endif
    /* determine if we have a sub command */
    if (argc > 1) {
        char *endp;

        simple_strtoul(argv[1], &endp, 16);
    
        if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
            return do_bootm_subcommand(cmdtp, flag, argc, argv);
    }

    if (bootm_start(cmdtp, flag, argc, argv)) //提取mkimage生成的文件頭部,放到bootm_headers_t結構體中
        return 1;

    iflag = disable_interrupts();

#if defined(CONFIG_CMD_USB)
       usb_stop();
#endif

#ifdef CONFIG_AMIGAONEG3SE
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */

    icache_disable();
    dcache_disable();
#endif

    ret = bootm_load_os(images.os, &load_end, 1); //加載操作系統的關鍵部分 確定使用的地址
    if (ret < 0) { //出錯處理
        if (ret == BOOTM_ERR_RESET)
            do_reset (cmdtp, flag, argc, argv);
        if (ret == BOOTM_ERR_OVERLAP) {
            if (images.legacy_hdr_valid) {
                if (image_get_type (&images.legacy_hdr_os_copy) ==IH_TYPE_MULTI)
                    puts ("WARNING: legacy format multi component "
                        "image overwritten\n");
            } else {
                puts ("ERROR: new format image overwritten - "
                    "must RESET the board to recover\n");
                show_boot_progress (-113);
                do_reset (cmdtp, flag, argc, argv);
            }
        }
        if (ret == BOOTM_ERR_UNIMPLEMENTED) {
            if (iflag)
                enable_interrupts();
            show_boot_progress (-7);
            return 1;
        }
    }
    lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));

    if (images.os.type == IH_TYPE_STANDALONE) {//獨立的應用程序
        if (iflag)
            enable_interrupts();
        /* This may return when 'autostart' is 'no' */
        bootm_start_standalone(iflag, argc, argv);
        return 0;
    }
    show_boot_progress (8);

#ifdef CONFIG_SILENT_CONSOLE //這裏處理Linux操作系統
    if (images.os.os == IH_OS_LINUX)
        fixup_silent_linux()//該函數中處理bootarg參數
#endif
    boot_fn = boot_os[images.os.os];
    if (boot_fn == NULL) {
        if (iflag)
            enable_interrupts();
        printf ("ERROR: booting os '%s' (%d) is not supported\n",
            genimg_get_os_name(images.os.os), images.os.os);
        show_boot_progress (-8);
        return 1;
    }

    arch_preboot_os();
/*下面的函數,繼續引導內核的鏡像,複製image header 到全局變量header;
檢查header的魔數,檢查數,header和image中的這兩個。確定image的體系結構和類型(KERNEL  or MULTI),關閉中斷,加載image到header中的加載地址*/

    boot_fn(0, argc, argv, &images); //調用do_bootm_linux()函數
    show_boot_progress (-9);
#ifdef DEBUG
    puts ("\n## Control returned to monitor - resetting...\n");
#endif
    do_reset (cmdtp, flag, argc, argv);

    return 1;
}

下面我們看一下bootm_load_os()函數

static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)
{
    uint8_t comp = os.comp;
    ulong load = os.load;
    ulong blob_start = os.start;
    ulong blob_end = os.end;
    ulong image_start = os.image_start;
    ulong image_len = os.image_len;
    uint unc_len = CONFIG_SYS_BOOTM_LEN;

    const char *type_name = genimg_get_type_name (os.type);

    switch (comp) { //判斷image的壓縮類型
    case IH_COMP_NONE
        if (load == blob_start) {
            printf (" XIP %s ... ", type_name);
        } else {
            printf (" Loading %s ... ", type_name);
//如果在Image head中加載的地址和bootm命令參數2指定的地址相同,則不需要複製,直接執行
            if (load != image_start) {
                memmove_wd ((void *)load,
                        (void *)image_start, image_len, CHUNKSZ);
            }
        }
        *load_end = load + image_len;
        puts("OK\n");
        break;
    case IH_COMP_GZIP:
        printf (" Uncompressing %s ... ", type_name);
        if (gunzip ((void *)load, unc_len,
                    (uchar *)image_start, &image_len) != 0) {
            puts ("GUNZIP: uncompress, out-of-mem or overwrite error "
                "- must RESET board to recover\n");
            if (boot_progress)
                show_boot_progress (-6);
            return BOOTM_ERR_RESET;
        }
        *load_end = load + image_len;
        break;
#ifdef CONFIG_BZIP2
    case IH_COMP_BZIP2: //判斷是什麼類型的壓縮類型
        printf (" Uncompressing %s ... ", type_name);
         int i = BZ2_bzBuffToBuffDecompress ((char*)load,
                    &unc_len, (char *)image_start, image_len,
                    CONFIG_SYS_MALLOC_LEN < (4096 * 1024), 0);
        if (!= BZ_OK) {
            printf ("BUNZIP2: uncompress or overwrite error %d "
                "- must RESET board to recover\n", i);
            if (boot_progress)
                show_boot_progress (-6);
            return BOOTM_ERR_RESET;
        }
        *load_end = load + unc_len;
        break;
#endif /* CONFIG_BZIP2 */
#ifdef CONFIG_LZMA
    case IH_COMP_LZMA:
        printf (" Uncompressing %s ... ", type_name);

....................
   return 0;
}

   如果image header中指示的加載地址和bootm命令中參數2指定的地址不相同,則表示要從image header中指示的加載地址處把image data copy到bootm命令中參數2指定的地址處,然後再執行。
   bootm命令是用來引導經過u-boot的工具mkimage打包後的kernel image的。
mkimage的用法
   uboot源代碼的tools/目錄下有mkimage工具,這個工具可以用來製作不壓縮或者壓縮的多種可啓動映象文件。
   mkimage在製作映象文件的時候,是在原來的可執行映象文件的前面加上一個0x40字節的頭,記錄參數所指定的信息,這樣uboot才能識別這個映象 是針對哪個CPU體系結構的,哪個OS的,哪種類型,加載內存中的哪個位置, 入口點在內存的那個位置以及映象名是什麼?到這裏整個U-Boot是如何啓動Linux內核的,基本上也就清楚了,特別是如何向Linux內核傳送的參 數。
PS:下面是“ARM Linux Kernel Boot Requirements”,這篇文章中介紹的,引導Linux內核啓動的必須要滿足的幾個條件:

* CPU register settings //這裏也就是我們的theKernel中的作用
          o r0 = 0.
          o r1 = machine type number.
          o r2 = physical address of tagged list in system RAM.
    * CPU mode
          o All forms of interrupts must be disabled (IRQs and FIQs.)
          o The CPU must be in SVC mode. (A special exception exists forAngel.)
    * Caches, MMUs
          o The MMU must be off.
          o Instruction cache may be on or off.
          o Data cache must be off and must not contain any stale data.
    * Devices
          o DMA to/from devices should be quiesced.
    * The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.



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