嵌入式Linux学习:重定位(Relocation)

引用了http://blog.csdn.net/zhaocj/article/details/6636175博文的图片

引用了《嵌入式Linux应用开发完全手册》P260的图片

可以参考:http://blog.csdn.net/skyflying2012/article/details/37660265;

这个链接里包含了一个简单的实例!


笔者也是嵌入式新手,以下博文只是我对uboot初浅的认识,有任何问题欢迎指正

--------------------------------------------正文开始----------------------------------------

什么是重定位?


上面解释了两个概念:加载地址和运行地址,这里在简单总结一下,就是程序员将所有的代码编译完以后按照一定的顺序进行存放到FLASH里,而且在存放的过程中设置了连接地址,这个连接地址就是后期我们希望这个代码能够运行的地址(而不是SRAM或者在FLASH中XIP,一般就在SDRAM中),上电完成后程序一般运行在FLASH或者SRAM,然后通过适当的复制程序将原本在FLASH中的数据复制到上述指定的连接地址,然后再跳到SDRAM中继续运行。这个是旧版本的uboot中的策略。


这样的策略最终让(一般连接地址就是SDRAM的0x33F80000)uboot处于SDRAM的中低端空间,再在其0x34000000后面放置kernal的代码;(这样的策略直接简单,一刀切的进行了SDRAM的空间划分,使得SDRAM的利用率偏低);


为了最尽可能的利用SDRAM资源,在上电完成后,运行在FALSH或者SRAM中的程序将整个uboot程序复制到了SDRAM的顶端开始,依次开始对SDARM进行空间划分。这样的结果就是uboot放置在SDRAM中的实际位置和连接地址会存在一个偏差,这种偏差会导致程序跑飞!

嵌入式Linux学习:重定位(Relocation)_第1张图片

图 1 两种策略的在uboot进入第二阶段时的SDRAM分布

从上面的图可以看到这两种策略所引起的不同,左图的uboot起始位置确定为0x33F80000,而右图的uboot的起始位置实际上是不确定的,因为uboot的.text和.bss段实际的大小是在连接时才最终确定,所以这里的连接地址的意义已经名存实亡,因为它和最终的运行地址已经存在一个offset;因为存在这么一个offset,所以uboot的程序需要重定位!

嵌入式Linux学习:重定位(Relocation)_第2张图片

图 2 策略1将uboot数据复制到了SDRAM的TEXT_BASE后,两者执行的转跳图

先看看策略1,加入现在已经完成了程序的复制工作(即将FLASH中相应的数据复制到了指定的TEXT_BASE位置),如果这个时候程序若还在FLASH(或SRAM)中运行:执行ldr pc,=lable,实际上就是读取offset处(FLASH中的offset)的数据,将这个数据赋值给pc,见上图左虚线部分就是取读取offset处的数据,但是这个地址实际上是指向SDRAM中的lable(并不是指向FLASH中的lable,连接地址的作用),所以这个时候就完成了从FLASH(或者SRAM)到SDRAM的转跳;

嵌入式Linux学习:重定位(Relocation)_第3张图片

图 3 跳到SDRAM后的运行情况

当完成FLASH到SDRAM的转跳后,程序已经运行在了SDRAM,那么后续的情况会如何呢,如此时要执行ldr pc,=lable2,实际上就是读取offset2处(SDRAM中的offset2)的数据,将这个数据赋值给pc,见上图右虚线部分就是取读取offset2处的数据,这个地址实际上仍指向SDRAM中的lable2(并不是指向FLASH中的lable2,连接地址的作用),所以这个时候程序正常转跳,且还在SDRAM中运行;


通过上面的解释,我们就明白了策略1的连接地址和后续需要复制的SDRAM的偏移地址就应该是一样的;那么如果此时将uboot复制到另外一个地方,情况会怎样?

嵌入式Linux学习:重定位(Relocation)_第4张图片

图 4简单的将uboot复制到SDRAM2中,而不是复制到相应的SDRAM1(TEXT_BASE所指向)


有上图可知,CPU在FLASH中运行时,执行ldr pc,=lable,实际上就是读取offset处(FLASH中的offset)的数据,将这个数据赋值给pc,见上图左虚线部分就是取读取offset处的数据,程序按照我们的预设跳到了SDRAM1(TEXT_BASE所指向)后面的某处,但是这个地址上并没有数据(因为我们并没有把数据复制到这个地方!),此时程序跑飞!


而如果不经过重定位,我们强行将程序运行在SDRAM2上会如何?

嵌入式Linux学习:重定位(Relocation)_第5张图片

图 5 若强行运行结果会怎么样?

同样的,才是CPU运行在SDRAM2中,执行ldr pc,=lable2,实际上就是读取offset2处(SDRAM2中的offset2)的数据,将这个数据赋值给pc,见上图右虚线部分就是取读取offset2处的数据,这个时候程序仍旧是转跳到了SDRAM1中,程序仍然跑飞!


解决办法就是重定位!


通过上面的实例我们知道了,实际上如果此时强行将CPU指针指向SDRAM2,那么为了能够正常的执行我们的程序,我们需要做的就是修改SDRAM2中offset2处所保留的数据,因为这个数据保留了接下来CPU要转跳的地址,那么靠的就是rel_dyn段!上面的图片中的FLASH都有rel_dyn段,但是他们都没有被复制到SDRAM,那么他们的作用如何实现,接下来仍旧用图片来解决问题;

嵌入式Linux学习:重定位(Relocation)_第6张图片

图 6 重定位的过程

首先在FLASH的,有一个数据段叫做rel_dyn,其中保存了很多地址,这些地址都指向了SDRAM1中的虚拟offset(为什么叫虚拟呢,因为我们uboot数据被复制到了SDRAM2中,SDRAM1中相应地方的数据为空),而虚拟offset中的数据就是虚拟lable的地址;我们再来重新解释下上面没有重定位的SDRAM2为什么运行失败:那是因为我们SDRAM2中的offset2中保留的数据,实际上指向了SDRAM1中的虚拟lable!


继续讲重定位;


假如,我们获得了REL_OFFSET,就是SDRAM2余SDRAM1的偏移;rel_dyn中有一个数据RD1,把这个数据作为地址,它指向的恰好就是SDRAM1的虚拟offset2(上图棕色实线),那么只要将rel_dyn加上REL_OFFSET,那么RD1作为地址就可以指向SDRAM2中的offset2(上图棕色虚线);


这样我们就获取到了SDRAM2中的offset2,它里面所包含的数据作为地址,实际上指向了SDRAM1中的虚拟lable2(上图黑色实线),那么只要将SDRAM2中的offset2里的数据也加上REL_OFFSET就可以指向正确的SDRAM2中的lable2(上图黑色虚线),这样即使CPU在SDRAM2中也就能够正常运行了,而不会跑飞;


那么关键就是修改SDRAM2中offset2中的数据(修改还分两种,绝对和相对,这里暂不展开),而这个恰恰就是重定位所需要完成的任务;而如何获得offset2的地址,靠的就是rel_dyn中所保留的原始数据!而rel_dyn中所保留的数据是由gcc的某个编译选项获得



你可能感兴趣的:(嵌入式Linux--uboot)