(简单地说)分散加载的目的就是让MCU内核知道哪里存的是代码、哪里存的是数据,去哪个特定的地址找到下一步需要运行的函数,并且告诉编译器把每一个编译好的函数、数据放到具体的哪一个物理地址。
(专业地说)分散加载(scatter)文件是一个文本文件,它可以用来描述连接器生成映像文件时需要的信息。通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配Code、RO-Data,RW-Data,ZI-Data等数据的存放地址。如果不用分散加载文件指定,那么ARM连接器会按照默认工程配置默认的方式来生成映像文件。一般情况下我们不需要使用分散加载文件。但对一些特殊的情况例如需要将不同的程序代码存储到不同的地址区域时需要修改分散加载文件。
目录
基本概念
Code:表示程序代码部分;
RO-Data:表示程序定义的所有常量及const型数据;
RW-Data:表示已经初始化的所有静态变量,变量有初值;
ZI-Data:表示未初始化的所有静态变量,变量无初值。
各类型数据声明举例如下:
#define DATA (0x10000000) /* RO-Data*/
char const GcChar = 5; /* RO-Data*/
char GcStr[] = "string."; /* RW-Data*/
char GcZero; /* ZI-Data*/
变量的初始化是在__main()函数中完成的,__main()是由编译器自动创建生成的,可以自动完成所有变量初始化,还可以自动生成拷贝代码等。执行到__main()后,可以看到有两个大的函数:__scatterload()和__rt_entry()。
__main():完成代码和数据的拷贝,并把ZI数据区清零。代码拷贝可将代码拷贝到另外一个映射空间并执行,如将代码拷贝到RAM执行;数据拷贝完成RW段数据赋值;数据区清零完成ZI段数据赋值。以上的代码和分散加载文件密切相关。
__scatterload():负责把RW/RO输出段从装载域地址复制到运行域地址,并完成了ZI运行域的初始化工作。
_rt_entry():进行STACK和HEAP等的初始化。最后_rt_entry跳进main()函数入口。当main()函数执行完后,_rt_entry又将控制权交还给调试器。
ZI-Data变量初始化方式:将所有无初始值变量全部连续放在一起,且初始化为零,它有一个名字叫零段变量,术语(即分散加载文件中的名称)为ZI。
RW-Data完成初始化的方式:RW-Data既占用Flash又占用RAM,而且占用的大小相同,始化方式了简述如下:首先,编译器将有初值的变量全都规划在一起,即为RW段变量,术语为RW;其次,编译器再将它们的初值按顺序存放在Flash中;最后,初始化时,只需要将Flash中RW对应的初值,拷贝到对应的RAM中即可。
分散加载描述文件语法概述
分散加载文件主要由加载时域和运行时域组成。其中:
加载时域,顾名思义用于加载并存储数据,包括Code、RO-Data和RW-Data;
运行时域,用于为运行时分配变量及代码映射空间,包含Code、ZI-Data、RW-Data。
分散加载描述文件的典型组织结构:
加载时域描述
加载时域BNF语法格式下,加载时域般用于规定一个区域来存储只读数据。
load_region_description ::=
load_region_name(base_address|("+"offset))[attribute_list][max_size]
{
execution_region_description+
}
其每项含义解释如所下。
load_region_name:为本加载时域的名称,名称可以按照用户意愿自己定义,该名称中只有前31个字符有意义;
base_designator:用来表示本加载时域的起始地址,可以有下面两种格式中的一种:base_address:表示本加载时域中的对象在连接时的起始地址,地址必须是字对齐的;+offset:表示本加载时域中的对象在连接时的起始地址是在前一个加载时域的结束地址后偏移量offset字节处。本加载时域是第一个加载时域,则它的起始地址即为offset,offset的值必须能被4整除。
attribute_list:指定本加载时域内容的属性,包含以下几种,默认加载时域的属性是ABSOLUTE。ABSOLUTE:绝对地址;PI:与位置无关;RELOC:可重定位;OVERLAY:覆盖;NOCOMPRESS:不能进行压缩。
max_size:指定本加载时域的最大尺寸。如果本加载时域的实际尺寸超过了该值,连接器将报告错误,默认取值为0xFFFFFFFF;
execution_region_description:表示运行时域,后面有个+号,表示其可以有一个或者多个运行时域。
加载时域描述中的组件:
运行时域的描述
运行时域语法格式如下,一般用于为运行时分配变量及代码映射空间。
execution_region_description ::=
exec_region_name(base_address|"+"offset)[attribute_list][max_size|" "length]
{
input_section_description*
}
其每项含义解释如下:
exec_region_name:为本加载时域的名称,名称可以按照用户意愿自己定义,该名称中只有前31个字符有意义;
base_address:用来表示本加载时域的起始地址,可以有下面两种格式中的一种: base_address:表示本加载时域中的对象在连接时的起始地址,地址必须是字对齐的; +offset:表示本加载时域中的对象在连接时的起始地址是在前一个加载时域的结束地址后偏移量offset字节处,offset的值必须能被4整除。
attribute_list:指定本加载时域内容的属性:ABSOLUTE:绝对地址; PI:与位置无关;RELOC:可重定位;OVERLAY:覆盖;FIXED:固定地址。区加载地址和执行地址都是由基址指示符指定的,基址指示符必须是绝对基址,或者偏移为0。 ALIGNalignment:将执行区的对齐约束从4增加到alignment。alignment必须为2的正数幂。如果执行区具有base_address,则它必须为alignment对齐。如果执行区具有offset,则链接器将计算的区基址与alignment边界对齐; EMPTY:在执行区中保留一个给定长度的空白内存块,通常供堆或堆栈使用。ZEROPAD:零初始化的段作为零填充块写入ELF文件,因此,运行时无需使用零进行填充;PADVALUE:定义任何填充的值。如果指定PADVALUE,则必须为其赋值;NOCOMPRESS:不能进行压缩;UNINIT:未初始化的数据。
max_size:指定本加载时域的最大尺寸。如果本加载时域的实际尺寸超过了该值,连接器将报告错误,默认取值为0xFFFFFFFF;
length:如果指定的长度为负值,则将base_address作为区结束地址。它通常与EMPTY一起使用,以表示在内存中变小的堆栈。
input_section_description:指定输入段的内容。
运行时域描述中的组件:
输入段描述
输入段在上述的运行时域中,未给出详细格式,其语法描述如下。
input_section_description ::=
module_select_pattern[ "(" input_section_selector ( "," input_section_selector )* ")" ]
input_section_selector ::=
("+" input_section_attr | input_section_pattern | input_symbol_pattern)
module_select_pattern:目标文件滤波器,支持使用通配符“*”与“?”。其中符号“*”代表零个或多个字符,符号“?”代表单个字符。进行匹配时所有字符不区分大小写。当module_select_pattern与以下内容之一相匹配时,输入段将与模块选择器模式相匹配: 包含段和目标文件的名称;库成员名称(不带前导路径名);库的完整名称(包括路径名),如果名称包含空格,则可以使用通配符简化搜索,例如,使用*libname.lib匹配C:\lib dir\libname.lib。
nput_section_attr:属性选择器与输入段属性相匹配。每个input_section_attr的前面有一个“+”号。如果指定一个模式以匹配输入段名称,名称前面必须有一个“+”号。可以省略紧靠“+”号前面的任何逗号。选择器不区分大小写。可以识别以下选择器:RO-CODE;RO-DATA;RO,同时选择RO-CODE和RO-DATA; RW-DATA;RW-CODE;RW,同时选择RW-CODE和RW-DATA;ZI;ENTRY:即包含ENTRY点的段。可以识别以下同义词:CODE表示RO-CODE;CONST表示RO-DATA;TEXT表示RO;DATA表示RW;BSS表示ZI。可以识别以下伪属性: FIRST;LAST。通过使用特殊模块选择器模式.ANY可以将输入段分配给执行区,而无需考虑其父模块。可以使用一个或多个.ANY模式以任意分配方式填充运行时域。在大多数情况下,使用单个.ANY等效于使用*模块选择器。
输入段描述的组件图:
简单分散加载存储器映射示例
LOAD_ROM 0x0000 0x8000 ; Name of load region (LOAD_ROM),
; Start address for load region (0x0000),
; Maximum size of load region (0x8000)
{
EXEC_ROM 0x0000 0x8000 ; Name of first exec region (EXEC_ROM),
; Start address for exec region (0x0000),
; Maximum size of first exec region (0x8000)
{
* (+RO) ; Place all code and RO data into
; this exec region
}
SRAM 0x10000 0x6000 ; Name of second exec region (SRAM),
; Start address of second exec region (0x10000),
; Maximum size of second exec region (0x6000)
{
* (+RW, +ZI) ; Place all RW and ZI data into
; this exec region
}
}
上述文件的存储器映射图:
复杂分散加载存储器映射示例
LOAD_ROM_1 0x0000 ; Start address for first load region (0x0000)
{
EXEC_ROM_1 0x0000 ; Start address for first exec region (0x0000)
{
program1.o (+RO) ; Place all code and RO data from
; program1.o into this exec region
}
DRAM 0x18000 0x8000 ; Start address for this exec region (0x18000),
; Maximum size of this exec region (0x8000)
{
program1.o (+RW, +ZI) ; Place all RW and ZI data from
; program1.o into this exec region
}
}
LOAD_ROM_2 0x4000 ; Start address for second load region (0x4000)
{
EXEC_ROM_2 0x4000
{
program2.o (+RO) ; Place all code and RO data from
; program2.o into this exec region
}
SRAM 0x8000 0x8000
{
program2.o (+RW, +ZI) ; Place all RW and ZI data from
; program2.o into this exec region
}
}
上述文件的存储器映射图: