一、分散加载文件基本概念
1、分散加载文件:(即scatter file 后缀为.scf)是一个文本文件,通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址。如果不用分散加载文件指定,那么ARM连接器会按照默认的方式来生成映像文件。如下图勾选Use Memory Layout from target dialog,会按照默认的方式生成映像文件
分散加载的根本功能是指定程序在存储空间上面的存储分配以及运行空间的分配,所有要有加载域和运行域来分别指定程序存储空间以及程序运行空间。在Cortex-M里面基本就是芯片内部的Flash空间;
2、映像文件:ARM映像文件是一个层次性结构的文件,其中包含了域(region)、输出段(output section)和输入段(input section)。各部分关系如下:
•一个映像文件由一个或多个域组成
•每个域包含一个或多个输出段
•每个输出段包含一个或多个输入段
•各输入段包含了目标文件中的代码和数据
输入段中包含了4类内容:代码、已经初始化的数据、未经初始化的存储区域、内容初始化成0的存储区域。每个输入段有相应的属性,可以为只读的(RO)、可读写的(RW)以及初始化成0的(ZI)。ARM连接器根据各输入段的属性将这些输入段分组,再组成不同的输出段以及域。
一个输出段中包含了一系列的具有相同的RO、RW和ZI属性的输入段。输出段的属性与其中包含的输入段的属性相同。在一个输出段内部,各输入段是按照一定的规则排序的。
一个域中包含了1~3个输出段,其中各输出段的属性各不相同。各输出段的排列顺序是由其属性决定的。其中,RO属性的输出段排在最前面,其次是RW属性的输出段,最后是ZI属性的输出段。一个域通常映射到一个物理存储器上,如ROM和RAM等。
加载域:该映像文件开始运行前存放的区域,即当系统启动或加载时应用程序存放的区域。
执行域:映像文件运行时的区域,即系统启动后,应用程序进行执行和数据访问的存储器区域,系统在实时运行时可以有一个或多个执行区域。由于MCU内部的Flash是可以运行代码的,但是不能用于变量也就是RW与ZI的加载,主要原因是变量需要经常修改,几个小时就可能连续改变几十万次,但是目前Flash工艺的写寿命介于10万次~100万次之间,如果把RW和ZI放在Flash上,那就是灾难,Flash会因为写次数的限制很快就会挂掉,而且Flash只能按块操作,开销太大,所以一般都是放到SRAM里面,所以运行域分成两个部分,RO数据段放在内部Flash里面,RW与ZI放到片内SRAM中去执行
需要使用分散加载文件的情况:
⦁ 复杂内存映射:如果必须将代码和数据放在多个不同的内存区域中,则需要使用详细指令指定将哪个节放在哪个内存空间中。
⦁ 不同类型的内存:许多系统都包含多种不同的物理内存设备,如闪存、 ROM、SDRAM 和快速 SRAM。 分散加载描述可以将代码和数据与最适合的内存类型相匹配。 例如,可以将中断代码放在快速 SRAM 中以缩短中断响应时间,而将不经常使用的配置信息放在较慢的闪存中。
⦁ 内存映射的外围设备:分散加载描述可以将数据节准确放在内存映射中的某个地址,以便能够访问内存映射的外围设备。
⦁ 位于固定位置的函数:可以将函数放在内存中的相同位置,即使已修改并重新编译周围的应用程序。 这有助于实现跳转表。
⦁ 使用符号标识堆和堆栈:链接应用程序时,可以为堆和堆栈位置定义一些符号。
二、分散加载文件语法
load_region_name start_address | "+"offset [attributes] [max_size]
{
execution_region_name start_address | "+"offset [attributes][max_size]
{
module_select_pattern ["("
("+" input_section_attr | input_section_pattern)
([","] "+" input_section_attr | "," input_section_pattern)) *
")"]
}
}
load_region: 加载区,用来保存永久性数据(程序和只读变量)的区域;
execution_region: 执行区,程序执行时,从加载区域将数据复制到相应执行区后才能被正确执行;
load_region_name: 加载区域名,用于“Linker”区别不同的加载区域,最多31个字符;
start_address: 起始地址,指示区域的首地址;
+offset:前一个加载区域尾地址+offset 做为当前的起始地址,且“offset”应为“0”或“4”的倍数;
attributes: 区域属性,可设置如下属性:
PI 与地址无关方式存放;
RELOC 重新部署,保留定位信息,以便重新定位该段到新的执行区;
OVERLAY 覆盖,允许多个可执行区域在同一个地址,ADS不支持;
ABSOLUTE 绝对地址(默认);
max_size: 该区域的大小;
execution_region_name:执行区域名;
start_address: 该执行区的首地址,必须字对齐;
+offset: 同上;
attributes: 同上;
PI 与地址无关,该区域的代码可任意移动后执行;
OVERLAY 覆盖;
ABSOLUTE 绝对地址(默认);
FIXED 固定地址;
UNINIT 不用初始化该区域的ZI段;
module_select_pattern: 目标文件滤波器,支持通配符“*”和“?”;
*.o匹配所有目标,* (或“.ANY”)匹配所有目标文件和库。
input_section_attr: 每个input_section_attr必须跟随在“+”后;且大小写不敏感;
RO-CODE 或 CODE
RO-DATA 或 CONST
RO或TEXT, selects both RO-CODE and RO-DATA
RW-DATA
RW-CODE
RW 或 DATA, selects both RW-CODE and RW-DATA
ZI 或 BSS
ENTRY, that is a section containing an ENTRY point.
FIRST,用于指定存放在一个执行区域的第一个或最后一个区域;
LAST,同上;
input_section_pattern: 段名;
三、多个加载区
上面的的分散加载描述仅指定 program1.o 和 program2.o 的代码和数据位置。 如果链接其他模块 (如 program3.o)并使用此描述文件,则不会指定 program3.o 的代码和数据位置。除非对代码数据位置的要求非常严格,否则建议使用 * 或 .ANY 说明符来放置其余代码和数据。
四、如何将函数或数据块固定地址
方法一:将函数和数据放在其自己的源文件中,然后在加载区中指定地址
方法二:使用 __attribute__((section(”name”))) 创建命名节。
下面的例子实现了将函数SysTick_Handler及变量TestNum放在加载域RAM中。
#define ISRHANDLE __attribute__((section("IsrHandle")))
U8 TestNum __attribute__((section("sec"))) = 10;
void ISRHANDLE SysTick_Handler (void){};
LR1 0x0 0x10000
{
RAM 0x400000
{
* (IsrHandle)
*(sec)
}
}
五、为执行区保留空白区方法
可以在执行区分散加载描述中使用 EMPTY 属性,为堆栈保留一个空白内存块,在运行时,不会将为 EMPTY 执行区创建的虚 ZI 区初始化为零。该内存块并不构成加载区的一部分,而是在执行时分配使用的。 由于它是作为虚 ZI 区创建的,因此链接器使用以下符号对其进行访问:
• Image$$region_name$$ZI$$Base
• Image$$region_name$$ZI$$Limit
• Image$$region_name$$ZI$$Length.
如果指定的长度为负值,则将该地址作为区结束地址。 它必须是绝对地址,而不是相对地址。
下图中定义了一个名为 STACK 的区,它的开始地址是 0x7F0000,结束地址是 0x800000。+0表示的意思紧接着栈排放
在此示例中,链接器生成以下符号:
Image$$STACK$$ZI$$Base = 0x7f0000
Image$$STACK$$ZI$$Limit = 0x800000
Image$$STACK$$ZI$$Length = 0x10000
Image$$HEAP$$ZI$$Base = 0x800000
Image$$HEAP$$ZI$$Limit = 0x810000
Image$$HEAP$$ZI$$Length = 0x10000
其他常用说明
1、使用预处理器:
当我们的分散加载文件比较复杂,希望通过宏定义来让其更加直观,可以在分散加载文件开头增加命令#! armcc –E来实现,这样链接器可以使用一组有限的运算符执行简单的表达式求值,即 +、 -、 *、 /、AND、 OR 和()。
2、屏蔽语句或添加注释:
在该语句前面添加分号“;”即可。