先复习一下段的概念: 一个程序由代码段、只读数据段、可读可写的数据段、BSS段等组成。
char g_Char = 'A'; // 可读可写,不能放在ROM上,应该放在RAM里
const char g_Char2 = 'B'; // 只读变量,可以放在ROM上
int g_A = 0; // 初始值为0,干嘛浪费空间保存在ROM上?没必要
int g_B; // 没有初始化,干嘛浪费空间保存在ROM上?没必要
-
代码段(RO-CODE):就是程序执行的指令,不能被修改
-
可读可写的数据段(RW-DATA):有初始值的全局变量、静态变量,需要从ROM上复制到内存
-
只读的数据段(RO-DATA):可以放在ROM上,不需要复制到内存
-
BSS段或ZI段:
-
初始值为0的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
-
未初始化的全局变量或静态变量,没必要放在ROM上,使用之前清零就可以
-
-
局部变量:保存在栈中,运行时生成
-
堆:一块空闲空间,使用malloc函数来管理它,malloc函数可以自己写
为什么要重定位?
当程序的加载地址 != 链接地址时,需要重定位。
加载地址和链接地址:
程序运行时,应该位于它的链接地址处,因为:
-
使用函数地址时用的是"函数的链接地址",所以代码段应该位于链接地址处
-
去访问全局变量、静态变量时,用的是"变量的链接地址",所以数据段应该位于链接地址处
但是: 程序一开始时可能并没有位于它的"链接地址":
-
比如对于STM32F103,程序被烧录器烧写在Flash上,这个地址称为"加载地址"
-
比如对于IMX6ULL/STM32MP157,片内ROM根据头部信息把程序读入内存,这个地址称为“加载地址”
程序重定位
-
代码段:如果它不在链接地址上,就需要重定位
-
只读数据段:如果它不在链接地址上,就需要重定位
-
可读可写的数据段:如果它不在链接地址上,就需要重定位
-
BSS段:不需要重定位,因为程序里根本不保存BSS段,使用前把BSS段对应的空间清零即可
谁来做重定位的操作?
1.CPU内部
2. 程序开头通过使用“位置无关码”编写的重定位代码,实现程序自己重定位
-
什么叫位置无关码:这段代码扔在任何位置都可以运行,跟它所在的位置无关
-
怎么写出位置无关码:
-
跳转:使用相对跳转指令,不能使用绝对跳转指令
-
只能使用branch指令(比如
bl main
),不能给PC直接复制,比如ldr pc, =main
-
-
不要访问全局变量、静态变量
-
不使用字符串
-
3. 怎么做重定位和清除BSS段?
-
核心:复制
-
复制的三要素:源、目的、长度
-
怎么知道代码段/数据段保存在哪?(加载地址)
-
怎么知道代码段/数据段要被复制到哪?(链接地址)
-
怎么知道代码段/数据段的长度?
-
-
怎么知道BSS段的地址范围:起始地址、长度?
-
这一切
-
在keil中使用散列文件(Scatter File)来描述
-
在GCC中使用链接脚本(Link Script)来描述
-