1.段的概念
我们的cpu可以直接把地址通过内存控制器发送到norflash,sram,sdram,但是我们的cpu不能直接到达nandflash,只能发送到nandflash控制器,我们的程序可以放到norflash以及sdram上面,它可以直接运行,但是nandflash的程序是不能直接运行的,但是我们仍然可以把程序放到nandflash上面,因为一上电2440内部的硬件会把nandflash的前4K代码复制到片内的SRAM,这个是由硬件做的,然后cpu从0地址开始运行,此时的0地址对应的是sram。那么如果我的程序超过4K怎么办,如果bin文件在nandflash上面超过4K,那么我们不能只复制前面4K代码,所以显然我们前面4K代码应该把整个程序都复制到SDRAM上面。这就是重定位,重新确定程序的地址。
如果我是用norflash启动,此时的0地址在norflash,此时片内4Ksram的地址是0x4000,0000.norflash可以像内存一样读,但是不能像内存一样的写。如果程序中含有需要更改的全局变量或者静态变量,全局变量是包含在bin文件中,烧在norflash上面(局部变量是在栈中在SRAM上面,可读可写没有问题),直接修改变量无效。所以我们需要把这些全局变量和静态变量重定位放到SDRAM中。
我们修改之前的main.c,然后在里面定义一个全局的变量,可以发现串口一直打印A,并没有递增的打印ABCD。
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;
int main(void)
{
uart0_init();
while (1)
{
putchar(g_Char);
g_Char++; /* nor启动时, 此代码无效 */
delay(1000000);
}
return 0;
}
我们的程序主要分为:
代码段(text):代码
数据段(data):一般的全局变量
只读数据段(rodata) :const全局变量。
bss段:初值为0或者无初始值的全局变量。BSS段不保存在bin文件中。
comment:注释:也不保存在bin文件中。
2.链接脚本的引入与简单测试
通过上一节的测试,我们发现当我们把程序烧写到norflash时,由于norflash是不可写的,因此不能修改g_Char,所以我们需要通过修改makefile,让g_Char放到SDRAM里面,我们的代码仍然放在NORFLASH里面,只是把变量保存到SDRAM里面,由于我们的SDRAM的地址是从0X30000000开始的,因此我们修改makefile为如下的形式。
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
但是编译完之后,我们发现我们的bin文件达到了800M,这是因为我们把变量放在了0X30000000这里,但是我们显然不能这么改, 针对这个问题有两个解决方案。
2.1方案一
首先我们在bin文件里面,让代码段和全局变量靠在一起,然后把bin文件烧写到norflash上面,然后运行的时候,我们前面的代码需要把全局变量复制到0X30000000的地方, 这就是重定位。
2.2方案二
第二种方法是,我们在链接的时候,让代码段和全局变量中间没有那么大的空闲,我们的代码段也从0x30000000开始,然后烧写到norflash,然后运行的时候,我们前面的代码把代码段和全局变量整个程序复制到0x30000000。
这两种方案的差别在于,第一种方法只是重定位了全局变量,第二种方法是重定位了整个程序。
下面我们先来看方案一:我们通过引入链接脚本,将本来应该位于0X30000000的变量和代码段拼在一起,我们首先修改makefile为下面的形式:
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o uart.o uart.c
arm-linux-gcc -c -o init.o init.c
arm-linux-gcc -c -o main.o main.c
arm-linux-gcc -c -o start.o start.S
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
clean:
rm *.bin *.o *.elf *.dis
然后我们来写出这个链接脚本sdram.lds
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800) { *(.data) }
.bss : { *(.bss) *(.COMMON) }
}
上面的{}里面的*表示所有文件的.text段,所有文件的.rodata段。其中的AT表示at,也就是全局变量运行的时候是在0x30000000,但是在bin文件中我们把它放在0x800这里。
我们把上面的程序烧写到板子上之后显示乱码,这是因为我们的bin文件中,全局变量是保存在0X800的地方,但是我们的main函数中是以0x30000000为地址去访问全局变量的。而我们的代码中并没有把0x30000000地址的地方用全局变量的值进行初始化,也就是我们的代码中缺少重定位相关的代码,我们需要修改代码,将变量从0x800的地方复制到0x30000000的地方,我们修改start.S,在执行main函数之前进行重定位。
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init /*首先要初始化SDRAM,要不然下面的重定位代码不起作用,没法写SDRAM*/
/* 重定位data段 */
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]
bl main
halt:
b halt
上面的重定位data的代码中,我们简单粗暴的从0x800复制了一个字节到0x30000000这里,这种方法并不通用,首先我们程序中有可能有很多个全局变量,另外我们这里的地址0X800和0x30000000都是写死的,这是因为肉眼从链接脚本中观察到的,下面我们写一个通用的程序,首先要在链接脚本中添加一些变量。
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
其中data_start后面的.表示当前地址,也就是0x30000000, data_end-data_start就等于数据段的长度,LOADADDR(.data)表示data段在bin文件中的地址,也就是0x800.
然后我们修改start.S如下:
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end /* data段结束地址 */
cpy:
ldrb r4, [r1]
strb r4, [r2]
add r1, r1, #1
add r2, r2, #1
cmp r2, r3
bne cpy
bl main
halt:
b halt
3.链接脚本的解析
我们上一节用到了链接脚本,现在我们来解析下前面用到的连接脚本。
首先通过文档 http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html,我们知道链接脚本的格式是这样的:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
在链接脚本格式中,BLOCK(align)很少用到,NOLOAD也很少用到,region:phdr=fill也很少用到。里面的secname名字我们可以随便写,也可以不叫.text .rodata,叫什么名字都可以,start表示运行时的起始地址runtime address,也叫重定位地址relocate address,AT可写也可以不写,表示加载地址,LOADaddress不写时,就等于runtime address.
然后就是contents的格式,contents格式有下面3种:
1:start.o:你可以 指定start.o,把整个start.o放到这个段里面。
2:*(.text):指定所有.o的text段放到这里面。
3:start.o
*(.text):指定start.o放到前面,然后剩下所有文件的text段放到
elf文件地址问题:
我们的makefile中通过链接脚本将一些.o文件生成了elf格式的文件,elf文件中是含有地址信息的,比如加载地址。
我们可以使用加载器把elf文件加载到内存的加载地址load address,对于裸板来说,JTAG调试器就是加载器,对于应用程序来说,加载器本身也是一个应用程序,
然后运行程序。
如果链接脚本中指定了load address ,然后load address不等于runtime address,那么程序本身还需要重定位代码。
裸板bin文件:
elf格式文件生成bin文件。
硬件机制的启动
如果bin文件所在位置不等于运行地址,那么需要程序本身实现重定位。
下面看一下我们前面用到的链接脚本:
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) }
}
第一行没有写加载地址,那么我们的text的加载地址就等于运行地址,也就是0.不要进行重定位。另外这里text是保存的是所有文件的text段,这里的所有文件的顺序就是按照makefile里面写的先后顺序arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf,然后看data段,这里加载地址是0x800,然后运行地址是0x30000000.那么我们程序启动之后需要把数据段从0x800拷贝到0x30000000,这就是重定位。另外,在elf或者bin文件中都不会存bss段,因为bss中是初始值为零的或者没有初始化的全局变量,假如我们有很多初始化为零的全局变脸,那么我们保存这么多零浪费空间。
我们通过编写程序,将初始化为零的全局变量打印出来,发现它并不是零,这是因为我们没有把他清零,我们需要写程序 将BSS段对应的内存清零, 要想清零,需要知道BSS空间的地址是什么,首先要修改链接脚本,
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
}
然后修改start.S清除BSS段。
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
上一篇:ARM64异常
下一篇:s3c2440的Memory Controller与外设地址线错位连接分析
推荐阅读最新更新时间:2024-11-17 14:02
设计资源 培训 开发板 精华推荐
- PCB线圈无线充电(小型设备无线方案贡献板)
- LT1021CIN8-5 具有升压输出电流且无电流限制的电压基准的典型应用
- 采用 LTC1929 5V 输入、1.6V/40A CPU 电源的典型应用电路
- AD8604DRZ 低侧运算放大器电流监视器的典型应用
- DER-846 - 基于LinkSwitch-TN2的15W抽头降压式非隔离电源,适用于家用电器
- AP4320 恒压恒流控制器典型应用 VOUT = VREFX(R1+R2/R1)
- KIT33816FRDMEVM,MC33816 Freedom Board KL25Z 评估套件,喷油器
- LT1170IT 的典型应用,5A 驱动高压 NPN
- USB 指纹模块适配板
- LT3756EUD-1 94% 效率 30W 白光 LED 前照灯驱动器的典型应用电路