九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
U-Boot啟動流程(Linux內(nèi)核)的分析
分類: Bootloader


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


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

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

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

./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控制器等設(shè)備,稱為片上外設(shè)。                   下面看一下makeconfig的作用。
(1)確定開發(fā)板名稱BOARD_NAME,相關(guān)代碼如下:

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"等符號,所以上面幾行不會執(zhí)行。

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

執(zhí)行完上面的這句后,BOADR_NAME的值等于第1個參數(shù),即"s3ck2410"
(2)創(chuàng)建到平臺開發(fā)板相關(guān)折頭文件的鏈接

if [ "$SRCTREE" != "$OBJTREE" ] ; then //判斷源代碼目錄和目標(biāo)文件目錄是否是一樣
    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

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

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

if [ -z "$6" -o "$6" = "NULL" ] ; then //$6="s3c24x0"不為空,也不為NULL,執(zhí)行else分支

    ln -s ${LNPREFIX}arch-$3 asm-$2/arch //LNPREFIX 為空,這個命令實(shí)際上等同于"ln - s arch-s3c24x0 asm-arm/arch"

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

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

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

(3)創(chuàng)建頂層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命令,上面幾行創(chuàng)建的config.mk文件的內(nèi)容如下

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

(4)創(chuàng)建開發(fā)板相關(guān)的頭文件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被重新建立,也就是執(zhí)行echo "#include <configs/$1.h>" >>config.h
#include <configs/smdk2410.h>
總之,當(dāng)你執(zhí)行make smdk2410_config ,實(shí)際的作用就是執(zhí)行./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0,它將產(chǎn)生如下的

幾種作用
(1) 開發(fā)板的名稱 BOARD_NAME等于 $1
(2)創(chuàng)建到平臺,開發(fā)板相關(guān)的頭文件的鏈接,如下所示
ln -s asm-$2 asm
ln -s arch-$6 asm-S2/arch
ln - s proc-armv asmn-$2/proc 如果$2不是arm的話,此行沒有
(3)創(chuàng)建頂層Makefile包含的incldue /config.mk,如下所示
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $ $5  為空,或者NULL的話,些行沒有
SOC = $6
(4) 創(chuàng)建開發(fā)板相關(guān)的頭文件include/config.h,如下 所示
#include <config.h/$1.h>
   從上面執(zhí)行完命令后的結(jié)果,可以看出來,如果要在board目錄下新建一個開發(fā)板<board_name>的目錄,則在 include/configs 目錄下也要建立一個文件<board_name>.h,里面存放的就是開發(fā)板<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相關(guān)的代碼。
第一行中包含的config.mk文件,就是在第一開始配置過程中制作出來的include/conifg.mk文件,我們在一開始配置U-boot時執(zhí)行過mkconfig。mini2440 時生成的文件,其中定義了ARCH,CPU,BOARD,SOC等。4個變量的值為arm,arm920t,smdk2410,s3c24x0.我們在執(zhí)行mkconfig。mini2440時,其實(shí)執(zhí)行的是如下的命令:

./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

   最后一句話include $(TOPDIR)/config.mk 包含頂層目錄的config.mk文件。它根據(jù)上面4個變量的值確定了編譯器。編譯選項(xiàng)等。在頂層的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”.所以最終結(jié)果是:BOARDDIR為smdk2410;LDFLAGS中有“-T \cpu\arm920t\u-boot.lds -Ttext 0x33f80000”.其中的-Ttext $(TEXT_BASE),這句指明了代碼段的起始地址。為什么是0x33F8 0000呢?這是將NAND中Uboot拷貝到RAM中的起始地址,所以在代碼拷貝到RAM之前不能使用絕對地址來尋址數(shù)據(jù),只能用相對地址,在以下將用虛擬地址來指Uboot在RAM中的地址,也就是0x33F80000
繼續(xù)分析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變量,也就是平臺,開發(fā)板相關(guān)的各個目錄,通用目錄下相應(yīng)的庫。OBJS LIBS所代表的.o,.a文件構(gòu)成了U-Boot,它們通過下面相應(yīng)的源文件編譯得到。

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

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

    對于OBJS中的每個成員,都將進(jìn)入cpu/$(CPU)目錄編譯它們,現(xiàn)在的OBJS為cpu/arm920t/start.o。它由cpu/arm920t/start.S編譯得到。對于LIBS中的每個成員,都將進(jìn)入相應(yīng)的子目錄執(zhí)行"make命令"。當(dāng)所有的OBJS,LIBS所表示的.o .a文件都生成后,就剩最后的連接了,這對應(yīng)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 -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 $< $@
................

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

   先使用$(obj)u-boot:規(guī)則連接得到ELF格式的U-Boot,最后轉(zhuǎn)換為二進(jìn)制格式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的入口點(diǎn)在cpu/arm920t/start.s中,
總結(jié)一下U-Boot的編譯流程:
(1)首先編譯cpu/$(CPU)/start.s,對于不同的CPU,還可能編譯cpu/$(CPU)下面的其他文件。
(2)然后,對于平臺開發(fā)板相關(guān)的每個目錄,每個通用目錄都使用它們各自的MAKEFILE生成相應(yīng)和庫。
(3)將1,2步驟生成的.o.a文件按照$(BOARDDIR)/config.mk 文件中指定的代碼段起始地址。$(obj)u-boot.lds 連接腳本進(jìn)行連接。
(4)第3步得到的是ELF格式的U-Boot,后面MAKEFILE還會將它轉(zhuǎn)換為二進(jìn)制格式 S-Record格式。

分類: Bootloader


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

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

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,處于內(nèi)
                       //存中,這一句的作用是把其值加載到r0中
 
 ldr r1, _TEXT_BASE // 把代碼的起始地址(0x33F80000)加載到r1中

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

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

0:
 ldr r3, [r0], #4 //內(nèi)存中r0的值加載到r3中,然后r0加4,即下一個寄存器的

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

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

(3)復(fù)制Bootloader的第二階段代碼到RAM空間中
這里將整個U-Boot代碼都復(fù)制到SDRAM中,這在cpu/arm920t/start.s中實(shí)現(xiàn)

relocate:                /* 將U-Boot復(fù)制到RAM中     */
    adr    r0, _start        /* r0:當(dāng)前代碼的開始地址 */
    ldr    r1, _TEXT_BASE        /* r1:代碼段的連接地址*/
    cmp r0, r1 /* 測試現(xiàn)在是在FLash中,還在是RAM中,如果要從NandFlash啟動的話,這里要根據(jù)需要修改 */
    beq stack_setup /*如果已經(jīng)在RAM中,則不需要復(fù)制*/

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

copy_loop:
    ldmia     {r3-r10}        /* 從地址[r0]處獲得數(shù)據(jù) */
    stmia     {r3-r10}        /* 復(fù)制到地址[r1]處 */
    cmp    r0, r2            /* 判斷是否復(fù)制完畢 */
    ble    copy_loop /*沒有復(fù)制完,則繼續(xù)*/
#endif    /* CONFIG_SKIP_RELOCATE_UBOOT */

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

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

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

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

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

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

   現(xiàn)在,C函數(shù)的運(yùn)行環(huán)境已經(jīng)完全準(zhǔn)備好,通過如下命令直接跳轉(zhuǎn),這之后在內(nèi)存中執(zhí)行,原先在NorFlash中,它將調(diào)用lib_arm/boadr.c中的star_armboot函數(shù),這是第二階段的入口。

ldr    pc, _start_armboot

_start_armboot:    .word start_armboot

2 U-Boot第二階段代碼分析
    U-boot在啟動內(nèi)核之前可以讓用戶決定是否進(jìn)入下載模式,即進(jìn)入U-Boot的控制界面。
第二階段從lib_arm/borad.c中的start_armboot函數(shù)開始,程序的流程如下 :
(1)初始化本階段要使用到的硬件設(shè)備
最主要的是設(shè)置系統(tǒng)時鐘,初始化串口,只要這兩個設(shè)置好了,就可以從串口看到打印信息。
board_init函數(shù)設(shè)置MPLL,改變系統(tǒng)時鐘,它是開發(fā)板相關(guān)函數(shù)。board\samsung\smdk2410/smdk2410.c中實(shí)現(xiàn)。串口的初始化函數(shù)主要是serial_init,它設(shè)置UART控制器,是CPU相關(guān)的函數(shù),在cpu/arm920t/s3c24x0/serial.c中實(shí)現(xiàn)。
(2)檢測系統(tǒng)內(nèi)存映射
對于特定的開發(fā)板,其內(nèi)存的分布是明確的,所以可以直接設(shè)置,board\samsung\smdk2410\smdk2410.c中的dram_init函數(shù)指定了本開發(fā)板的內(nèi)存起始地址為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) 為內(nèi)核設(shè)置啟動參數(shù)
   在start_armboot()函數(shù)的最后,調(diào)用main_loop()函數(shù),進(jìn)行一個無限循環(huán),該函數(shù)在common/main.c文件中定義。

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

#define CONFIG_SETUP_MEMORY_TAGS 1
#define CONFIG_CMDLINE_TAG 1

   對于ARM架構(gòu)的CPU來說,都是通過u-boot-2009.08\lib_arm\bootm.c中的do_bootm_linux函數(shù)來啟動內(nèi)核的,這個函數(shù)中,設(shè)置標(biāo)記列表,最后通過“theKernel (0, machid, bd->bi_boot_params);”調(diào)用內(nèi)核,其中,這里第1、2、3個參數(shù)就分別存儲在r0、r1、r2中。theKernel指向內(nèi)核存放的地址,(對于ARM架構(gòu)的CPU,通常是0x30008000),bd->bi_boot_params就是在board_init函數(shù)設(shè)置的機(jī)器類型ID,而bd->bi_boot_params就是標(biāo)記列表的地址


分類: Bootloader


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

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

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

  如果定義了CONFIG_BOOTDELAY,則在沒有CONFIG_BOOTDELAY秒中,串口上沒有輸入,則會進(jìn)行自動的引導(dǎo)Linux內(nèi)核。也就是執(zhí)行bootcmd命令。

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

    debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
// 這里如果bootdelay大于0,并且中間沒有被中斷的話,執(zhí)行命令行參數(shù)
    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); //運(yùn)行啟動的命令行,例如 可以使用tftp命令
# else
  parse_string_outer(s, FLAG_PARSE_SEMICOLON |
        FLAG_EXIT_FROM_LOOP);
# endif

   到這里我們就可以看到是怎么調(diào)用設(shè)置的命令行參數(shù)的,在這里還要使用到bootm命令,先來看看bootm命令的實(shí)現(xiàn),在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函數(shù),在arm體系結(jié)構(gòu)中,就是在lib_arm/bootm.c文件中,可以從lib_arm/bootm.c文件中的59行看到do_bootm_linux()的定義。其中第64行聲明了這樣一個函數(shù)指針theKernel

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

   看看它的名字和參數(shù)的命名我們也可以猜到這個其實(shí)就是內(nèi)核的入口函數(shù)的指針了。幾個參數(shù)的命名也說明了下文提到的ARM Linux內(nèi)核啟動要求的第一條,因?yàn)楦鶕?jù)ACPS(ARM/Thumb Procedure Call Standard)的規(guī)定,這三個參數(shù)就是依次使用r0,r1和r2來傳遞的。接下來第73行就是給這個函數(shù)指針賦值: 

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

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

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 */
...............

}

最后是對內(nèi)核入口函數(shù)的調(diào)用,發(fā)生在第128行:

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

   調(diào)用的時候?qū)?shù)進(jìn)行賦值,r0=0,r1=bd->bi_arch_number,r2=bd-> bi_boot_params,一個都不少。至此U-Boot的使命完成,開始進(jìn)入ARM Linux的世界。
   要知道哪個地址是啟動內(nèi)核,哪個地址啟動文件系統(tǒng),要分析common/cmd_bootm.c中的函數(shù) do_bootm,因?yàn)橐龑?dǎo)kernel就是bootm這條命令的工作,do_bootm是命令bootm的執(zhí)行函數(shù)現(xiàn)在我們來分析一下common/cmd_bootm.c中的函數(shù)do_bootm,這是bootm命令的處理函數(shù).do_bootm()函數(shù)中的很多功能都是分成了函數(shù)的形式,而在以前的版本中沒有這么有結(jié)構(gòu)層次,這里我們也只是分析對引導(dǎo)Linux內(nèi)核有作用的部分,因?yàn)檫@是一個在common文件夾下的文件,也就意味著,在引導(dǎo)別的操作系統(tǒng)時也會用到這個函數(shù),而不單單是Linux操作系統(tǒng).

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 (i = 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結(jié)構(gòu)體中
        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); //加載操作系統(tǒng)的關(guān)鍵部分 確定使用的地址
    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) {//獨(dú)立的應(yīng)用程序
        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操作系統(tǒng)
    if (images.os.os == IH_OS_LINUX)
        fixup_silent_linux(); //該函數(shù)中處理bootarg參數(shù)
#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();
/*下面的函數(shù),繼續(xù)引導(dǎo)內(nèi)核的鏡像,復(fù)制image header 到全局變量header;
檢查header的魔數(shù),檢查數(shù),header和image中的這兩個。確定image的體系結(jié)構(gòu)和類型(KERNEL  or MULTI),關(guān)閉中斷,加載image到header中的加載地址*/

    boot_fn(0, argc, argv, &images); //調(diào)用do_bootm_linux()函數(shù)
    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()函數(shù)

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命令參數(shù)2指定的地址相同,則不需要復(fù)制,直接執(zhí)行
            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 (i != 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命令中參數(shù)2指定的地址不相同,則表示要從image header中指示的加載地址處把image data copy到bootm命令中參數(shù)2指定的地址處,然后再執(zhí)行。
   bootm命令是用來引導(dǎo)經(jīng)過u-boot的工具mkimage打包后的kernel image的。
mkimage的用法
   uboot源代碼的tools/目錄下有mkimage工具,這個工具可以用來制作不壓縮或者壓縮的多種可啟動映象文件。
   mkimage在制作映象文件的時候,是在原來的可執(zhí)行映象文件的前面加上一個0x40字節(jié)的頭,記錄參數(shù)所指定的信息,這樣uboot才能識別這個映象是針對哪個CPU體系結(jié)構(gòu)的,哪個OS的,哪種類型,加載內(nèi)存中的哪個位置, 入口點(diǎn)在內(nèi)存的那個位置以及映象名是什么?到這里整個U-Boot是如何啟動Linux內(nèi)核的,基本上也就清楚了,特別是如何向Linux內(nèi)核傳送的參數(shù)。
PS:下面是“ARM Linux Kernel Boot Requirements”,這篇文章中介紹的,引導(dǎo)Linux內(nèi)核啟動的必須要滿足的幾個條件:

* 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 for Angel.)
    * 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.





本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
U-boot在開發(fā)板上移植過程詳解
U-Boot在FS2410上的移植(轉(zhuǎn)載)
u-boot 分析 - <節(jié)選> [嵌入式Linux系統(tǒng)開發(fā)技術(shù)詳解-基于ARM]
u-boot源碼配置原理分析
u-boot-2010.03在tq6410上的移植詳解(二) - embedsky的專欄 ...
U-Boot編譯過程分析
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服