以下所写的内容都是基于韦东山JZ2440开发板的,更多内容可参考韦东山第一期ARM裸机视频代码重定位章节或韦东山S3c2440代码重定位详解学习。
代码重定位就是将代码的text段、rodata段、data段等从一个地址搬移到另外一个地址。本来程序烧写好之后是在某一个地址开始运行的,经过代码重定位之后,可以在另外一个地址运行。例如:程序烧写在Nor Flash上,本来是在Nor Flash上的0地址开始运行的,通过代码重定位,把数据段或整个程序搬移到SDRAM的0x30000000地址运行。
1、程序烧写在Nand Flash上的情况
(1)Nand Flash程序的启动流程:当我程序烧写在Nand Flash的时候,CPU是无法从Nand Flash中取代码执行的,开机上电的时候Nand启动硬件会自动把Nand Flash前4K复制到CPU的内部SRAM里面,然后CPU从内部SRAM的0地址开心执行代码。
(2)存在问题:如果当我们的程序大于4K的时候,内部SRAM就不够存放Nand Flash的程序了,因为SRAM的大小只有4K。
(3)解决程序大于4K的方法:在程序设计时,设计程序的前4K代码实现将整个程序读出来放到外部的SDRAM。
2、程序烧写在Nor Flash上的情况
(1)Nor Flash程序的启动流程:Nor Flash的基地址为0,片内RAM地址为0x4000 0000;CPU读出Nor上第1个指令(前4字节),执行;CPU继续读出其它指令执行。
(2)存在问题:Nor Flash可以像内存直接读,但是不能直接写。当程序中含有需要写的全局变量或静态变量时,假如是在Nand Flash可以正常操作,如果是在Nor Flash,修改是无效。
(3)解决写无效的方法:我们把全局变量和静态变量这些数据段或者整个程序重定位放到外部SDRAM上,在SDRAM上修改全局变量和静态变量的值。
1、一个程序里面一般有的段
(1)text:代码段,代码段其实就是函数编译后生成的东西。
(2)data:数据段
(3)rodata:只读数据段(const全局变量)
(4)bss段:又叫做ZI段,零初始化段,就是初始化为0的全局变量
2、代码重定位实现思路
在汇编启动代码start.S中,初始化执行完sdram后,开始进行代码段、数据到、只读数据段等拷贝,拷贝完成之后清除bss段、完成bss段的清除之后再(ldr pc, =main)绝对跳转,跳到SDRAM的main函数开始执行。
3、实现代码重定位的C语言方法
(1)编译链接脚本
SECTIONS
{
. = 0x30000000; /* 外部SDRAM的起始地址 */
__code_start = .; /* 代码起始地址等于外部SDRAM的起始地址0x30000000 */
. = ALIGN(4); /* 4字节对齐,因为进行代码重定位时时以4字节为单位进行搬移的 */
.text : /* 所有text代码段 */
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) } /* 所有rodata 只读数据段 */
. = ALIGN(4);
.data : { *(.data) } /* 所有data 数据段 */
. = ALIGN(4);
__bss_start = .; /* bss段的起始地址,也是data段的结束地址,因为这里是连续的 */
.bss : { *(.bss) *(.COMMON) }/* 所有bss段 */
_end = .; /* bss段结束地址 */
}
(2)拷贝代码段到SDRAM C语言程序
/**************************************************************
函数名称:copy2sdram
函数功能:拷贝norflash/nandflash的
text段、rodata段、data段等整个代码到sdram
输入参数:无
返 回 值:无
备 注:无
**************************************************************/
void copy2sdram(void)
{
/* 要从lds文件中获得 __code_start, __bss_start
* 然后从0地址把数据复制到__code_start
*/
extern int __code_start, __bss_start; /* 声明外部变量 */
volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
volatile unsigned int *src = (volatile unsigned int *)0;
while(dest < end)
{
*dest++ = *src++;
}
}
(3)清除bss段 C语言程序
/**************************************************************
函数名称:clean_bss
函数功能:清除bss段
输入参数:无
返 回 值:无
备 注:无
**************************************************************/
void clean_bss(void)
{
/* 要从lds文件中获得 __bss_start, _end */
extern int _end, __bss_start;
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_end;
while(start <= end)
{
*start++ = 0;
}
}
4、代码重定位实现的汇编的方法
(1)链接脚本
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
(2)汇编代码
bl sdram_init
/* 重定位text段、rodata段、data段整个程序 */
mov r0, #0 /* 对于norflash地址就是0开始,对于nandflash这里假设代码小于4K,因此是拷贝到sdram的时候也是从0地址开始 */
ldr r2, =_start /* _start运行时的地址 */
ldr r3, =__bss_start /* __bss_start,由连接脚本可知bss段的起始地址就是data段的结束地址 */
cpy:
ldr r4, [r1] /* 从r1读到r4 */
str r4, [r2] /* r4存放到r2 */
add r1, r1, #4 /* r1+1,地址加1 */
add r2, r2, #4 /* r2+1,地址加1 */
cmp r2, r3 /* r2,r3地址比较 */
ble cpy /* 如果不等则继续拷贝,地址相等说明数据拷贝完成了 */
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
5、为什么要清除bss段?
这里的bss段清除起始只的就是将bss段初始化为0,bss段是不会出现在程序下载文件(*.bin *.hex)中的,因为全都是0。如果把它们出现在程序下载文件中,会增加程序下载文件的大小。实际应用中,通常只需要把bss段的起始地址和结束地址保存起来,而不需要将程序下载文件中出现bss段(一堆0)将来真正运行程序的时候,再将bss段初始化为0即可,也就是清除bss段,如果不清除,那么全局变量的初始值就不是0了。
6、为什么要用绝对跳转到main函数执行?
这里的绝对跳转用的指令为:ldr pc, =main,执行该指令之后,程序是跳转到SDRAM的main函数开始执行,如果用相对跳转(bl main),程序仍会在NOR Flash或内部SRAM执行。
1、韦东山S3c2440代码重定位详解:https://blog.csdn.net/thisway_diy/article/details/79397066
2、博客园shimejing,bss段为什么要初始化,清除:https://www.cnblogs.com/0822vaj/p/3605987.html
3、从零开始的理解代码重定位:https://blog.csdn.net/dhauwd/article/details/78566668