然而,问题是在内存未初始化之前,系统是在FLASH设备中运行的汇编,C程序不能运行(原因是C语言函数的栈操作不能在FLASH中进行,FLASH的写操作需要首先擦除再写入)。要想运行C程序就需要找到一块在内存初始化之前就可用的其它内存,这就是系统的数据d-cache缓存了。cache是一块高速内存,其与内存具有相同的属性,不同点在于cache中的内容有可能被换出到内存中,MIPS处理器有指令可以锁定cache,防止cache中内容被换出,所以可当做一块ram使用。
cache的size一般都是非常小的,好在C语言实现的ram检查以及动态配置程序不大,看一下在uboot中整个过程的实现。
前提条件
由于内存初始化程序是单独编译成一个可执行的C程序,在uboot中这部分程序称为boot1_5,需要在编译过程中将其嵌入到uboot整体镜像中。为了方便嵌入,将Uboot分成3个单独的程序:boot1、boot1_5和boot2。boot1为加电之后首先运行的汇编程序,它会加载boot1_5的C程序初始化内存;完成后,返回到boot1继续执行以及加载boot2执行。
以上流程如能正常执行,需满足以下条件:
1)boot1需要知道boot1_5程序的执行入口地址(BOOT1_5_ENTRY)和数据段起始地址BOOT1_5_FDATA。
2)boot1需要把boot1_5的数据段从flash中拷贝到d-cache中。
3)boot1要告知boot1_5在内存初始化完成之后,如何调回boot1程序中。
编译过程
-DBOOT1_5_ENTRY=0xdeadbeef -DBOOT1_5_FDATA=0xdeadbeef
因为此时boot1_5程序还没有编译,不可能知道它的入口和数据段地址,首次编译时为boot1指定一个伪地址(0xdeadbeef)。在boot1编译完成链接的时候,为ld程序指定链接脚本ld.script:
$(LD) -Tld.script $(OBJS) -o boot1.first
在此链接脚本中,自定义一个.boot1_5的段,在其编译完成后,将其链接在boot1的这个位置,如下:
__boot1_5_start = . ;
.boot1_5 : {
*(.boot1_5)
}
. = ALIGN(8) ;
__boot1_5_end = . ;
接下来编译boot1_5,其使用上一步确定下来的__boot1_5_start地址作为自身的数据段的起始地址(入口地址),例如__boot1_5_start=0x9fc04800。在boot1_5编译时,指定宏变量-DLOADADDR=__boot1_5_start,其链接文件的头如下,使用LOADADDR为起始地址:
SECTIONS
{
. = LOADADDR;
_loadaddr = . ;
.text : {
_ftext = . ;
}
}
编译完成后,可得到boot1的真实的入口起始地址和数据段起始地址,加入分别为0x9fc04804与0x9fc1b920:
$(NM) boot1_5 | grep -w _start |cut -f1 -d' '
$(NM) boot1_5 | grep -w _fdata |cut -f1 -d' '
使用objcopy工具将boot1_5转为二进制文件,之后使用ld在次链接boot1_5。这样做的目的主要是将整个boot1_5程序链接到一个.boot1_5的段内,在boot1的链接文件中已经为此预留了位置,当第二次编译boot1之时,将二者链接成一个文件:
objcopy -S -O binary -R .note -R .comment boot1_5 boot1_5_bin
@echo "OUTPUT_ARCH(mips) SECTIONS { .boot1_5 : { *(.data) } }" > ld.script.boot1_5
$(LD) -T ld.script.boot1_5 -b binary -o $@ boot1_5_bin
其次编译boot2完成后,再次编译boot1,此时就不需要伪地址0xdeadbeef了,替换为真实地址在此编译:
-DBOOT1_5_ENTRY=0x9fc04804 -DBOOT1_5_FDATA=0x9fc1b920
执行流程
在程序执行时,boot1完成锁定d-cache和拷贝boot1_5数据段的工作。另外boot1会在处理器中找到一个闲置的寄存器,把返回地址写入此寄存器,boot1_5就可根据此地址跳转回boot1继续执行。
完。