一个简单的重定位例子
重定位顾名思义就是重新分配代码和数据的物理位置,通俗来说就是把一块数据,代码由一个地方搬到另一个地方,通常是从ROM搬到RAM。为什么?因为ROM为只读存储器其写入速度很慢,读出速度很快,可以用来存放代码并执行,但并不适合用来存放数据。若想写程序中的变量,就需要在RAM中进行,因此进行搬运。

如上图,我举一个简单的例子来说明重定位的原理。我们希望把程序从0x0000-0x1000的ROM地址拷贝到RAM中进行运行,这个时候就需要重定位函数。其一般放在程序的开头如图所示,上电后从0x0000开始运行,执行重定位代码。

上面的代码为ARM程序重定位代码,比较困惑的就是_start和_end。怎么理解呢?可以把他们理解成链接标号,表示关键的地址位置。

如上这些标号是在连接脚本中定义的,然后在链接的时候赋给程序中的常量const_start和const_end。const_start和const_end也被叫做占位符,编译的时候并没有明确的值,连接后才有。 连接后,const_start的值为0x1000。const_end的值为0x1999,他们两个表示程序重定位的目标地址使用ldr加载。而当前程序的地址使用adr来获取。
重定位例子
上面只是举了一个简单的例子来说明重定位实际上的重定位要比这个复杂的多。
stm32的重定位
先介绍stm32的重定位,因为下面的我们没进行学习过。其实stm32的重定位又叫做分散加载。也就是它只重定位了.data数据而没有对其他程序段进行重定位。

在stm32术语体系里.data被称作RW section。如上图,stm32在上电后会把RW程序段拷贝到SRAM里,从而实现代码和常量数据运行在flash里而变量数据和堆栈在SDRAM里。重定位的代码位于__main里面,这个函数找不到源码,官方文档里说这里面完成了RW section的分散加载,以及ZI取的初始化。显然这些都离不开,链接脚本。lds,在STM32的语境下被称为SCT分散加载文件。
SCT文件规划了 Code、RO-data、RW-data 及 ZI-data 这些区域的地址,链接器根据这些地址将子程序组织在一块。SCT文件中主要包含描述加载域及执行域的部分,一个文件中可包含有多个加载域,而一个加载域可由多个部分的执行域组成。同等级的域之间使用花括号“{}”分隔开,最外层的是加载域,第二层“{}”内的是执行域。

如上图为一个默认生成的SCT文件,有一个加载域和两个执行域。第一个执行域里面将start.S里的Reset区域放在了起始位置0x80000000,Reset区域实际上就是向量表。对了讲的有点远了,其实根据上述SCT文件我们就可以确定源地址为:0x2000+0x800000000。目标地址为:0x200000000。拷贝尺寸为 :0x5000。
uboot的重定位
uboot是用来加载启动启动linux内核的,它包含镜像烧写,网络通信等功能,是一个比较大的裸机程序。
(1) 从NorFlah启动时,由于NorFlash写入速度慢,因此不适应变量的写入,因此需要将uboot代码从NorFlash重定位到SDRAM。

如上图所示,复位后CPU会将NorFlash映射为0x00000000并从这里启动。重定位代码根据连接脚本里的地址标识(_start,_end,_textbase)将代码从NorFlash搬运到SDRAM。代码如下:


(2)从NandFlash启动
如上代码,我们可以发现从NandFlash或NorFlash使用不同的代码进行拷贝,Nor_Flash可以直接访问,而Nand_Flash需要进行初始化以及使用指令进行内存读取。

上图展现了从NandFlash启动的过程,当从Nand Flash启动的时候CPU会将内部的Stepstone映射为0x00000000地址,并从这里开始启动。当然了这里是有一部分代码的,这些代码是复位后硬件从NandFlash自动加载过来的4KB程序,里面包含了重定位代码。