SDRAM和重定位
2017年09月19日 20:44:11
阅读数:280
一、栈的介绍
1.设置栈的原因:C语言运行需要一定条件,那就是栈,而这些栈应该在启动代码运行时分配,以供后面C语言的运行。
2.C语言与栈的关系:C语言的局部变量都需要栈来实现。如果在启动代码开始时,没有给C部分设置合理的栈地址,那么C语言的局部变量就找不到地方存放,导致程序死掉。
3.CPU在各种模式下的栈:每种模式下都有自己独立的SP寄存器(r13),在各种模式下使用不同的栈,每个应用程序也使用不同的栈。(栈的作用:保存现场和传递参数)
4.栈的分配:栈必须是当前一段可用内存,这个内存是已经被初始化过并且可用内存,这个内存这会用作栈,不会被其他程序征用。刚启动的时候,DRAM尚未初始化,所以当前可用内存只有SRAM,SRAM是不需要初始化即可用的内存,因此我们在SRAM中找一段内存来作为SVC的栈。
5.栈的分类:满减栈满增栈 空减栈 空增栈。ARM使用满减栈。
二、汇编设置栈和C语言调用
由S5PV210_iROM_ApplicationNote手册可知,SVC栈应该从0xD0037D80开始。
***led.c 的内容:
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define WGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define WGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
void delay_time(void);
void led_link(void){
WGPJ0CON = 0x11111111; //设置GPJ0为输出模式
while(1){
WGPJ0DAT = ~(1<<3);
delay_time();
WGPJ0DAT = ~(1<<4);
delay_time();
WGPJ0DAT = ~(1<<5);
delay_time();
}
}
void delay_time(void){
unsigned int counter = 0xAAAAA;
while(counter--);
}
***Start.s的内容:
#define SVC_STACK 0xD0037D80
.global _start //把_start设置为外部链接属性
_start:
ldr sp, = SVC_STACK /* 设置SVC栈 */
bl led_link /* 开始调用C程序,C函数名led_link */
b .
除了汇编文件和C 文件以外,还需要Makefile文件进行编译链接。
三、Makefile文件
***Makefile的内容:
led.bin: start.o led.o
arm-linux-ld -Ttext 0x0 -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
四、重定位
连接地址和运行地址
这两个地址可能相同,也可以不相同,对于有关位置的代码,最终执行的地址和连接编译时给定的地址必须相同,否则会出错。
连接地址:连接时指定的地址,指定的方式为:Makefile中用-Ttext,或者连接脚本。
运行地址:程序运行时的地址,指定方式为:实际运行时被加载到内存的指定位置。
为什么需要重定位?uboot实际使用方式:uboot大小可以随意,现假定为200KB。启动过程:开机上电后BL0运行,BL0会加载外部启动设备中的uboot前16KB(BL1)到SRAM中去运行,BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转指令从SRAM跳转到DDR中继续运行,uboot启动后在uboot命令行中去启动OS。所以结论是:链接地址和运行地址有时候必须不同,而且必须用位置无关码,这时候必须重定位。
分散加载:把uboot分成两部分(BL1和uboot),两部分分别制定不同的链接地址,启动时分别把两部分加载到不同的地址(BL1加载到SRAM,整个uboot加载到DDR),这时候不用重定位也能启动。分散加载其实就是相当于手工重定位,重定位使用代码进行重定位,分散加载是是手工重定位。
运行地址由什么决定?运行地址由运行时决定,编译链接时是无法绝对确定运行时的地址。
链接地址由什么决定?链接地址是由程序员在编译链接的过程中,通过-Ttext XXX指定或者通过链接脚本决定。
链接脚本究竟是什么?链接脚本其实是个规则文件,是程序员是指挥链接器工作,连接器会参考链接脚本,并且使用规定的规则来处理.o文件那些段,将其链接成一个可执行程序。链接脚本的关键内容有两部分:段名+地址(此地址作为链接的内存地址)。SECTIONS {}这个是整个链接脚本,.点号在链接脚本中代表当前位置,= 等号代表赋值。
五、程序段的划分
程序段分有几个段:代码段、数据段、bss(ZI段)、自定义段。段是程序的一部分,把程序分为几个段,给每个段起上名字,然后再链接时就可以用这些名字指示段,也就是说给段命名就是为了在链接脚本中用段名来让段站在核实的位置。段又分为两种类型:一种是编译器连接器内部制定好的,先天性的名字,另一种是程序员自己指定的,自定义的段名。
先天性的段名:
代码段:(.text),又叫文本段,代码段其实就是函数编译生成的。
数据段:(.data),数据段就是C语言中有显示初始化为非零的东西。
Bss段:(.bss)就是零初始段,对应C语言中初始化为0的全局变量。
后天性的段名:
段名由程序员自己定义,段的属性和特征也由程序员自己定义。
两个问题:
1.如何保证未显示初始化的全局变量为0?
***C语言把这类全局变量放在了.bss段,从而保证为0。
2.如何保证显示初始化的非零全局变量在main之前就被赋值了?
***因为它把这类变量放在了.data中,而.data段会在main之前被处理了。
六、在SRAM中重定位代码
为什么做这个实验?就是纯粹的练习重定位技能,以备后续实验。
思路:
① 通过连接脚本将代码连接到0xd0024000
② dnw下载时将.bin文件下载到0xd0020010
以上步骤保证了:代码实际下载运行在0xd0020010,但却被连接到
0xd0024000执行,从而保证了重定位。当我们把连接地址设置为
0xd0024000,意味着这个代码必须放到0xd0024000位置才能正确执行。
什么是重定位:在PIC执行完之前(代码中第一句位置有关码执行之前),
必须将整个代码搬移到0xd0024000处去执行。
③ 代码执行时通过前段少量的位置无关码将整个代码搬移到0xd0024000
④ 使用长跳转命令跳转到0xd0024000处的代码继续执行,重定位完成
***start.s内容:
#define SVC_STACK 0xD0037D80
.global _start //_start设置为外部可连接
_start:
ldr sp, = SVC_STACK //设置栈
/* 重定位 */
adr r0, _start //adr指令用于加载_start当前运行地址
ldr r1, = _start //ldr指令用于加载连接地址: 0xd0024000
/* bss段的起始地址,就是重定位代码的结束地址,重定位只需重定位数
据段和代码段,*/
ldr r2,= bss_start
cmp r0, r1 //比较_start运行时的地址和连接地址是否一样
/* 如果相等,不需要重定位,执行clean_bss;不相等执行下面的
copy_loop进行重定位,重定位完成后,继续执行clean_bss */
beq clean_bss
copy_loop:
ldr r3,[r0], #4
str r3,[r1], #4 //这两条语句完成4字节的拷贝
cmp r1,r2
bne copy_loop
clean_bss:
ldr r0,= bss_start
ldr r1,= bss_end
cmp r0,r1
beq run_on_dram
clear_loop:
str r2, [r0], #4
cmp r0, r1
bne clear_loop
run_on_dram:
Ldr pc, =led_blink //长跳转到led_blink
b .
***link.lds文件的内容:
SECTIONS
{
. = 0xd0024000;
.text : {
start.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
***led.c文件的内容:
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
void delay(void)
{
volatile unsigned int i = 900000;
While(i--);
}
void led_blink(void)
{
rGPJ0CON = 0x11111111;
while(1)
{
rGPJ0DAT = ((0<<3)|(0<<4)|(0<<5));
delay();
rGPJ0DAT = ((1<<3)|(1<<4)|(1<<5));
delay();
}
}
***Makefile文件的内容:
led.bin: start.o led.o
arm-linux-ld -Tlink.lds -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
七、代码重定位到SDRAM
SDRAM的介绍
SDRAM/DDR都属于动态内存,相对来说,SRAM可看作静态内存。SDRAM先要初始化才能用,类似于NorFlash和NandFlash(硬盘)这两个,而SRAM一上电后就可以直接运行代码。
汇编实现SDRAM的初始化
sdram_init.S文件,对应的DDR初始化。