都知道U-BOOT分为两个阶段,第一阶段是(~/cpu/arm920t/start.S中)在FLASH上运行(通常状况下),完成对硬件的初始化,包括看门狗,中断缓存等,而且负责把代码搬移到SDRAM中(在搬移的时候检查自身代码是否在SDRAM中),而后完成C程序运行所须要环境的创建,包括堆栈的初始化等,最后执行一句跳转指令:linux
ldr pc, _start_armboot编程
_start_armboot: .word start_armboot,缓存
进入到/lib_arm/board.c中的函数void start_armboot (void),今后就进入了第二阶段。这是在不少资料上都有讲述的,因此勿需多言了。ide
如今对于第一阶段有几个问题,之前我一直是没有搞明白的,既然在FLASH中的代码是把本身拷贝到SDRAM中,那么在S3C2410的内存地址空间,就有两份的启动代码,第一份就是在FLASH中,第二份就是在SDRAM中。根据连接脚本文件(~/board/smdk2410/u-boot.lds)函数
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; /* 后记:这个连接起始地址实际上被-Ttest $(TEST_BASE)更新了*/翻译
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}debug
. = ALIGN(4);
.rodata : { *(.rodata) }指针
. = ALIGN(4);
.data : { *(.data) }调试
. = ALIGN(4);
.got : { *(.got) }code
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
其中的连接命令 . = 0x00000000;表示地址计数器从0地址开始计数,并且_start 是程序代码段的入口,那么*.text中的全部地址标号(cpu/arm920t/start.S中定义的)就应该从0地址开始计数,那么标号start_armboot(就是void start_armboot (void)函数的入口地址)应该在FLASH中才对啊,因此按照上边的分析,
ldr pc, _start_armboot
_start_armboot: .word start_armboot
此条语句后,并无跳转到SDRAM中的void start_armboot (void),而是跳转到了FLASH中的void start_armboot (void)中。
因此就出现了这样的矛盾,在FLASH中有一段代码把本身拷贝到SDRAM中,产生了两份UBOOT可执行的指令流,可是最后却没有跳转到SDRAM中去运行以提升指令执行的速度。
产生以上的认识是基于如下几个认识(确定是错误的):
1.*.text中的全部地址标号(在连接时肯定)是从0地址开始生成的。
实际上在arm-linux-ld 执行时,原来定义的0x0地址被更新为TEXT_BASE定义的地址。
2.relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
若是不是出于调试阶段,这段搬移代码中的r0和r1确定不相等的,r0=#0,r1=#TEXT_BASE: 0x33F80000(在./board/smdk2410/config.mk中),因此执行代码的自身拷贝与搬移。
注意:在GNU中:adr r0, _start 做用是得到 _start 的实际运行所在的地址值,而ldr r1, _TEXT_BASE 为得到地址_TEXT_BASE中所存放的数据,其中adr r0, _start翻译成 add r0,(PC+#offset),offset 就是 adr r0, _start 指令到_start 的偏移量,在连接时肯定,这个偏移量是地址无关的。而 ldr r1, _TEXT_BASE 指令表示以程序相对偏移的方式加载数据,是索引偏移加载的另一种形式,等同于ldr r1,[PC+#offset],offset 是 ldr r1, _TEXT_BASE 到 _TEXT_BASE 的偏移量。注意这种用法并非伪指令,伪指令的特征是 ldr r1, =expr/lable_expr。对于LDR伪指令,ADS的状况有些不同(细微差异),在ADS中的状况能够参考杜春雷<ARM体系结构与编程>144页。
比较一下:
add r0,(PC+#offset):(PC+#offset)是相对地址,表示把本指令上溯或下溯offset处的地址加载到 r0;
ldr r1,[PC+#offset]:[PC+#offset]也是相对地址,表示把偏移offset处的地址上的数据加载到 r1;
如今继续:
刚才分析所获得的矛盾,确定是在认识上存在的误差,通过把U-BOOT进行make后,从所生成的两个.map文件来看(~/u-boot.map和Systen.map),全部的地址标号都是从0x33f80000开始的,就是从SDRAM的高地址开始,等于TEXT_BASE的值,也就是说,连接器是从0x33F80000开始来连接所编译生成的目标文件的,而不是从0地址开始,通过查看,start_armboot=0x33f80d9c,就是说void start_armboot (void)函数的入口地址在SDRAM中(连接器决定),因此执行
ldr pc, _start_armboot
_start_armboot: .word start_armboot,
PC指针确定就指向了SDRAM中,换句话就是说进入到SDRAM中了,对于ldr pc, _start_armboot,其仍然是GNU中使用程序相对偏移的方式加载数据,翻译一下就是ldr pc, [pc+pc到_start_armboot的偏移值],结果就把_start_armboot地址中的数start_armboot放入pc中完成了跳转,而 start_armboot 的值(函数地址)是在连接时就肯定了,是相对于 TEXT_BASE 的。由于在整个UBOOT的阶段1中全部的寻址都是相对位置的寻址(虽然连接器认为是阶段1的代码是从地址0x3ff80000中开始连接的),把阶段1的代码放在0地址开始的FLASH中也是能够正确的运行的,若是ARM的复位向量是在0x00000001(假设),那么把代码烧写到从0x00000004处开始的地方,上电时也能够正确的运行(假设ARM的复位向量是在0x00000004成立),固然ARM的复位向量不在这里,只是以此假设来讲明以上的对于阶段1的分析。
如今最后一个矛盾就是连接脚本(~/board/smdk2410/u-boot.lds)所描述的连接地址与实际的连接地址不相同的问题,由于根据连接脚本,全部的地址标号应该从0地址开始计数的,然而不是。通过查找Makefile文件,在顶层的Makefile文件中,在166行中连接是的连接命令:
$(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \,
其中的LDFLAGS在定义在顶层的config.mk中的145行:LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS),
最关键的就是 -Ttext $(TEXT_BASE)命令了,他的含义就是说,起始地址在TEXT_BASE,而TEXT_BASE在~/board/smdk2410/config.mk中TEXT_BASE = 0x3FF80000;
到此就弄清楚为何连接从0x3ff80000开始的了,至于连接脚本,其主要做用是用来指明各个*.o文件的顺序,如入口地址标号(_start)等,以及使两个地址标号获得当前的地址
__u_boot_cmd_start = .; *.u_boot_cmd段的起始地址
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; *.u_boot_cmd段的结束地址
以供C程序使用。 __u_boot_cmd_start和__u_boot_cmd_end能够做为全局的一个常数使用。
总结:
由于-Ttext $(TEXT_BASE)命令的使用,连接器把UBOOT从地址0x3ff80000开始链接,在第一阶段中,全部使用的目标地址寻址都是使用当前PC值加减偏移量的方法,因此把UBOOT烧写到0地址开始的FLASH中,不影响第一阶段的正确执行。