13.4 映像文件存储器映射调整
13.4.1 关于分散加载
映像由域(Regions)和输出段(Output Sections)组成。每个域可以有不同的加载地址和执行地址。
分散加载可以更加方便准确的指定映像存储器映射,为映像组件分组和布局提供了全面控制。它能够描述由载入时和执行时分散在存储器映射中的多个区组成的复杂映像映射。虽然,分散加载可以用于简单映像,但它通常仅用于具有复杂存储器映射的映像。
要构建映像的存储器映射,必须向armlink提供以下信息:
· 分组信息:决定如何将各输入段组织成相应的输出段和域;
· 定位信息:决定各域在存储空间的起始地址。
有两种方法可以配置指定映像文件的分组和定位信息:如果映像文件中地址映射关系比较简单,可以使用命令行选项;如果映像文件中地址映射关系比较复杂的情况,可以使用一个配置文件。使用该配置文件可以告诉链接器相关的地址映射关系。配置文件又叫Scatter文件,是一个文本文件,通过下面的链接选项来实现。
-scatter filename
1.为分散加载定义的符号
当armlink使用Scatter文件创建映像时,它创建一些区相关符号。表13.2概括了这些符号的意义。
表13.2 域相关符号
符 号 |
意 义 |
Load$$region_name$$Base |
域的载入地址 |
Image$$region_name$$Base |
域的执行地址 |
Image$$region_name$$Length |
执行域字节长度(4的倍数) |
Image$$region_name$$Limit |
执行区末尾地址 |
Image$$region_name$$ZI$$Base |
执行域中ZI段的执行地址 |
Image$$region_name$$ZI$$Length |
ZI输出段的长度(4的倍数) |
Image$$region_name$$ZI$$Limit |
执行域中ZI段的末尾地址 |
2.使用Scatter文件的优势
链接程序的命令行选项提供了一些对数据和代码布局的控制,但要实现对布局的全面控制命令行输入的指令是远远不够的。在下面一些情况下,就需要使用Scatter文件对映像布局进行控制。
① 需要实现复杂存储器映射
系统中的代码和数据必须放在多个不同存储器区域中,这样连接器必须知道哪个段放在哪个储存器空间的详细信息。这种情况下,最好用Scatter文件实现代码映像的分散加载。
② 系统中存在多种不同类型存储器
许多系统包含多种不同类型存储器,如flash存储器、ROM、SDRAM和快速SRAM。分散载入描述可以将代码和数据放置在最适合的存储器类型中。例如,中断代码可能放在快速SRAM中,以加快中断响应时间,而不频繁使用的配置信息可能放在较慢的flash存储器中。
③ 存储器映射I/O
分散载入描述可以将数据精确定位在内存地址中,而避免数据和内存映射外围地址相冲突。
④ 位于固定位置函数
可以将特定函数放在存储器中的同一个位置,这样即使周围的应用程序已经被修改并重新编译,也可以使具有特定功能的函数地址保持不变。
⑤ 使用符号识别堆和栈
可以为堆和栈的位置定义符号,链接应用程序时可以指定该封闭模块的位置。
随着目前嵌入式系统越来越复杂,系统中可能同时使用flash、ROM和RAM,所以建议在生产系统映像时使用Scatter文件。
3.分散加载命令行选项
可以使用下面的命令行选项使用分散加载文件。
-scatter description_file_name
使用该命令可以使链接器使用命令中给出的description_file_name文件生成最终的映像文件。
4.简单存储器映像举例
例如,一个实际系统的存储器映射如图13.7所示。
图13.7 简单存储器映射
为了实现图13.7的存储器映射,使用图13.8所现实的Scatter文件。
5.复杂存储器映像实现举例
一个复杂存储器映射如图13.9所示。
图13.8 实现简单内存映射的Scatter文件
图13.9 复杂存储器映射实例
为了实现图13.9的存储器映射,使用以下程序所现实的Scatter文件。
LOAD_ROM_1 0x0000 ;第一个加载时域的起始地址
{
EXEC_ROM_1 0x0000 ;第一个运行时域的起始地址
{
programl.o(+RO) ;放置program.o中所以的RO段
{
SRAM 0x9000 ;运行时域的起始地址
{
programl.o(+RW,+ZI) ;放置program.o中所有的RW和ZI段
}
}
LOAD_ROM_2 0x4000 ;第二个加载时域的起始地址
{
EXEC_ROM_2 0x4000 ;运行时域的起始地址
{
program2.o(+RO)
}
DRAM 0x18000 ;运行时域的起始地址
{
program2.o(+RW,+ZI)
}
}
上面两个例子中,简单存储器映射可以使用命令行选项实现,但第二个复杂存储器映射的例子却只能使用Scatter文件实现。
13.4.2 Scatter文件语法
分散载入描述文件是一个文本文件,它向 armlink 描述目标系统的存储器映射。如果从命令行加载Scatter文件,可以使用任意类型的文件扩展名。
在Scatter文件中,用户可以指定以下存储器映像内容:
· 每个载入区的载入地址和最大尺寸;
· 每个载入区的属性;
· 从每个载入区派生的执行区;
· 每个执行区的执行地址和最大尺寸;
· 每个执行区的输入节。
描述文件的格式反映出载入区、执行区和输入节的层次结构。
1.BNF的表示法和语法
所谓BNF(Backus Naur Format)即Scatter文件所用的形式语言。表13.3概括了其所用的符号和语法规则。
表13.3 BNF语法
符 号 |
说 明 |
” |
引号用于表示BNF语法中的字符被用作普通字符。 例如,定义B"+"C,它只能替换为模式B+C。而定义B+C可以替换为模式BC、BBC或BBBC |
A ::= B |
将A定义为B。例如,A::= B"+" | C 表示A相当于B+或C。 在其组件方面,::=表示法用于定义高级结构。每个组件可能还有一个::=定义,对更简单的组件进行定义。 例如,A::=B以及B::= C | D表示定义A相当于模式C或D |
续表
符 号 |
说 明 |
[A] |
可选元素A。例如,A::= B[C]D 表示定义A可以扩展为BD或BCD |
A+ |
元素A可以出现一次或多次。例如,A::= B+表示定义A可以扩展为B、BB或BBB等 |
A* |
元素A可以不出现或多次出现 |
A|B |
出现元素A或B,但不能同时出现 |
(A|B) |
元素A和B组合在一起。 这在使用 | 操作符时,或重复复杂模式时尤其适用。 例如,A::=(B C)+ (D | E) 表示定义A可以扩展为BCD、BCE、BCBCD、BCBCE、BCBCBCD或BCBCBCE |
2.Scatter文件语法概述
分散加载描述scatter_description被定义为一个或多个load_region_description模式:
Scatter_description ::=
load_region_description+
加载域描述load_region_description 被定义为载入区名称,可以选择性地在其后跟随属性、尺寸说明符以及一个或多个执行区描述:
load_region_description ::=
load_region_name (base_address | ("+" offset)) [attributes] [max_size]
"{"
execution_region_description+
"}"
执行域描述execution_region_description 被定义为执行区名称,是一种基址规范,可以选择性地在其后跟随属性、尺寸说明符以及一个或多个输入段描述:
execution_region_description ::=
exec_region_name (base_address | "+" offset) [attribute_list] [max_size | "–"
length]
"{"
input_section_description*
"}"
输入段描述input_section_description被定义为源模块选择程序模式,可以在其后选择性地跟随输入节选择程序:
input_section_description ::=
module_select_pattern
[ "("
("+" input_section_attr | input_section_pattern)
([","] "+" input_section_attr | "," input_section_pattern))*
")" ]
图13.10显示一个典型的分散载入描述文件的内容和组织结构。
图13.10 典型的分散载入描述文件的内容和组织结构
3.加载域描述
一个加载域具有以下属性:
· 名称:链接程序使用它识别不同的加载域;
· 基址:载入视图中的代码和数据的起始地址;
· 属性:可选;
· 最大尺寸:可选;
· 执行区列表:这些执行区标识执行视图中模块的类型和位置。
图13.11显示了加载域的描述。
BNF语法为:
load_region_description ::=
load_region_name (base_address | ("+" offset)) [attribute_list] [ max_size
]
"{"
execution_region_description+
"}"
语法说明如下。
① load_region_name为加载域的名称。只有前31个字符有效。该名称仅用于识别每个域。
注意 |
load_region_name与执行域exec_region_name不同,load_region_name不用于生成Load$$region_ |
② base_address是区中对象的链接地址。base_address必须是一个字对齐数值。
③ +offset描述基址,它从前一个加载域的末尾偏移offset个字节。offset的值必须能被4整除。如果是第一个加载域,则+offset表示该域的基地址是从0之后的offset字节开始。
④ attribute_list指定加载域内容的属性:
· PI:位置独立;
· RELOC:可重定位;
· OVERLAY:重叠;
· ABSOLUTE:绝对地址;
· NOCOMPRESS:代码不被压缩。
可以指定这些属性中的一项(除NOCOMPRESS外,其他4项属性为互斥关系)。默认的加载域属性是ABSOLUTE。具有PI、RELOC或OVERLAY属性之一的加载域可以有重叠的地址范围。对于ABSOLUTE加载域,armlink不允许重叠的地址范围。OVERLAY关键字允许在同一个地址有多个执行区。
注意 |
ARM在RVCT中不提供重叠机制。要在同一个地址使用多个执行区,必须提供自己的重叠管理程序。 |
⑤ max_size:它指定加载域的最大尺寸。(如果指定了可选的max_size值,但分配给该区的字节超过max_size字节,armlink将生成错误。)
⑥ execution_region_description:它指定执行区名称、地址和内容。
4.执行域描述符
执行域具有以下一些属性:
·
域名称;
· 执行域基地址(支持绝对地址的或相对地址的);
· 执行域的最大尺寸(可选);
· 指定执行域属性;
· 一个或多个输入段描述(放在本执行区中的模块)。
图13.12显示了一个典型的执行域描述。
执行域描述符中的BNF语法为:
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整除。如果前面没有执行区(即,这是载入区中的第一个执行区),则+offset表示基址从它所在的载入区的基址之后offset个字节开始。如果使用+offset格式并且所在的加载域具有RELOC属性,则执行区继承该RELOC属性。但是,如果使用固定的base_address,则随后出现的offset不继承RELOC属性。
④ attribute_list指定执行区内容的属性:
· PI:位置独立。
· OVERLAY:重叠。
· ABSOLUTE:绝对地址。域的执行地址由base_designator指定。
· FIXED:固定地址。执行域的加载地址和执行地址都由base_designator指定。base_designator必须是绝对基址,或者偏移量为+0。
· EMPTY:它在执行区中保留一个已知长度的空白存储器块,通常用作堆或栈。
· PADVALUE:指定填充字的默认值,如果在域定义中指定了该属性,则必须为该属性赋值。使用该属性的例子如下。
EXEC 0x10000 PADVALUE 0xffffffff EMPTY ZEROPAD 0x2000
通过该Scatter文件描述符,创建了一个长度为0x2000的域,该域中的所有内容用0xffffffff填充。
注意 |
所指定的域值必须以字为单位。 |
· ZEROPAD 0:初始化一块内容全为0的内存区域,并将其作为一个输入段填充到ELF映像文件中。这样减少了在运行时将某段内存初始化为0的操作。
注意 |
只有根执行区可以使用ZEROPAD属性进行0初始化。对非根执行区使用ZEROPAD属性将出现警告信息,并且忽略该属性。 |
· UNINIT:指示该段为不能被初始化为0。
⑤ max_size为可选的参数,如果分配给域的存储器超过max_size字节,则它指示armlink生成错误。
⑥ -length如果指定的长度为负值,则base_address是域的结束地址。它通常与EMPTY一起使用,以表示在存储器中变小的栈。
当确定执行域属性时,注意以下几点。
① PI、OVERPLAY、FIXED和ABSOLUTE为并列关系属性,某一个执行域只能为这4种属性之一。如果没有指定,ABSOLUTE为其默认属性。
② 使用+offset格式的base_designator的执行区继承前一个执行区的属性(如果它是加载域中的第一个执行区,则继承所在加载域的属性,),或者具有ABSOLUTE属性。
③ 不能为执行域显式指定RELOC属性。该属性只能从前面的执行域或父区继承才能具有RELOC属性。
④ 被指定了PI或OVERLAP属性的执行域,不能有重叠的地址范围。但对于ABLOUTE和FIXED属性的执行域,ARM编译器不允许有重叠的地址范围。
⑤ RW段默认使用压缩属性。如果不想链接器对该段进行压缩,必须在Scatter文件中使用NOCOMPRESS显示声明。
⑥ UNINIT指定执行区中的ZI输出节(如果有)不被初始化为0。使用它可以创建包含未初始化数据或存储器映射I/O的执行区。
5.输入段描述符
输入段由以下部分组成。
· 模块名称,如目标文件名称、库成员名称或库文件名称。模块名称可以使用通配符。
· 输入段名称,或输入节属性,如READ-ONLY或CODE。
图13.13显示了输入段描述符的基本组成。
BNF语法为:
input_section_description ::=
module_select_pattern
["("
("+" input_section_attr | input_section_pattern)
([","] "+" input_section_attr | "," input_section_pattern))*
")"]
其语法说明如下。
① module_select_pattern
这是由文字文本构成的模式。“*”通配符匹配0个或多个字符,而“?”匹配任何单个字符。匹配不区分大小写。
使用*.o可以匹配所有对象。使用*可以匹配所有目标文件和库。
当满足下列条件之一时,链接器认为module_selector_pattern与输入段匹配。
· 包含输入段的目标文件与module_selector_pattern匹配。
· 包含输入段的库成员名称(不带路径名)与module_selector_pattern匹配。
· 从其中提取段的库全名(包含路径名)。如果名称包含空格,使用通配符可以简化搜索。例如,使用*libname.lib匹配C:\lib dir\libname.lib。
另外,ARM链接器支持特殊的模块选择程序模式“.ANY”,允许将输入节分配给执行区,而无需考虑其父模块。使用.ANY以任意分配方式填充执行区。
注意 |
最好不要依赖编译程序生成的或ARM库代码使用的输入段名。因为,这些名称在每次编译之间可以变化,例如编译选项的改变或编译器版本发生变化,都可能引起输入段名称的变化。 |
② 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。
如果对输入段的排列顺序有特殊的要求,如特定的输入段必须是域中的第一个输入节,而包含校验和的输入段必须是最后一个输入段,可以使用FIRST和LAST标记执行区中的第一个和最后一个段。
FIRST或LAST伪属性必须放在属性列表的最后。
特殊的模块选择程序模式“.ANY”允许在不考虑其父模块的情况下,将输入段分配给执行域。使用一个或多个“.ANY”模式以任意分配方式填充执行域。在大多数情况下,使用单个“.ANY”相当于使用“*”模块选择属性。
在分散载入描述文件中不能使用两个“*”选择属性。但是,可以使用两个变形的选择程序,例如,*A和*B,也可以将.ANY选择属性与模块选择属性一起使用。*模块选择属性的优先级比.ANY高。如果删除了文件中包含*选择属性的部分,.ANY选择属性才能在链接时起作用。
在解析所有其他(非.ANY)输入段描述并且将输入段分配给最匹配的执行区之后,才解析使用.ANY模块选择程序模式的input_section_descriptions。如果有一个以上.ANY模式,则链接程序尽可能多地填充第一个.ANY,然后开始填充下一个.ANY。
每个未被分配的剩余输入段将被分配给具有以下特性的执行区:
· 最大的剩余空间(由max_size的值和已分配给该区的输入段的尺寸确定);
· 匹配.ANY的input_section_description;
· 与输入段的存储器属性相匹配的存储器访问属性(如果有);
· input_section_pattern。
13.4.3 Scatter文件典型用法
1.创建启动域
所谓启动域就是加载地址和执行地址相同的域。系统执行的初始入口点必须要在启动域中,否则链接器将报告以下错误。
Entry point (0x00000000) lies within non-root region ER_ROM
在Scatter文件中确定启动域可以使用下面两种方法。
① 使用ABSOLUTE设置执行区属性,并且对第一个执行区及其所在的加载区使用相同的地址。为确保执行域地址和加载域地址相同,可以将加载域的起始地址和执行域的起始地址设为相同的值或者将第一个执行域的地址偏移量设为0。
下面的例子,指定了一个启动域。
BOOT 0x0000 ;加载域的起始地址在0x0
{
EXER 0x0000 ;指定加载域和执行域的地址相同
{
* (+RO) ;必须将启动域包含在内
}
;其他执行域
}
② 使用FIXED执行域属性,确保指定域的载入地址和执行地址相同。
下面的例子显示了使用FIXED属性,将执行域的起始地址固定在ROM中。
BOOT 0x0000 ;加载域的起始地址在0x0
{
EXER 0x0000 ;指定加载域和执行域的地址相同
{
* (+RO) ;必须将启动域包含在内
}
EXER_INIT 0x8000 FIXED
{
init.o(+RO)
}
}
③ 如果使用分散加载,负责创建执行域的代码和数据不能将其自身复制到另一位置,因此启动域必须包含以下内容。
· _main.o和_scatter*.o:包含复制代码和数据的代码。
· Region$$Table和ZISection$$Table段:包含要复制代码和数据的地址。
· _dc*.o:执行代码压缩。
可以使用armlinker产生的InRoot$$Sections符号放置启动代码。因为这些代码被定义为只读属性,所有如果Scatter文件中包含了“* (+RO)”,则表示启动域中包含了这些代码。或者显式的使用InRoot$$Sections符号在Scatter文件中对以上代码进行配置。
下面的例子显示了如何在Scatter文件中使用InRoot$$Sections链接符号,放置启动域。
LOADREG 0x8000 ;
{
ROOT 0x8000
{
* (InRoot$$Sections) ;放置启动域
}
OTHER 0x100000
{
* (RO,+RW,+ZI)
}
;其他Scatter文件描述
}
2.为执行域确定固定地址
可以在执行区分散加载描述中使用FIXED属性来创建根区,该根区在固定地址载入和执行。
FIXED可以用于在单一加载域内(因此通常用于单个ROM设备)创建多个根区。
例如,使用FIXED属性将函数或数据块(如常数表或校验和)放在ROM中的固定地址,这样就可以使用指针很方便的对其进行访问。
下面的例子显示了如何放置单个目标内容。
LOADREG1 0x0 0x10000
{
EXECREG1 0x0 0x1000 ;启动域,包含初始化代码
{ ;将初始化代码放在0x0地址
init.o (Init, +FIRST)
* (+RO) ;随后排放余下的只读数据
}
RAM 0x400000 0x2000 ;将可读可写数据放在0x400000地址
{
* (+RW +ZI)
}
DATABLOCK 0x4FF00 FIXED 0xFF ;执行域放在 0x4FF00地址
{ ;限制该域的最大长度为 0xFF
data.o(+RO-DATA) ;将只读数据放在0x1FF00 和 0x1FFFF之间
}
}
通过上面的Scatter文件,可以将初始化代码放在0x0处,其后是其他RO代码和除了data.o对象中的RO数据之外的所有RO数据;所有全局的RW变量放在RAM中0x400000处;最好将data.o的RO-DATA只读数据表放在地址0x4FF00处,并指定其最大长度为0xFF。
上例将代码或数据对象放在其各自的源文件中,然后放置目标文件域,这些操作方式是ARM公司建议的标准编码方式。为方便起见,可以使用编译指示#pragma和分散载入描述文件放置已命名的域。下面的例子创建模块dump.c并显式命名域。
// file dump.c
int a = 10; // 放入数据域
short b[100]; // 放入bss段
int const c[3] = {1,2,3}; // 放入.constdata段
int func1(int a) {return a*1;} // 放入.text段
#pragma arm section rwdata = "foo", code ="foo"
int x = 5; // 在foo的数据域
char *s = "abc"; // s3在code段, "abc" 在 .constdata
int func2(int x) {return x+1;} // 放入foo的.text段
#pragma arm section code, rwdata // 返回
使用下面的Scatter文件指定上面的代码在内存中的放置位置。如果代码和数据段的名称相同,则首先放置代码段。
FLASH 0x10000000 0x2000000
{
FLASH 0x10000000 0x2000000
{
init.o (Init, +First) ; 放置初始化代码
* (+RO) ;
}
RAM 0x0000
{
vectors.o (Vect, +First) ; 放置向量表
* (+RW,+ZI) ;
}
DUMP 0x08000000
{
dump.o (foo) ;
}
}
通过上面的Scatter文件,将init中的初始化段放在0x10000000地址,并将除foo外的只读数据func1和c[]放在该初始段的后面;接下来的执行域RAM放置向量表;最后的DUMP域放置由#pragma指定的段dump。
3.在代码映像中保留空白域
可以在Scatter中使用 EMPTY 属性为栈保留一个空白存储器块。该存储块不构成载入区的一部分,但指定在执行时使用。由于它创建为虚 ZI区,所以 armlink 使用以下符号访问它:
· Image$$region_name$$ZI$$Base;
· Image$$region_name$$ZI$$Limit;
· Image$$region_name$$ZI$$Length。
如果指定的长度为负值,则Image$$region_name$$ZI$$Limit被视为域的结束地址。它是绝对地址,不是相对地址。下面例子显示了如何在Scatter文件中预留一个空白区域。
LOADREGION 0x700000 ; 加载域的起始地址在0x700000
{ ;
STACK 0x7000000 EMPTY –0x10000 ; 该域的结束地址为0x700000,因为其长度为负
;
region
;
{
; 预留空白区放置栈
}
HEAP +0 EMPTY 0x10000 ; 栈的起始地址在上个预留区域介绍地址
;
;
{
; 预留空白区域放置堆
}
; rest of scatter description...
}
在上面的例子中定义了一个执行域STACK 0x7000000 EMPTY -0x10000,它从地址 (0x7000000-0x1000)开始,在地址0x7000000结束。
在此示例中,链接程序生成符号:
Image$$STACK$$ZI$$Base = 0x6ff0000
Image$$STACK$$ZI$$Limit = 0x7000000
Image$$STACK$$ZI$$Length = 0x1000
Image$$HEAP$$ZI$$Base = 0x7000000
Image$$HEAP$$ZI$$Limit = 0x7010000
Image$$HEAP$$ZI$$Length = 0x1000
EMPTY属性仅适用于执行区。如果在载入区定义中使用EMPTY属性,则链接程序生成警告信息并忽略该属性。链接程序检查用于EMPTY区的地址空间不与任何其他执行区重叠。
4.使用OVERLAY关键字
在ARM以前的编译器中,没有提供地址空间的重叠管理。如果有运行时域地址空间重叠,需要用户自己提供地址空间重叠的管理机制。但在RVDS的编译器中,提供了运行时域属性关键字OVERLAY,用户可以使用该关键字生成自己的重叠空间。
下面例子显示了如何使用OVERLAY关键字,生成运行时域的重叠空间。
LOADREG 0x8000
{
;
STATIC_RAM 0x0 ; 静态RAM区,包含大部分的RW和ZI
{
* (+RW,+ZI)
}
OVERLAY_A_RAM 0x1000 OVERLAY ; 重叠区...
{
module1.o (+RW,+ZI)
}
OVERLAY_B_RAM 0x1000 OVERLAY
{
module2.o (+RW,+ZI)
}
;
}
5.在Scatter文件中使用预处理伪操作
可用在Scatter文件的第一行加上需要编译器进行预处理的操作。语法格式如下所示。
#! <preprocessor> [pre_processor_flags]
LOAD_FLASH ( 0x8000 + ( 0x2 * 0x400 )) ;
例如:
#! armcc -E
联接器可以对预处理的表达式进行简单的计算,可以识别简单的运算符如+、-、×、/、AND和OR,如:
#define AN_ADDRESS (BASE_ADDRESS+(ALIAS_NUMBER*ALIAS_SIZE))
同时,也可以在Scatter文件头加一些预处理的伪操作,如:
#define ADDRESS 0x20000000
#include "include_file_1.h"
#define BASE_ADDRESS 0x8000
#define ALIAS_NUMBER 0x2
#define ALIAS_SIZE 0x400
在Scatter文件中,使用预处理的更详细的信息,请参见ARM相关文件。
13.4.4 等效的简单映像分散载入描述
前面介绍了分散加载的命令行选项,如-ro-base、-rw-base、-reloc、-split、-ropi和-rwpi。但在实际编程时,因为使用Scatter文件可以产生更清晰的内存映像视图,所以最好使用Scatter文件对映像进行加载。
本节详细介绍如何将各分散加载的命令行选项,替换为Scatter文件。
1.-ro-base address选项的替换
使用-ro-base address命令行链接产生的内存映像由一个加载域和三个执行域组成。执行域放在存储器映像中的相邻位置。
选项中的address指定了加载域和第一个执行域的起始地址(加载域和第一个执行域的起始地址相同)。
下面的例子显示了与“-ro-base 0x8000”命令行选项等价的Scatter文件。
LOADREG 0x8000 ;定义加载域的起始地址0x8000
{ ;
ROM +0 ;定义第一个执行域的起始地址,该地址与加载域的起始地址相同,为0x8000
;
{
*(+RO) ;该域放置所有的RO段
}
RAM_RW +0 ;定义第二个执行域,起始地址为0x8000+ROM段大小
;
{
*(+RW) ;将所有的RW代码放置在该段
}
RAM_ZI +0 ;定义ZI段
;ZI段的起始地址为0x8000+ROM段的大小+RAM_RW段的大小
;
{
*(+ZI) ;放置所有的ZI段
}
}
上例中的Scatter文件创建的映像由一个加载域和三个执行域组成。加载域的起始地址为0x8000。三个执行域分别为ROM、RAM_RW和RAM_ZI,它们分别包含RO、RW和ZI输出段。RO和RAM_RW为启动域,RAM_ZI在执行时动态创建。ROM的执行地址是0x8000,通过对执行区描述使用+offset格式的基址指定程序,所有三个执行域在存储器映射中相邻放置,即前一个执行域的末尾放置后一个执行域。
如果链接程序时,将-ro-base选项和-ropi混合使用,则可以生成位置无关代码。
下面的例子显示了与-ro-base 0x8000 -ropi等效的Scatter文件。
LOADREG 0x8000 PI ;加载域的地址为0x8000,并指定该加载域的属性为PI
{
ROM +0 ;第一执行域的地址为0x8000,而且该执行域继承了加载域的PI属性
;所有该域的执行地址是可变的
{
*(+RO) ;放置所有的RO段
}
RAM_RW +0 ABSOLUTE ;使用ABSOLUTE属性代替PI属性
{
*(+RW) ;放置RW段
}
RAM_ZI +0
{
*(+ZI)
}
}
执行域ROM从LOADREG加载域继承 PI 属性。下一个执行域 RAM_RW 被标记为 ABSOLUTE 所以其不再具有PI属性。另外,因为RAM_ZI 域使用了+0的偏移量,所以它从 RAM_RW域继承 ABSOLUTE 属性。
2.-ro-base和-rw-base选项的替换
使用-ro-base和-rw-base选项链接的映像也由一个加载域和三个执行域组成,它与类型1生成的映像十分相似,只是此类映像的RW执行区与RO执行区不相邻。
在-ro-base选项中指定加载域的起始地址,在-rw-base选项中指定执行域的地址。
下面的例子显示与使用-ro-base 0x8000 -rw-base 0x040000等效的分散载入描述。
LOADREG 0x8000 ;定义加载域的起始地址为0x8000
{
ROM_RO +0 ;定义第一个执行域的起始地址为0x8000
{
* (+RO) ;在该域中放置所有的RO段
}
RAM_RW 0x040000 ;第二个执行域名为RAM_RW,起始地址为0x40000
{
* (+RW) ;放置所有的RW段
}
RAM_ZI +0
{
* (+ZI) ;放置所有的ZI段
}
}
该Scatter文件创建的映像有一个名为LOADREG的加载域,载入地址是0x8000。该映像有3个执行区,分别为ROM、RAM_RW和RAM_ZI,它们分别包含RO、RW和ZI输出段。其中,RO域是启动域,执行地址是0x8000,RAM_RW执行域与第一个执行域RAM_RW不相邻。其执行地址是0x040000。紧随其后的执行区RAM_ZI放置所有的ZI数据。
另外,也可以将-rw-base和位置无关选项-rwpi配合使用,将RW输出节的执行区标记为位置独立。
下面的例子显示了使用-ro-base 0x8000 -rw-base 0x40000 -rwpi等效的Scatter文件。
LOADREG 0x0x8000 ;定义加载域的起始地址为0x8000
{
ROM +0 ;定义第一执行域,其起始地址为0x8000
{
*(+RO) ;放置所有RO段
}
RAM_RW 0x40000 PI ;设置第二执行域的属性为PI属性
{
*(+RW)
}
ER_ZI +0 ;继承了PI属性
{
*(+ZI)
}
}
第一个执行域ROM从加载域LOADREG继承ABSOLUTE属性。第二个执行区RAM_RW标记为PI属性。另外,因为ER_ZI区的偏移为+0,所以它从RAM_RW区继承PI属性。
3.-reloc -split选项的替换
使用-split选项生成的映像由两个加载域和三个执行域组成。
使用以下的链接选项重新分割并定位加载域。
· -reloc
组合使用-reloc -split生成具有两个加载域的映像,并且使加载域具有RELOC属性。
· -ro-base address1
指定包含RO输出段的域的载入地址和执行地址。
· -ro-base address2
指定包含RW输出段的域的载入地址和执行地址。
· -split
将默认的单一加载域(包含RO和RW输出段的加载域)分成两个加载域。一个载入域包含RO输出段,另一个包含RW输出段。
下面的例子显示了与使用-ro-base 0x8000 -rw-base 0x040000 -split等效的Scatter文件。
LOADREG1 0x8000 ;指定第一个加载域的起始地址为0x8000
{
ROM +0
{
*(+RO)
}
}
LOADREG2 0x040000 ;第二个加载域的起始地址为0x40000
{
RAM_RW +0
{
*(+RW) ;放置所有的RW段
}
RAM_ZI +0
{
*(+ZI)
}
}
使用上例中的Scatter文件创建的内存映像有两个加载域,分别为LOADREG1和LOADREG2,它们的起始地址分别为0x8000和0x040000。
该映像文件有三个执行域,分别为ROM、RAM_RW和RAM_ZI,它们分别包含RO、RW和ZI输出段。ROM的执行地址是0x8000。
RAM_RW执行域与ROM不相邻。其执行地址是0x040000。
执行域RAM_ZI紧随RAM_RW域放置。
可以使用-reloc选项和-split选项配合使用,指定两个加载域具有RELOC属性。
下面的例子显示与使用-ro-base 0x8000 -rw-base 0x040000 -reloc -split等效的Scatter文件。
LOADREG 0x010000 RELOC
{
ROM + 0
{
* (+RO)
}
}
LOADREG 0x040000 RELOC
{
RAM_RW + 0
{
* (+RW)
}
RAM_ZI +0
{
* (+ZI)
}
}