ARM架构与编程——7.代码重定位

代码重定位

一、清除BSS段(ZI段)

1.1 C语言中的BSS段

程序里的全局变量,如果它的初始值为0,或者没有设置初始值,这些变量被放在BSS段里,也叫ZI段。

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;  // 放在BSS段
int g_B;      // 放在BSS段

BSS段并不会放入bin文件中,也不会烧写到flash中,否则也太浪费空间了。
在使用BSS段里的变量之前,把BSS段所占据的内存清零就可以了。

  • 注意:对于keil来说,一个本该放到BSS段的变量,如果它所占据的空间小于等于8字节自己,keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时,才会放到BSS段。
int g_A[3] = {
   0, 0};   // 放在BSS段			
char g_B[9];           // 放在BSS段

int g_A[2] = {
   0, 0};   // 放在data段
char g_B[8];           // 放在data段

在这里插入图片描述

打印数组的首项,显示结果是随机值,怎么修改才能让打印出的内存地址不是随机值呢?初始化BSS段,清零
所以在定义的变量值为0,或者未初始化时,将他们放到BSS段上,在使用前清零即可

1.2 BSS段在哪?多大?

在散列文件中,BSS段(ZI段)在可执行域RW_IRAM1中描述:

LR_IROM1 0x08000000 0x00080000  {
       ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {
     ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {
     ; RW data
   .ANY (+RW +ZI)
  }
}

BSS段(ZI段)的链接地址(基地址)、长度,使用下面的符号获得:
在这里插入图片描述

1.3 怎么清除BSS段

1.汇编码

IMPORT |Image$$RW_IRAM1$$ZI$$Base|
IMPORT |Image$$RW_IRAM1$$ZI$$Length|

LDR R0, = |Image$$RW_IRAM1$$ZI$$Base|       ; DEST
MOV R1, #0									; VAL
LDR R1, = |Image$$RW_IRAM1$$ZI$$Length|     ; Length
BL memset

2.C语言

  • 方法1
    声明为外部变量,使用时需要使用取址符:
extern int Image$$RW_IRAM1$$ZI$$Base;
extern int Image$$RW_IRAM1$$ZI$$Length;
memset(&Image$$RW_IRAM1$$ZI$$Base, 0, &Image$$RW_IRAM1$$ZI$$Length);
  • 方法2
    声明为外部数组,使用时不需要使用取址符:
extern char Image$$RW_IRAM1$$ZI$$Base[];
extern int Image$$RW_IRAM1$$ZI$$Length[];
memset(Image$$RW_IRAM1$$ZI$$Base[], 0, Image$$RW_IRAM1$$ZI$$Length);

二、代码重定位

2.1 加载地址等于链接地址

在默认散列文件中,代码段的load address = execution address,(执行地址)
也就是加载地址和**执行地址(链接地址)**一致,所以无需重定位:

ROM对应的地址是0x08000000;RAM对应的地址是0x020000000

LR_IROM1 0x08000000 0x00080000  {
       ; load region size_region			//加载域;加载域里面有两个执行域
  ER_IROM1 0x08000000 0x00080000  {
     ; load address = execution address		//这个可执行域里面放的主要是代码;加载地址=执行地址,所以
   *.o (RESET, +First)				//所有.o 文件的RESET段					//不需要重定位
   *(InRoot$$Sections)
   .ANY (+RO)			//.o 文件,或者.h文件的只读数据段; .ANY表示所有.o文件、库文件
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {
     ; RW data
   .ANY (+RW +ZI)
  }
}

2.2 加载地址不等于链接地址

有时候,我们需要把程序复制到内存里运行,比如:

  • 想让程序执行得更快:需要把代码段复制到内存里
  • 程序很大,保存在片外SPI Flash中,SPI Flash上的代码无法直接执行,需要复制到内存里

这时候,需要修改散列文件,把代码段的可执行域放在内存里
那么程序运行时,需要尽快把代码段重定位到内存
散列文件示例:

LR_IROM1 0x08000000 0x00080000  {
       ; load region size_region
  ER_IROM1 0x20000000   {
     ; load address != execution address
   *.o (RESET, +First)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 +0   {
     ; RW data
   .ANY (+RW +ZI)
  }
}

上面的散列文件中:

  • 可执行域ER_IROM1
    • 加载地址为0x08000000,可执行地址为0x20000000,两者不相等
    • 板子上电后,从0x08000000处开始运行,需要尽快把代码段复制到0x20000000
  • 可执行域RW_IRAM1
    • 加载地址:紧跟着ER_IROM1的加载地址
    • 可执行地址:紧跟着ER_IROM1的可执行地址
    • 需要尽快把数据段复制到可执行地址处

数据段的重定位我们做过实验,
如果代码段不重定位的话,会发生什么事?

2.3 代码段不重定位的后果

不能使用链接地址来调用函数

  • 汇编中
ldr  pc, =main   ; 这样调用函数时,用到main函数的链接地址,如果代码段没有重定位,则跳转失败
  • C语言中
void (*funcptr)(const char *s, unsigned int val);
funcptr = put_s_hex;
funcptr("hello, test function ptr", 123);

2.4 代码段重定位

①code:代码段,存放程序的所有代码

②RO:只读数据段,存放程序中定义的常量

③RW:读写数据段,存放初始化为非0值的全局变量

④ZI:零数据段,存放未初始化的全局变量,以及初始化为0的变量

ROM:存放指令代码和一些固定数值,程序运行后不可改动。

凡是c文件及h文件中所有代码、全局变量、局部变量、’const’限定符定义的常量数据、startup.asm文件中的代码(类似ARM中的bootloader或者X86中的BIOS,一些低端的单片机是没有这个的)通通都存储在ROM中。

RAM:用于程序运行中数据的随机存取,掉电后数据消失。

凡是整个程序中,所用到的需要被改写的量,都存储在RAM中,“被改变的量”包括全局变量、局部变量、堆栈段。

程序经过编译、汇编、链接后,生成hex文件。用专用的烧录软件,通过烧录器将hex文件烧录到ROM中,这个时候的ROM中,包含所有的程序内容:无论是一行一行的程序代码,函数中用到的局部变量,头文件中所声明的全局变量,const声明的只读常量,都被生成了二进制数据,包含在hex文件中,全部烧录到了ROM里面,此时的ROM,包含了程序的所有信息,正是由于这些信息,“指导”了CPU的所有动作。

可能有人会有疑问,既然所有的数据在ROM中,那RAM中的数据从哪里来?什么时候CPU将数据加载到RAM中?

回答这个问题,首先必须明确一条:ROM是只读存储器,CPU只能从里面读数据,而不能往里面写数据,掉电后数据依然保存在存储器中;

RAM是随机存储器,CPU既可以从里面读出数据,又可以往里面写入数据,掉电后数据不保存,这是条永恒的真理,始终记挂在心。

RAM中的数据不是在烧录的时候写入的,同时也说明,在CPU运行时,RAM中已经写入了数据。关键就在这里:这个数据不是人为写入的,CPU写入的,那CPU又是什么时候写入的呢?

这里所做的工作是为整个程序的顺利运行做好准备,或者说是对RAM的初始化(注:ROM是只读不写的),工作任务有几项:
1、为全局变量分配地址空间—如果全局变量已赋初值,则将初始值从ROM中拷贝到RAM中,如果没有赋初值,则这个全局变量所对应的地址下的初值为0或者是不确定的。当然,如果已经指定了变量的地址空间,则直接定位到对应的地址就行,那么这里分配地址及定位地址的任务由“连接器”完成。
2、设置堆栈段的长度及地址-–用C语言开发的单片机程序里面,普遍都没有涉及到堆栈段长度的设置࿰

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值