【韦东山旧1期学习笔记】08.S3C2440 代码重定位实验(三)

代码重定位

我们现在来解决代码重定位实验(一)所引入的代码重定位问题。
对于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中运行的速度有了明显的提升。

发布了26 篇原创文章 · 获赞 2 · 访问量 1072

猜你喜欢

转载自blog.csdn.net/BakerTheGreat/article/details/104302118
今日推荐