代码重定位
我们现在来解决代码重定位实验(一)所引入的代码重定位问题。
对于S3C2440来说:
- BIN文件小于4KB时:
- 若是Nand方式启动,则不存在任何问题
- 若是Nor方式启动,则我们可以只重定位.data段即可
- 当BIN文件大于4KB时:
- 若是Nand方式启动,则需要重定位整个程序,包括代码段和数据段
- 若是以Nor方式启动,则依然只重定位.data段即可
只重定位.data段和清零.bss段
对于重定位.data段的代码,正常情况下应该是使用汇编来编写的,我为了简便起见,使用了C语言来编写。由于此时尚未重定位数据段和清零BSS段,是不应该调用C函数的。但是我保证了这两个函数不访问全局变量,所以只要正确设置了栈指针,调用也可以正常工作。
使用的链接脚本relocate.lds如下:
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
_data_offset = .;
.data 0x30000000 : AT(_data_offset) {
_data_LMA = LOADADDR(.data);
_data_start = .;
*(.data)
_data_end = .;
}
_bss_start = .;
.bss : { *(.bss) }
_bss_end = .;
}
relocate.c
extern unsigned char _data_offset;
extern unsigned char _data_start;
extern unsigned char _data_end;
extern unsigned char _bss_start;
extern unsigned char _bss_end;
void copyDataSection(void){
volatile unsigned char *dataLMA = &_data_offset;
volatile unsigned char *start = &_data_start;
volatile unsigned char * end = &_data_end;
while(start <= end){
*start = *dataLMA;
start++;
dataLMA++;
}
}
void clearBSS(void){
volatile unsigned char* start = &_bss_start;
volatile unsigned char* end = &_bss_end;
while(start < end){
*start = 0;
start++;
}
}
CRT0.S
.text
.global _start
_start:
/* 1.关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 2.设置时钟 */
/* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 2.3 设置CPU处于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */
mcr p15,0,r0,c1,c0,0
/* 2.4 设置MPLLCON(0x4C000004)=(92<<12) | (1 << 4) | (1 << 0)
* m = MDIV + 8 = 100
* p = PDIV + 2 = 3
* s = SDIV = 1
* Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12) | (1 << 4) | (1 << 0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 3.设置栈
* 自动分辨NOR启动或者NAND启动
* 向0地址写入0,在读出来,如果写入则是NAND,否则是NOR
*/
ldr r0, =0
ldr r1, [r0] /* 读出原来的值备份 */
str r0, [r0] /* 向0地址写入0 */
ldr r2, [r0] /* 再次读出来 */
cmp r1, r2
ldr sp, =0x40000000 + 4096 /* nor启动 */
movne sp, #4096 /* nand启动 */
strne r1, [r0] /* 恢复原来的值 */
//初始化SDRAM内存控制器
bl sdram_init
bl copyDataSection
bl clearBSS
bl main
halt:
b halt
main.c:
#include "myprintf.h"
#include "uart.h"
#include "util.h"
char gCh = 'A';
char gCh1;
int main(void) {
uart0_init();
printf("%s\n\r", "NorFlash Relocate Test.");
while(1) {
gCh++;
printf("%c(0x%x)", gCh, gCh);
wait(800000);
}
return 0;
}
将编译生成的BIN文件烧写至NorFlash中,启动开发板,发现可以main函数正常修改全局变量了,如下图所示:
上述代码效率改进
从拷贝函数中可以看出,我们每次只拷贝一个字节。而JZ2440的SDRAM是32位的,这样拷贝的话效率就很低。为此,我们一次拷贝4个字节的数据。我们修改relocate.c文件如下所示:
relocate.c
extern unsigned int _data_offset;
extern unsigned int _data_start;
extern unsigned int _data_end;
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void copyDataSection(void){
volatile unsigned int *dataLMA = &_data_offset;
volatile unsigned int *start = &_data_start;
volatile unsigned int * end = &_data_end;
while(start <= end){
*start = *dataLMA;
start++;
dataLMA++;
}
}
void clearBSS(void){
volatile unsigned int* start = &_bss_start;
volatile unsigned int* end = &_bss_end;
while(start < end){
*start = 0;
start++;
}
}
再次编译烧写至开发板,以Nor方式启动,并上电观察。
通过上述动态图可以看出,全局变量gCh本来是等于‘A’,对应0x41。但是现在却是0了,被清零了。这是为什么呢?为什么清零.bss段的时候,会把.data段的值给清掉呢?
我们观察清零.bss段的反汇编,如下所示:
Disassembly of section .data:
30000000 <_data_start>:
30000000: 41 .byte 0x41
Disassembly of section .bss:
30000001 <gCh1>:
...
00000d2c <clearBSS>:
d2c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
d30: e28db000 add fp, sp, #0 ; 0x0
d34: e24dd00c sub sp, sp, #12 ; 0xc
d38: e59f3040 ldr r3, [pc, #64] ; d80 <clearBSS+0x54>
d3c: e50b300c str r3, [fp, #-12]
d40: e59f303c ldr r3, [pc, #60] ; d84 <clearBSS+0x58>
d44: e50b3008 str r3, [fp, #-8]
d48: ea000005 b d64 <clearBSS+0x38>
d4c: e51b200c ldr r2, [fp, #-12]
d50: e3a03000 mov r3, #0 ; 0x0
d54: e5c23000 strb r3, [r2]
d58: e51b300c ldr r3, [fp, #-12]
d5c: e2833001 add r3, r3, #1 ; 0x1
d60: e50b300c str r3, [fp, #-12]
d64: e51b200c ldr r2, [fp, #-12]
d68: e51b3008 ldr r3, [fp, #-8]
d6c: e1520003 cmp r2, r3
d70: 3afffff5 bcc d4c <clearBSS+0x20>
d74: e28bd000 add sp, fp, #0 ; 0x0
d78: e8bd0800 pop {fp}
d7c: e12fff1e bx lr
d80: 30000001 .word 0x30000001
d84: 30000002 .word 0x30000002
由该段汇编代码可知,它对0x30000001地址开始,到0x30000002地址前一个字节为止,进行了清零操作。而数据段.data的地址位于0x30000000处,所以理论上好像不会被清掉。
我们再观察SDRAM的电路图可知:
CPU发出的地址线最低两位没有接到SDRAM芯片上,因为两片SDRAM组成了32位的数据总线,CPU对于内存是按照4字节寻址的,地址线的最低两位被忽略,默认为0。
所以若是单次访问4个字节的数据,无论是访问地址0x30000001、0x30000002或者0x30000003,CPU最终访问的都是0x30000000开始的四字节数据。所以数据段的数据就在清零.bss段时被清理掉了。
问题原因找到了,解决方法就简单了。我们在链接脚本中主动让数据段和BSS段都按4字节先对齐好,就不会有问题了。修改链接脚本如下所示:
relocate.lds:
SECTIONS {
.text 0 : {*(.text)}
.rodata : {*(.rodata)}
/*按4字节对齐*/
. = ALIGN(4);
_data_offset = .;
.data 0x30000000 : AT(_data_offset) {
_data_LMA = LOADADDR(.data);
_data_start = .;
*(.data)
_data_end = .;
}
/*按4字节对齐*/
. = ALIGN(4);
_bss_start = .;
.bss : { *(.bss) }
_bss_end = .;
}
重新编译,以Nor方式启动,上电观察,又恢复正常了。
重定位整个程序
当我们的开发板是以Nand方式启动,并且BIN文件超过4KB时,则需要重定位整个程序,包括代码段和数据段。为此,我们需要引入一个概念:位置无关代码。位置无关代码(PIC:Position Independent Code),就是无论被加载到任何地址空间都可以正常工作的代码。
那么怎么写位置无关的程序呢?
- 调用程序时使用b或者bl相对跳转指令
- 重定位之前,不能使用绝对地址,不可访问全局变量或者静态变量
- 不可访问有初始值的数组(因为这些初始值放置在.rodata段内,而.rodata段是位置相关的,不在栈中)
- 重定位之后,需要使用绝对跳转指令跳转到运行时地址(链接地址)处开始执行,比如ldr pc, =main
由于我们还没有涉及到Nand Flash的操作实验(下一篇文章),而Nand Flash不能像访问内存一样地读取数据,所以我们还是先将BIN文件烧写至Nor Flash,并以Nor Flash方式启动,进行整个程序的重定位。
跳转指令
在汇编文件中调用main函数时,注意必须使用绝对跳转指令:
ldr pc, =main
而不能使用bl main这种位置无关指令,否则仍旧是在Nor Flash或者是SRAM中运行。
又因为ldr pc, =main指令并不改变lr寄存器存储的返回地址,所以在跳转到main函数之前,要修改lr,使其指向halt,否则从main函数返回,会重新再一次执行ldr pc, =main指令,如下所示:
ldr lr, =halt
ldr pc, =main
全部文件如下所示:
我们列出最主要的几个源码文件:
CRT0.S
.text
.global _start
_start:
/* 1.关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 2.设置时钟 */
/* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 2.3 设置CPU处于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */
mcr p15,0,r0,c1,c0,0
/* 2.4 设置MPLLCON(0x4C000004)=(92<<12) | (1 << 4) | (1 << 0)
* m = MDIV + 8 = 100
* p = PDIV + 2 = 3
* s = SDIV = 1
* Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12) | (1 << 4) | (1 << 0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 3.设置栈
* 自动分辨NOR启动或者NAND启动
* 向0地址写入0,在读出来,如果写入则是NAND,否则是NOR
*/
ldr r0, =0
ldr r1, [r0] /* 读出原来的值备份 */
str r0, [r0] /* 向0地址写入0 */
ldr r2, [r0] /* 再次读出来 */
cmp r1, r2
ldr sp, =0x40000000 + 4096 /* nor启动 */
movne sp, #4096 /* nand启动 */
strne r1, [r0] /* 恢复原来的值 */
//初始化SDRAM内存控制器
bl sdram_init
//bl copyDataSection
bl copyBIN2SDRAM
bl clearBSS
//bl main
ldr lr, =halt
ldr pc, =main //这才是真正跳转到SDRAM中执行代码
halt:
b halt
relocate.c
extern unsigned int _text_start;
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void copyBIN2SDRAM(void){
volatile unsigned int* srcStart = 0x0;
volatile unsigned int* destStart = &_text_start;
volatile unsigned int* destEnd = &_bss_start;
while(destStart < destEnd){
*destStart = *srcStart;
destStart++;
srcStart++;
}
}
void clearBSS(void){
volatile unsigned int* start = &_bss_start;
volatile unsigned int* end = &_bss_end;
while(start < end){
*start = 0;
start++;
}
}
relocate.lds
SECTIONS {
. = 0x30000000;
_text_start = .;
.text : {*(.text)}
.rodata : {*(.rodata)}
/*按4字节对齐*/
. = ALIGN(4);
.data : {
_data_start = .;
*(.data)
_data_end = .;
}
/*按4字节对齐*/
. = ALIGN(4);
_bss_start = .;
.bss : { *(.bss) }
_bss_end = .;
}
Makefile
CROSS_COMPILE_PREFIX=arm-linux-
CFLAGS=-g
TARGET=relocate
$(TARGET).bin : CRT0.o uart.o myprintf.o Ctype.o sdram.o util.o $(TARGET).o main.o
@#$(CROSS_COMPILE_PREFIX)ld -Ttext 0x0000000 -Tdata 0x30000000 -o $(TARGET).elf $^
$(CROSS_COMPILE_PREFIX)ld -T $(TARGET).lds -o $(TARGET).elf $^
$(CROSS_COMPILE_PREFIX)objcopy -O binary -S $(TARGET).elf $@
$(CROSS_COMPILE_PREFIX)objdump -D $(TARGET).elf > $(TARGET).dis
CRT0.o : CRT0.S
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -c -o $@ $^
myprintf.o : myprintf.c stdarg.h
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
uart.o : uart.c stdarg.h s3c2440_soc.h
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
$(TARGET).o : $(TARGET).c
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
main.o : main.c
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
Ctype.o : Ctype.c Ctype.h
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
util.o : util.c util.h
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
sdram.o : sdram.c sdram.h
$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
clean:
rm -f *.elf *.dis *.bin *.o
编译并烧写至开发板,选择Nor Flash方式启动,观察如下所示:
与本文上面的在Nor Flash中运行效果动态图对比,可以发现,在SDRAM中运行的速度有了明显的提升。