看完韦东山老师讲的代码重定位后依然感觉懵懵懂懂的,在这写下这篇博文也是为强制自己好好地将代码重定位的相关知识点梳理一便,如果有写的不对的地方希望读者能提出,灰常感谢。
百问科技开发的JZ2440支持NOR启动和NAND启动,程序烧到NOR FLASH时可以直接在NOR FLASH上运行,但是烧到NANDFLASH时却不能直接NNAD FLASH上运行,当烧到NAND FLASH时CPU会将NAND FLASH上前4K的代码复制到片内4K的SRAM中运行。那么问题来了,如果烧到NAND FLASH中的代码超过4K了怎么办呢?如果我们不加以处理的话相应的程序肯定是运行不了的;同时我们都知道NAND FLASH是可读可写的,NOR FLASH是不能直接写的。如果我们在程序中对全局变量进行更改了,那么NOR 启动的话相应的全局变量的不会被更改的(此时全局变量存放在NOR FLASH)。
我们如何才能让上面运行不了的程序正常运行呢?这就涉及到代码重定位了,在NAND启动时我们可以用超过4K的程序中的前4K代码将整个程序复制到SDRAM中去,也可以用前4K内容只复制超过4K部分的数据;在NOR启动时可以只将代码中的某个段重定位到SDRAM中执行(一个程序包含有程序段、代码段、只读数据段、数据段、bss段、注释段等),也可以将整个程序全复制到SDRAM中执行。在这里我们选择将整个程序全复制到SDRAM中执行。
代码重定位是通过链接脚本来实现的,简单链接脚本的编写如下。
关于链接脚本可以参考Using LD, the GNU linker。
SECTIONS{
. = 0x30000000; /* "."表示当前地址,将当前运行地址设为0x30000000 */
__code_start = .; /* 得到当前地址 */
. = ALIGN(4); /* 4字节对齐 */
.text : {*(.text)} /* 将所有文件的代码段都放在一个代码段中,(.段名 运行时的地址(可选) : AT(加载地址)(可选) {要将什么文件的什么段放进前面设置的段中}) */
. = ALIGN(4);
.rodata : {*(.rodata)} /* 只读数据段 */
. = ALIGN(4);
.data : {*(.data)} /* 数据段 */
. = ALIGN(4);
__bss_start = .; /* 得到bss段运行时的起始地址 */
.bss : {*(.bss) *(.COMMON)}
_end = .; /* 得到bss段运行时的结束地址 */
}
其中如果运行地址不等于加载地址时程序本身要进行重定位,显然经过我们的设置后程序运行时的地址必然是不等于加载地址的。可以用汇编或者C语言来对程序本身进行重定位,这里我们用C语言来实现。
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++;
}
}
因为初始化为0的全局变量和未初始化的全局变量是存放在bss段中的,但是bss段是不会保存在bin文件中的,所以我们得在程序运行时将bss段所在的空间清零,以下是清除bss段的代码。
void clean_bss(void)
{
/* 要从lds文件中获得 __bss_start,_end
* 然后把0写到__bss_start
*/
extern int __bss_start, _end; /* 从链接脚本中得到 */
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_end;
while (start <= end)
{
*start++ = 0;
}
}
上面两个C函数在启动文件start.S中调用,以下为截取的重要部分。
bl sdram_init /* 初始话sdram,方便后面使用 */
/* 重定位text, rodata, data段,整个程序 */
bl copy2sdram
/* 清除Bss段*/
bl clean_bss
//bl main /* 使用BL命令相对跳转,程序仍然在nor/sram上执行 */
ldr pc, =main /* 绝对跳转,跳到sdram */
halt: /* 死循环 */
bl halt
其中的sdram_init函数是我在上一篇博文S3C2440内存控制器与SDRAM中设置的。
【注意】
1、在程序中的重定位代码之前,不可以使用绝对跳转,不可以访问全局变量\静态变量,不可以访问有初始值的数组。
2、重定位之后,使用绝对跳转(ldr pc, =xxx)跳转到程序的运行地址。