本文是基于韦东山视频的学习笔记
引入
为什么要有重定位,什么是重定位?
我们知道,CPU可以从一些特定的芯片直接读写数据如SDRAM,Nor和网卡,但是不同于Nor,Nand是不可以直接和CPU交互滴。那既然这样,当程序烧写到Nand时,CPU是怎么运行程序的呢。
其实啊,Nand启动时,Nand的前4k内存都会被复制到SRAM当中(通过硬件),CPU又是可以直接和SRAM读写数据的,那就理所当然可以运行程序了。
那么问题来了,如果程序大于4K呢?
对于这样的情况,我们可以运行程序全部复制到SDRAM,这就是重定位,重定位简单理解就是,把程序的地址重新定位。
这是一种运用到重定位的情况,还有一种和Nor启动有关。由于防止运行程序轻易地被破坏,Nor在一般情况下都是只读,不能被写入。我们知道局部变量是放在SRAM中的,但是全局变量是烧写在bin文件,也就是会烧进Nor flash中的,当需要修改这些变量,就需要把他重定位了:把全局变量放到SDRAM。
段
再讲重定位之前,先说一下段。一般我们链接的程序会有四个“段”。
- text 程序段
- data 数据段,放全局变量的,局部变量放在栈里面的
- rodata 只读数据段,放const 全局变量的
- bss 这个段是存放初始值为零和没有定义初始值的全局变量
- comment 注释段
链接脚本
先说Nor启动,上文说到要修改全局变量,就要把全局变量放到SDRAM中,而我们知道,SDRAM的地址是0x3000 0000,而程序又是从零地址开始(Nor启动)。也就是说,如果把全局变量放进去,程序代码会从零地址一直写到0x3000 0000(十进制805,306,368)地址,那bin文件就有800多m了!
没法玩啊。其实有两种方法解决
- 要么把数据段顺着程序段放进 bin 文件,要到运行的时候再把数据段重定位到地址为0x3000 0000 的 SDRAM。
- 要么把程序段数据段等等的都放进0x3000 0000的地址,再把全部把0x3000 0000放进 bin 文件,运行的时候,把全部程序都重定位到SDRAM 去运行。
由于单纯靠Makefile的简单链接指令已经满足不了重定位的需求了,所以必须引入链接脚本。
先来试试第一个解决方法,在main.c 定义一个全局变量 g_char = ‘A’,在链接脚本这样写:
SECTIONS {
.text 0: {*(.text)} //指定代码段从零地址开始
.rodata : {*(.rodata)} //指定只读段从 原rodata的地址 开始,即不变
.data 0x30000000: AT(0x800) {*(.data)} //意思是程序会从0x3000 0000运行,但是程序会放在0x800的地方,等下我们要手动把0x800的程序复制到0x3000 0000去(如果不指定为0x800,会默认从0x2xxx开始,bin文件就会很大)
.bss : {*(.bss) *(.COMMET)} //不变
}
start.S 这样写
/* 重定位
* 把数据段0x800的内容复制到0x30000000
*/
mov r0, #0x800
ldrb r1, [r0]
mov r0, #0x30000000
strb r1, [r0]
但是这样写,完全不具有普遍性,都需要指定地址,所以换一种写法:
SECTIONS {
.text 0: {*(.text)}
.rodata : {*(.rodata)}
.data 0x30000000: AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = .;
*(.data)
data_end = .;
}
.bss : {*(.bss) *(.COMMET)}
}
bl sdram_init //要用到sdram,记得初始化
/* 重定位 */
ldr r1, =data_load_addr //本例子是0x30000000
ldr r2, =data_start //本例子是0x800
ldr r3, =data_end //本例子是0x800+data
copy:
ldrb r0, [r1]
strb r0, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne copy