1.代码重定位的改进
用ldr、str代替ldrb, strb加快代码重定位的速度。
前面重定位时,我们使用的是ldrb命令从的Nor Flash读取1字节数据,再用strb命令将1字节数据写到SDRAM里面。
我们2440开发板的Nor Flash是16位,SDRAM是32位。 假设现在需要复制16byte数据。
不同的读写指令 | cpu读取nor的次数 | cpu写入sdram的次数 |
---|---|---|
ldrb、strb | 16 | 16 |
ldr、str | 8 | 4 |
可以看出我们更换读写指令后读写次数变少了,提升了cpu的访问效率。
修改后的start.s代码如下图所示,这里我只简单的列出了重定位的实现:
...
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4 //r1加4
add r2, r2, #4 //r2加4
cmp r2, r3 //如果r2 =< r3继续拷贝
ble cpy
...
用c语言实现重定位
添加如下链接脚本:
SECTIONS
{
. = 0x30000000;
__code_start = .;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
在main.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地址依次copy到__code_start(代码段的运行地址)
}
}
然后在start.s中设置栈指针sp后,即可执行bl copy2sdram进行重定位代码。如何设置栈指针请参考
时钟编程(二、配置时钟寄存器)中有实现,重复代码我就不贴上来了。
2.清bss的改进
用ldr、str代替ldrb, strb加快清bss的速度
和上面重定位类似,代码如下:
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
bl main
halt:
b halt
c语言实现清bss
和上面重定位的代码实现一样,就是往bss段全部写0. 执行完bl copy2sdram, 然后再bl 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;
}
}
注意:汇编代码获取的是链接脚本中的变量的地址,而C语言代码中获取的是链接脚本中的变量的值,所以这里的用C语言改进重定位还是清bss都是要加取址符。
保证所有段的起始地址以4字节对齐
我们前面为了加快重定位和清bss的速度,用到了ldr,str这样以4字节为单位进行读写,但是还可能导致一个问题,假设现在链接脚本没有进行用ALIGN(4)让不同的段以4字节对齐,那么就会出现访问错乱的情况。
我举个例子:
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
char g_Char = 'A'; //.data
char g_Char3 = 'a';
const char g_Char2 = 'B'; //.rodata
int g_A = 0; //bss
int g_B; //bss
int main(void)
{
uart0_init();
puts("nrg_A = ");
printHex(g_A);
puts("nr");
putchar(g_Char);
return 0;
}
将链接脚本中.data段和.bss之间的ALIGN(4)去掉。那么我们会发现程序执行的时候输出的g_A=0,为什么呢,我们明明初始化g_A=‘A’呀?
我们分析下反汇编看看:
我们的.bss段紧接着.data段后面,可知在对bss段进行清除的时候,由于我们是以4字节为单位操作的,所以我们清除g_A的时候,连带g_Char,g_Char的值也一起清除了。所以data段和数据段之间添加ALIGN(4)。修改后就会发现bss段的地址以0x30000248开始了,如下图:
3.位置无关码
我们对‘bl sdram_init’指令进行分析:
查看反汇编:(代码段的链接地址为0x3000,0000)
这里的bl 3000036c不是跳转到3000036c,这个时候sdram并未初始化,那么这个物理地址是无法访问的;
为了验证,我们做另一个实验,修改连接脚本sdram.lds, 链接地址改为0x3000,0800,编译,查看反汇编:
可以看到现在变成了bl 300003ec,但两个的机器码e1a0c00d都是一样的,机器码一样,执行的内容肯定都是一样的。 因此这里并不是跳转到显示的地址,而是跳转到: pc + offset,这个由链接器决定。
假设程序从0x30000000执行,当前指令地址:0x3000005c ,那么就是跳到0x3000036c;如果程序从0运行,当前指令地址:0x5c 调到:0x000003ec
跳转到某个地址并不是由bl指令所决定,而是由当前pc值决定。反汇编显示这个值只是为了方便读代码。
重点: 反汇编文件里, B或BL 某个值,只是起到方便查看的作用,并不是真的跳转。
怎么写位置无关码?
使用相对跳转命令 b或bl;
重定位之前,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问);
重定位之后,使用ldr pc = xxx,跳转到/runtime地址;
写位置无关码,其实就是不使用绝对地址,判断有没有使用绝对地址,除了前面的几个规则,最根本的办法看反汇编。
因此,前面的例子程序使用bl命令相对跳转,程序仍在NOR/sram执行,要想让main函数在SDRAM执行,需要修改代码:
//bl main /*bl相对跳转,程序仍在NOR/sram执行*/
ldr pc, =main/*绝对跳转,跳到SDRAM*/