前面一段時間一直在移植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是什么?它有什么作用?其實它就是系統(tǒng)上電后運行的和小段程序。
1 BootLoader的概念
在系統(tǒng)上電后,需要一段程序來進行初始化:關(guān)閉WATCHDOG,改變系統(tǒng)時鐘,初始化存儲控制器,將更多的代碼復(fù)制到內(nèi)存中。并將操作系統(tǒng)內(nèi)核復(fù)制到內(nèi)
存中運行,這就段程序代碼就叫做Bootloader。沒有一個Bootloader完全支持所有CPU,所以我們要想使用Bootloaser一般情況
下要自己進行修改,我們可以增強Bootloader的功能,讓它具有網(wǎng)絡(luò)功能,可以通過NFS遠程下載Linux內(nèi)核和根文件系統(tǒng),可以燒寫Linux
內(nèi)核和根文件系統(tǒng)到NandFlash中,而這些功能對于最終的用戶來說是沒有什么意義的,它們看到的只是Bootloader引導(dǎo)Linux內(nèi)核啟動這
一個功能,而其余的功能只對開發(fā)人員很有用處。也就是說在開發(fā)期間這些功能是必不可少的。 (1)啟動加載模式:這種模式也稱為“自主”模式。也就是Bootloader從目標機上的某個固態(tài)存儲設(shè)備上將操作系統(tǒng)加載到RAM中運行,整個過程并沒有用戶的介入,這種模式是在嵌入式產(chǎn)品發(fā)布里的通用模式。 (2)
下載模式:在這種模式下,目標機上的Bootloader將通過串口連接或網(wǎng)絡(luò)連接等通信手段從主機下載文件,例如:下載內(nèi)核映像和根文件系統(tǒng)映像等。從
主機下載的文件
通常首先被Bootloader保存到目標機的RAM中,然后再被Bootloader寫到目標上的Flash類的固態(tài)存儲設(shè)備中,Bootloader
的這種模式是在在開發(fā)時使用的工作于這種模式的Bootloader通常都 會向它的終端用戶提供一個簡單的命令行接口。 在嵌入式Linux系統(tǒng)中從軟件的角度通??梢苑譃?個層次: (1)引導(dǎo)加載程序,包括固化在固件中的boot代碼(可選)和Bootloader兩大部分。 有些CPU在運行Bootloader之前運行一段固化的程序 ,比如x86結(jié)構(gòu)的CPU就是先運行BIOS中的固件,然后才運行硬盤的第一個分區(qū)中的BootLoader。在大多數(shù)的嵌入式系統(tǒng)中并沒有固件,Bootloader是上電后第一個執(zhí)行的程序。 (2)Linux內(nèi)核 嵌入式定制的內(nèi)核以及啟動參數(shù),啟動參數(shù)可以是Bootloader傳遞給內(nèi)核的,也可以是內(nèi)核默認的。 (3)文件系統(tǒng) 包括根文件系統(tǒng)和建立于Flash內(nèi)存設(shè)備之上的文件系統(tǒng)。里面包括了Linux系統(tǒng)能夠運行所必要的應(yīng)用程序和庫文件等。比如可以給用戶提供操作Linux的控制shell程序。 (4)用戶應(yīng)用程序 特定于用戶的應(yīng)用程序,它們也存儲在文件系統(tǒng)中,有時在用戶應(yīng)用程序和內(nèi)核層之間可以還會包括一個嵌入式圖形用戶界面。 2. Bootloader啟動的兩個階段 從固態(tài)存儲設(shè)備上啟動的Bootloader大多都是兩階段的啟動過程,第一階段使用匯編來實現(xiàn)。它完成一些依賴于CPU體系結(jié)構(gòu)的初始化,并調(diào)用第二階段的代碼,第二階段則通常使用C語言來實現(xiàn),這樣可以實現(xiàn)更復(fù)雜的功能,而且代碼會有更好的可讀性和可移植性。 (1) Bootloader第一階段的功能 1)硬件設(shè)備初始化 2)為加載Bootloader的第二階段準備RAM空間。 3)復(fù)制Bootloader的第二階段代碼到RAM空間中。 4)設(shè)置好棧 5)跳轉(zhuǎn)到第二階段代碼的C入口點。 在第一階段進行的硬件初始化一般包括:關(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)核存放在適當?shù)奈恢煤?,直接跳到它的入口點即可調(diào)用內(nèi)核,調(diào)用內(nèi)核之前,下列條件要滿足 (1)CPU寄存器的設(shè)置 R0=0. R1=機器類型ID;對于ARM結(jié)構(gòu)的CPU,其機器類型ID在linux/arch/arm/tools/mach-types R2=啟動參數(shù)標記列表在RAM中起始基地址 (2)CPU工作模式 必須禁止中斷(IRQs和FIQs) CPU必須為SVC模式 (3)Cach和MMU的設(shè)置 MMU必須關(guān)閉 指令Cach可以打開也可以關(guān)閉 數(shù)據(jù)Cach必須關(guān)閉
這
一篇主要就是U-Boot的config.mk進行了分析。如果要使用開發(fā)板board/<board_name>,就先執(zhí)行
“make<board_name>_config”命令進行配置,然后執(zhí)行”make all“,就可以生成 如下3個文件。 U-boot.bin:二進制可執(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中運行,啟動后可以看到串口輸出一些信息后進行控制界面。
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”實際上就是執(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 //判斷源代碼目錄和目標文件目錄是否是一樣 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分支中,進入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 為空,這個命令實際上等同于"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> 總之,當你執(zhí)行make smdk2410_config ,實際的作用就是執(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時,其實執(zhí)行的是如下的命令:
|
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0
| 最后一句話include $(TOPDIR)/config.mk 包含頂層目錄的config.mk文件。它根據(jù)上面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”.所以最終結(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中的每個成員,都將進入cpu/$(CPU)目錄編譯它們,現(xiàn)在的OBJS為cpu/arm920t/start.o。它由cpu
/arm920t/start.S編譯得到。對于LIBS中的每個成員,都將進入相應(yīng)的子目錄執(zhí)行"make命令"。當所有的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)換為二進制格式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中, 總結(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 連接腳本進行連接。 (4)第3步得到的是ELF格式的U-Boot,后面MAKEFILE還會將它轉(zhuǎn)換為二進制格式 S-Record格式。
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的第二階段代碼準備RAM空間。 所謂準備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 //這里相當于定義一個全局的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 //在計算出來的存放地址加上#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中實現(xiàn)
|
relocate: /* 將U-Boot復(fù)制到RAM中 */ adr r0, _start /* r0:當前代碼的開始地址 */ 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在前面定義,是第一條指令的運行地址*/ 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)存以實現(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入口點 在跳轉(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ù)的運行環(huán)境已經(jīng)完全準備好,通過如下命令直接跳轉(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)核之前可以讓用戶決定是否進入下載模式,即進入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中實現(xiàn)。串口的初始化函數(shù)主
要是serial_init,它設(shè)置UART控制器,是CPU相關(guān)的函數(shù),在cpu/arm920t/s3c24x0/serial.c中實現(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ù),進行一個無限循環(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也是通過標記列表向內(nèi)核傳遞參數(shù)的,一般而言,設(shè)置這以下兩個標記就可以了,在配置文件
include/configs/smdk2410.h中,增加如下兩個配置項即可:
|
#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è)置標記列表,最后通過“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è)置
的機器類型ID,而bd->bi_boot_params就是標記列表的地址
在上一篇中分析到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秒中,串口上沒有輸入,則會進行自動的引導(dǎo)Linux內(nèi)核。也就是執(zhí)行bootcmd命令。
|
#ifdef CONFIG_BOOTCOUNT_LIMIT //啟動次數(shù)的限制功能,如果到達一定次數(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); //運行啟動的命令行,例如 可以使用tftp命令 # else parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); # endif
| 到這里我們就可以看到是怎么調(diào)用設(shè)置的命令行參數(shù)的,在這里還要使用到bootm命令,先來看看bootm命令的實現(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ù)的命名我們也可以猜到這個其實就是內(nèi)核的入口函數(shù)的指針了。幾個參數(shù)的命名也說明了下文提到的ARM
Linux內(nèi)核啟動要求的第一條,因為根據(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)核的入口點(Entry
Point)。知道了images->ep的意義之后,給theKernel賦這個值也就是理所當然的了。 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ù)進行賦值,r0=0,r1=bd->bi_arch_number,r2=bd-> bi_boot_params,一個都不少。至此U-Boot的使命完成,開始進入ARM Linux的世界。
要知道哪個地址是啟動內(nèi)核,哪個地址啟動文件系統(tǒng),要分析common/cmd_bootm.c中的函數(shù)
do_bootm,因為引導(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)核有作用的部分,因為這是一個在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) {//獨立的應(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)存中的哪個位置,
入口點在內(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.
|
|