使用分散文件指定栈和堆
ARM C 库提供了该函数的多种实现__user_setup_stackheap(),并且可以从分散文件中提供的信息中自动为您选择正确的一种。
要选择两个区域内存模型,请在名为ARM_LIB_HEAP和的分散文件中定义两个特殊的执行区域ARM_LIB_STACK。两个区域都有该EMPTY属性。这会导致库选择__user_setup_stackheap()使用符号值的非默认实现:
Image$$ARM_LIB_STACK$$Base
Image$$ARM_LIB_STACK$$ZI$$Limit
Image$$ARM_LIB_HEAP$$Base
Image$$ARM_LIB_HEAP$$ZI$$Limit
1234567
只能指定一个ARM_LIB_STACK
或ARM_LIB_HEAP
区域,并且必须分配一个大小,例如:
ARM_LIB_HEAP0x20100000EMPTY0x100000-0x8000;Heapstartsat1MB
;andgrowsupwards
ARM_LIB_STACK0x20200000EMPTY-0x8000;Stackspacestartsattheend
;ofthe2MBofRAM
;Andgrowsdownwardsfor32KB
12345
可以通过定义名为单一执行区域使用组合的栈和堆区域ARM_LIB_STACKHEAP
,与EMPTY
属性。这会导致__user_setup_stackheap()
使用符号Image$$ARM_LIB_STACKHEAP$$Base
和Image$$ARM_LIB_STACKHEAP$$ZI$$Limit
的值。
注意如果您重新实现__user_setup_stackheap()
,这将覆盖所有库里面的实现。
创建root执行区
要将区域指定为分散文件中的根区域,您可以:
-
指定
ABSOLUTE
为执行区的属性(显式或允许它默认),并为第一个执行区和封闭加载区使用相同的地址。要使执行区地址与加载区地址相同,请执行以下任一操作:
- 为执行区的基地址和加载区的基地址指定相同的数值。
-
指定
+0
加载区中第一个执行区的偏移量。如果+0
为加载区中的所有后续执行区指定零偏移(+0)
,则所有不跟随包含 ZI 的执行区的执行区也是根区。
以下示例显示了隐式定义的根区域:
LR_10x040000;loadregionstartsat0x40000
{;startofexecutionregiondescriptions
ER_RO0x040000;loadaddress=executionaddress
{
*(+RO);allROsections(mustincludesectionwith
;initialentrypoint)
}
...;restofscatter-loadingdescription
}
123456789
-
使用
FIXED
执行区属性可以确保特定区域的加载地址和执行地址相同。您可以使用该FIXED
属性将任何执行区放置在 ROM 中的特定地址。例如,以下内存映射显示了固定执行区:图 8. 固定执行区的内存映射
The following example shows the corresponding scatter-loading description:下面的例子给出了相应分散加载描述:
LR_10x040000;loadregionstartsat0x40000
{;startofexecutionregiondescriptions
ER_RO0x040000;loadaddress=executionaddress
{
*(+RO);ROsectionsotherthanthoseininit.o
}
ER_INIT0x080000FIXED;loadaddressandexecutionaddressofthis
;executionregionarefixedat0x80000
{
init.o(+RO);allROsectionsfrominit.o
}
...;restofscatter-loadingdescription
}
12345678910111213
Examples of misusing the FIXED attribute误用 FIXED 属性 例子
The following example shows common cases where the FIXED execution region attribute is misused:
LR10x8000
{
ER_LOW+00x1000
{
*(+RO)
}
;AtthispointthenextavailableLoadandExecutionaddressis0x8000+sizeof
;contentsofER_LOW.Themaximumsizeislimitedto0x1000sothenextavailableLoad
;andExecutionaddressisatmost0x9000
ER_HIGH0xF0000000FIXED
{
*(+RW+ZI)
}
;Therequiredexecutionaddressandloadaddressis0xF0000000.Thelinkerinserts
;0xF0000000-(0x8000+sizeof(ER_LOW))bytesofpaddingsothatloadaddressmatches
;executionaddress
}
;TheothercommonmisuseofFIXEDistogivealowerexecutionaddressthanthenext
;availableloadaddress.
LR_HIGH0x100000000
{
ER_LOW0x1000FIXED
{
*(+RO)
}
;ThenextavailableloadaddressinLR_HIGHis0x10000000.TherequiredExecution
;addressis0x1000.BecausethenextavailableloadaddressinLR_HIGHmustincrease
;monotonicallythelinkercannotgiveER_LOWaLoadAddresslowerthan0x10000000
}
12345678910111213141516171819202122232425262728293031
使用 FIXED 属性创建根区域
您可以FIXED
在执行区分散文件中使用该属性来创建在固定地址加载和执行的根区。FIXED
用于在单个加载区域内创建多个根区域,因此通常是单个 ROM 设备。例如,您可以使用它来将函数或数据块(例如常量表或校验和)放置在 ROM 中的固定地址,以便可以通过指针轻松访问。
例如,如果您指定将一些初始化代码放置在 ROM 的开头并在 ROM 的末尾放置一个校验和,则某些内存内容可能未被使用。使用*或.ANY模块选择器来填充初始化块末尾和数据块开头之间的区域。
为了使您的代码更易于维护和调试,建议您在分散文件中使用最少的布局规范,并将函数和数据的详细布局留给链接器。
您不能指定已部分链接的组件对象。例如,如果您将对象obj1.o
、obj2.o
和部分链接obj3.o
在一起以产生obj_all.o
,则在生成的对象中会丢弃组件对象名称。因此,您不能按名称引用其中一个对象,例如,obj1.o
。您只能引用组合对象obj_all.o
。
注意在某些情况下,使用FIXED
和 单个加载区域是不合适的。指定固定位置的其他方式是:
- 如果您的加载程序可以处理多个加载区域,请将 RO 代码或数据放在其自己的加载区域中。
-
如果您不要求函数或数据位于 ROM 中的固定位置,请使用
ABSOLUTE
代替FIXED
。然后加载器将数据从加载区复制到 RAM 中的指定地址。ABSOLUTE是默认属性。 -
要将数据结构放置在内存映射 I/O 的位置??,请使用两个加载区域并指定
UNINIT. UNINIT
确保内存位置不会被初始化为零。
在特定地址放置函数和数据
通常,编译器从单个源文件生成 RO、RW 和 ZI 节。这些区域包含源文件中的所有代码和数据。要将单个函数或数据项放置在固定地址,您必须使链接器能够将函数或数据与其余输入文件分开处理。
链接器有两种方法可以让您将段放置在特定地址:
- 您可以创建一个分散文件,该文件在所需地址处定义一个执行区,并带有仅选择一个段的段描述。
-
对于特殊命名的段,链接器可以从段名中获取放置地址。这些专门命名的部分称为
__at
段。
要将函数或变量放置在特定地址,必须将其放置在其自己的段中。有几种方法可以做到这一点:
- 将函数或数据项放在其自己的源文件中。
-
使用到地方变量在一个单独的部分,在一个特定的地址
__attribute__((at(address)))
-
用于在指定段中放置函数和变量
__attribute__((section("name")))
-
使用
AREA
汇编语言中的指令。在汇编代码中,最小的可定位单元是AREA
. -
使用
--split_sections
编译器选项为源文件中的每个函数生成一个 ELF 部分。此选项会导致某些函数的代码大小略有增加,因为它降低了在函数之间共享地址、数据和字符串文字的可能性。但是,当您指定armlink --remove
这可以帮助减少最终固件镜像整体大小,使链接器能够删除未使用的函数。
在没有分散加载的情况下将变量放置在特定地址的示例
此示例显示如何修改源代码以将代码和数据放置在特定地址,并且不需要分散文件:1、创建main.c包含以下代码的源文件:
#include externintsqr(intn1);
intgSquared__attribute__((at(0x5000)));//Placeat0x5000intmain()
{
gSquared=sqr(3);
printf("Valuesquaredis:%d
",gSquared);
}
12345678910
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、编译并链接源:
armcc-c-gfunction.c
armcc-c-gmain.c
armlink--mapfunction.omain.o-osquared.axf
123
--map
选项用于生成内存映射文件即.map文件,同样--autoat
是默认值
在此示例中,__attribute__((at(0x5000)))
指定将全局变量gSquared
放置在绝对地址处0x20000
。gSquared
被放置在执行区ER$$.ARM.__AT_0x00005000
和加载区中LR$$.ARM.__AT_0x00005000
。
The memory map shows:
...
LoadRegionLR$$.ARM.__AT_0x00005000(Base:0x00005000,Size:0x00000000,Max:0x00000004,ABSOLUTE)
ExecutionRegionER$$.ARM.__AT_0x00005000(Base:0x00005000,Size:0x00000004,Max:0x00000004,ABSOLUTE,UNINIT)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000050000x00000004ZeroRW15.ARM.__AT_0x00005000main.o
123456789
使用分散加载将变量放置在指定段中的示例
此示例显示如何使用分散文件修改源代码以将代码和数据放置在特定部分中:1、创建main.c包含以下代码的源文件:
#include externintsqr(intn1);
intgSquared__attribute__((section("foo")));//Placeinsectionfoointmain()
{
gSquared=sqr(3);
printf("Valuesquaredis:%d
",gSquared);
}
12345678910
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、创建scatter.scat包含以下加载区域的分散文件:
LR10x00000x20000
{
ER10x00x2000
{
*(+RO);restofcodeandread-onlydata
}
ER20x80000x2000
{
main.o
}
ER30x100000x2000
{
function.o
*(foo);PlacegSquaredinER3
}
RAM0x200000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x200000
{
*(+RW,+ZI)
}
ARM_LIB_STACK0x800000EMPTY-0x10000
{
}
ARM_LIB_HEAP+0EMPTY0x10000
{
}
}
1234567891011121314151617181920212223242526
该ARM_LIB_STACK
和ARM_LIB_HEAP
都需要,因为程序将与半主机库链接。4、编译并链接
armcc-c-gfunction.c
armcc-c-gmain.c
aarmlink--map--scatter=scatter.scatfunction.omain.o-osquared.axf
123
内存映射显示:
LoadRegionLR1(Base:0x00000000,Size:0x00001778,Max:0x00020000,ABSOLUTE)
...
ExecutionRegionER3(Base:0x00010000,Size:0x00000004,Max:0x00002000,ABSOLUTE)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000100000x00000004DataRW15foomain.o
...
12345678
注意
如果*(foo)从分散文件中省略,则该部分将放置在相同类型的区域中。在这个例子中就是RAM区。
使用分散加载将变量放置在特定地址的示例
1、创建main.c包含以下代码的源文件
#include externintsqr(intn1);
//Placeataddress0x10000constintgValue__attribute__((section(".ARM.__at_0x10000")))=3;
intmain()
{
intsquared;
squared=sqr(gValue);
printf("Valuesquaredis:%d
",squared);
}
12345678910111213
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、创建scatter.scat包含以下加载区域的分散文件:
LR10x0
{
ER10x0
{
*(+RO);restofcodeandread-onlydata
}
ER2+0
{
function.o
*(.ARM.__at_0x10000);PlacegValueat0x10000
}
RAM0x200000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x200000
{
*(+RW,+ZI)
}
ARM_LIB_STACK0x800000EMPTY-0x10000
{
}
ARM_LIB_HEAP+0EMPTY0x10000
{
}
}
12345678910111213141516171819202122
该ARM_LIB_STACK
和ARM_LIB_HEAP
都需要,因为程序将与半主机库链接。
4、编译并链接
armcc-c-gfunction.c
armcc-c-gmain.c
armlink--no_autoat--scatter=scatter.scat--mapfunction.omain.o-osquared.axf
123
内存映射显示变量放置ER2在地址处的执行区中0x11000:
...
ExecutionRegionER2(Base:0x00001598,Size:0x0000ea6c,Max:0xffffffff,ABSOLUTE)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000015980x0000000cCodeRO3.textfunction.o
0x000015a40x0000ea5cPAD
0x000100000x00000004DataRO15.ARM.__at_0x10000main.o...
12345678
在这个例子中,ER1的大小是未知的。因此,gValue可能放在ER1或 中ER2。要确保将gValue其放置在ER2中,您必须包含相应的选择器ER2并与--no_autoat
命令行选项链接。如果省略--no_autoat
, gValue将被放在一个单独的加载区域LR$$.ARM.__AT_0x00010000
,包含执行区域ER$$.ARM.__AT_0x00020000
变量指定段
方式一
intvariable__attribute__((section("foo")))=10;
1
FLASH0x240000000x4000000
{
...;restofcode
ADDER0x08000000
{
file.o(foo);selectsectionfoofromfile.o
}
}
123456789
方式二
//placevariable1inasectioncalled.ARM.__at_0x00008000intvariable1__attribute__((at(0x8000)))=10;
//placevariable2inasectioncalled.ARM.__at_0x8000intvariable2__attribute__((section(".ARM.__at_0x8000")))=10;
12345
ER_FLASH0x80000x2000
{
*(+RO)
*(.ARM.__at_0x8000);
}
12345
函数地址指定
intsqr(intn1)__attribute__((section(".ARM.__at_0x20000")));
intsqr(intn1)
{
returnn1*n1;
}
123456
注意
- 如果不使用分散加载,则该部分将放置在加载区的默认ER_RW执行区中LR_1
- 如果源码中使用了未定义段名(分散加载文件中无此段名),则该部分将放置在定义的 RW 执行区中
-
--autoat
or--no_autoat
不影响放置
使用分散加载显式放置命名部分
以下示例显示如何使用分散加载显式放置命名部分:
LR10x00x10000
{
ER10x00x2000;RootRegion,containinginitcode
{
init.o(INIT,+FIRST);placeinitcodeatexactly0x0
*(+RO);restofcodeandread-onlydata
}
RAM_RW0x400000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x400000
{
*(+RW)
}
RAM_ZI+0
{
*(+ZI)
}
DATABLOCK0x1FF000xFF;executionregionat0x1FF00
{;maximumspaceavailablefortableis0xFF
data.o(+RO-DATA);placeROdatabetween0x1FF00and0x1FFFF
}
}
1234567891011121314151617181920
在这个例子中,分散加载描述放置:
- 初始化代码放在文件的INIT段中init.o。此示例显示该INIT段中的代码首先放置在地址 处0x0,然后是 RO 代码的其余部分以及除对象中的 RO 数据之外的所有 RO 数据data.o。
- RAM 中的所有全局 RW 变量位于0x400000
- data.o中的所有RO-DATA数据放置在0x1FF00
使用.ANY模块选择器放置未分配的段
链接器尝试将输入节放入特定的执行区。对于无法解析的任何输入部分,并且这些部分的放置不重要,您可以使用.ANY
分散文件中的模块选择器。
在大多数情况下,使用单个.ANY
选择器等同于使用*
模块选择器。但是不同的是,您可以.ANY
在多个执行区中指定。
放置未分配段的默认规则
默认情况下,链接器使用以下条件放置未分配的段:
-
在当前拥有最多可用空间的执行区中放置一个未分配的段。您可以使用执行区域属性指定用于未分配段的最大空间量
ANY_SIZE
。 - 按大小降序对部分进行排序。
使用多个.ANY选择器时的放置规则
如果分散文件中存在多个.ANY
选择器,则链接器采用最大大小的未分配段并将该段分配给具有足够可用空间的最具体的.ANY
执行区。例如,.ANY(.text)
被判断为比.ANY(+RO)
更具体。
如果多个执行区具有相同的特性,则该段将分配给具有最多可用剩余空间的执行区。
例如:
-
如果您有两个同样特定的执行区,其中一个的大小限制为0x2000,另一个没有限制,则所有段都分配给第二个无界
.ANY
区域。 -
如果你有两个同样的特定执行区,其中一个大小限制为0x2000和另一个大小限制为0x3000,然后第一个段将被分配到
第二个.ANY
(区域大小限制0x3000),直到第二个.ANY
剩余的大小减少到0x2000。从这一点开始,section在两个.ANY
执行区域之间交替分配。
.ANY优先段
如果您有多个.ANY
带有选择器的部分,您可以给出优先顺序,其中是从零向上的正整数。最高优先级被赋予具有最高整数的选择器。.ANYnum
以下示例显示了如何使用:.ANYnum
lr10x80001024
{
er1+0512
{
.ANY1(+RO);evenlydistributedwither3
}
er2+0256
{
.ANY2(+RO);Highestpriority,sofilledfirst
}
er3+0256
{
.ANY1(+RO);evenlydistributedwither1
}
}
123456789101112131415
控制多个.ANY选择器的输入段的放置
.ANY通过使用不同的放置算法或不同的排序顺序,您可以修改链接器在使用多个选择器时放置未分配输入段的方式。以下命令行选项可用:
-
--any_placement=algorithm
, 其中algorithm是first_fit
,worst_fit
,best_fit
, 或next_fit
之一 -
--any_sort_order=order
,其中order是cmdline
或descending_size
之一
first_fit当您想要按顺序填充区域时使用。best_fit当您想最大程度地填充区域时使用。worst_fit当您想要均匀填充区域时使用。使用相同大小的区域和部分worst_fit循环填充区域。当您需要更具确定性的填充模式时,请使用 next_fit。
如果链接器尝试将区域填充到其极限,就像使用first_fit和 一样best_fit,它可能会过度填充该区域。这是因为在将节分配给.ANY选择器之前,链接器生成的内容(例如填充和单板)是未知的。如果发生这种情况,您可能会看到以下错误:
Error: L6220E: Execution region regionname size (size bytes) exceeds limit (limit bytes).错误:L6220E:执行区regionname大小(size字节)超过限制(limit字节)。
该--any_contingency
选项可防止链接器将区域填充到最大值。它为链接器生成的内容保留了该区域大小的一部分,并且仅当其他区域没有空间时才填充此应急区域。默认情况下为first_fit
和best_fit
算法启用它,因为它们最有可能表现出这种行为。
指定允许放置未分配段的最大尺寸
执行区属性使您能够指定armlink可以用未分配的节填充的区域中的最大大小。ANY_SIZE max_size
使用此关键字时请注意以下限制:
-
max_size
必须小于或等于区域大小 -
您可以
ANY_SIZE
在没有.ANY
选择器的区域上使用,但它会被armlink忽略
当ANY_SIZE存在时,armlink:
- 不覆盖给定的.ANY大小。也就是说,它不会降低优先级,然后尝试在稍后放入更多段。
- 从不重新计算意外事件。
- 从不分配应急空间中的段。
ANY_SIZE不需要--any_contingency
指定。但是,无论何时--any_contingency
指定和ANY_SIZE
未指定,armlink 都会尝试调整意外情况。目标是:
- 永远不会溢出一个.ANY区域
- 永远不要拒绝在应急保留空间中放置一个段。
如果您--any_contingency
在命令行上指定,则对于已ANY_SIZE
指定的区域将忽略它。它通常用于未ANY_SIZE
指定的区域。
以下示例显示了如何使用ANY_SIZE
:
LOAD_REGION0x00x3000
{
ER_10x0ANY_SIZE0xF000x1000
{
.ANY
}
ER_20x0ANY_SIZE0xFB00x1000
{
.ANY
}
ER_30x0ANY_SIZE0x10000x1000
{
.ANY
}
}
123456789101112131415
在这个例子中:
ER_1为链接器生成的内容保留了0x100。ER_2为链接器生成的内容保留了0x50。这和--any_contingency
的自动应急保留类似。ER_3没有预留空间。因此,100%的区域被填满,没有应急保留。省略ANY_SIZE参数会导致98%的区域被填满,只有2%的应急保留。
使用 __at 在外设寄存器上放置
要将未初始化的变量放置在外设寄存器上,您可以使用 ZI__at
部分。假设一个寄存器可用于0x10000000,定义一个__at名为.ARM.__at_0x10000000
. 例如:
intfoo__attribute__((section(".ARM.__at_0x10000000"),zero_init));
1
ER_PERIPHERAL0x10000000UNINIT
{
*(.ARM.__at_0x10000000)
}
1234
使用自动放置,并假设附近没有其他执行区0x10000000,链接器会自动创建一个UNINIT属性为 at的区域0x10000000。该UNINIT属性创建一个包含未初始化数据或内存映射 I/O 的执行区。
预留一个空区域
可以EMPTY在执行区分散加载描述中使用该属性来为堆栈保留一块空内存。
内存块不构成加载区的一部分,而是在执行时分配使用。因为它是作为虚拟 ZI 区域创建的,所以链接器使用以下符号来访问它:
-
Image$$region_name$$ZI$$Base
-
Image$$region_name$$ZI$$Limit
-
Image$$region_name$$ZI$$Length
如果长度为负值,则该地址被视为区域的结束地址。这必须是绝对地址而不是相对地址。
在以下示例中,执行区定义STACK 0x800000 EMPTY -0x10000定义了一个名为的区域STACK,该区域从 address 开始并在 address0x7F0000结束0x800000:
LR_10x80000;loadregionstartsat0x80000
{
STACK0x800000EMPTY-0x10000;regionendsat0x800000becauseofthe
;negativelength.Thestartoftheregion
;iscalculatedusingthelength.
{
;Emptyregionusedtoplacestack
}
HEAP+0EMPTY0x10000;regionstartsattheendofprevious
;region.Endofregioncalculatedusing
;positivelength
{
;Emptyregionusedtoplaceheap
}
...;restofscatter-loadingdescription...
}
12345678910111213141516
注意
为EMPTY执行区域创建的虚拟ZI区域在运行时不会初始化为零。
如果地址是相对的(+offset)形式并且长度是负的,链接器会产生一个错误。下图显示了该示例的图解表示。
图 9. 为堆栈保留一个区域
在本例中,链接器生成符号:
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=0x10000123456
该EMPTY属性仅适用于执行区。链接器生成警告并忽略EMPTY加载区定义中使用的属性。
链接器检查用于该EMPTY区域的地址空间是否与任何其他执行区域不一致。
在分散文件中使用预处理命令
您可以通过 C 预处理器传递分散文件。这允许访问 C 预处理器的所有功能。
使用分散文件中的第一行指定链接器调用以处理文件的预处理器命令。命令的格式如下:
#!preprocessor[
pre_processor_flags
]
123
最典型的命令是#! armcc -E
. 这会通过armcc预处理器传递分散文件。
你可以:
- 将预处理指令添加到分散文件的顶部
- 在分散文件中使用简单的表达式评估。
例如,分散文件file.scat, 可能包含:
#!armcc-E#defineADDRESS0x20000000#include"include_file_1.h"
lr1ADDRESS
{
...
}
123456789
链接器解析预处理后的分散文件并将指令视为注释。
您还可以将分散文件的预处理与–predefine命令行选项结合使用。对于这个例子:
-
修改file.scat以删除指令。
#define ADDRESS 0x20000000
-
指定命令:
armlink --predefine="-DADDRESS=0x20000000" --scatter=file.scat
在分散文件中使用表达式求值以避免填充
使用ALIGN,ALIGNALL或FIXED在分散的文件属性可导致在镜像中的大量填充的。要删除此填充,您可以使用表达式计算来指定加载区和执行区的起始地址。内置函数AlignExpr可用于帮助您指定地址表达式。
避免在分散文件中填充的示例以下分散文件生成带有填充的图像:
LR10x4000
{
ER1+0ALIGN0x8000
{
...
}
}
1234567
使用ALIGN关键字ER1
与0x8000
加载视图和执行视图中的边界对齐。要在加载视图中对齐,链接器必须插入0x4000填充字节。
以下分散文件生成没有填充的图像:
LR10x4000
{
ER1AlignExpr(+0,0x8000)
{
...
}
}
1234567
使用AlignExpr
的结果+0
与0x8000
边界对齐。这将创建一个执行区,其加载地址为0x4000但执行地址为0x8000。
审核编辑:郭婷
Image$$ARM_LIB_STACK$$Base
Image$$ARM_LIB_STACK$$ZI$$Limit
Image$$ARM_LIB_HEAP$$Base
Image$$ARM_LIB_HEAP$$ZI$$Limit
1234567
只能指定一个ARM_LIB_STACK
或ARM_LIB_HEAP
区域,并且必须分配一个大小,例如:
ARM_LIB_HEAP0x20100000EMPTY0x100000-0x8000;Heapstartsat1MB
;andgrowsupwards
ARM_LIB_STACK0x20200000EMPTY-0x8000;Stackspacestartsattheend
;ofthe2MBofRAM
;Andgrowsdownwardsfor32KB
12345
可以通过定义名为单一执行区域使用组合的栈和堆区域ARM_LIB_STACKHEAP
,与EMPTY
属性。这会导致__user_setup_stackheap()
使用符号Image$$ARM_LIB_STACKHEAP$$Base
和Image$$ARM_LIB_STACKHEAP$$ZI$$Limit
的值。
注意如果您重新实现__user_setup_stackheap()
,这将覆盖所有库里面的实现。
创建root执行区
要将区域指定为分散文件中的根区域,您可以:
-
指定
ABSOLUTE
为执行区的属性(显式或允许它默认),并为第一个执行区和封闭加载区使用相同的地址。要使执行区地址与加载区地址相同,请执行以下任一操作:
- 为执行区的基地址和加载区的基地址指定相同的数值。
-
指定
+0
加载区中第一个执行区的偏移量。如果+0
为加载区中的所有后续执行区指定零偏移(+0)
,则所有不跟随包含 ZI 的执行区的执行区也是根区。
以下示例显示了隐式定义的根区域:
LR_10x040000;loadregionstartsat0x40000
{;startofexecutionregiondescriptions
ER_RO0x040000;loadaddress=executionaddress
{
*(+RO);allROsections(mustincludesectionwith
;initialentrypoint)
}
...;restofscatter-loadingdescription
}
123456789
-
使用
FIXED
执行区属性可以确保特定区域的加载地址和执行地址相同。您可以使用该FIXED
属性将任何执行区放置在 ROM 中的特定地址。例如,以下内存映射显示了固定执行区:图 8. 固定执行区的内存映射
The following example shows the corresponding scatter-loading description:下面的例子给出了相应分散加载描述:
LR_10x040000;loadregionstartsat0x40000
{;startofexecutionregiondescriptions
ER_RO0x040000;loadaddress=executionaddress
{
*(+RO);ROsectionsotherthanthoseininit.o
}
ER_INIT0x080000FIXED;loadaddressandexecutionaddressofthis
;executionregionarefixedat0x80000
{
init.o(+RO);allROsectionsfrominit.o
}
...;restofscatter-loadingdescription
}
12345678910111213
Examples of misusing the FIXED attribute误用 FIXED 属性 例子
The following example shows common cases where the FIXED execution region attribute is misused:
LR10x8000
{
ER_LOW+00x1000
{
*(+RO)
}
;AtthispointthenextavailableLoadandExecutionaddressis0x8000+sizeof
;contentsofER_LOW.Themaximumsizeislimitedto0x1000sothenextavailableLoad
;andExecutionaddressisatmost0x9000
ER_HIGH0xF0000000FIXED
{
*(+RW+ZI)
}
;Therequiredexecutionaddressandloadaddressis0xF0000000.Thelinkerinserts
;0xF0000000-(0x8000+sizeof(ER_LOW))bytesofpaddingsothatloadaddressmatches
;executionaddress
}
;TheothercommonmisuseofFIXEDistogivealowerexecutionaddressthanthenext
;availableloadaddress.
LR_HIGH0x100000000
{
ER_LOW0x1000FIXED
{
*(+RO)
}
;ThenextavailableloadaddressinLR_HIGHis0x10000000.TherequiredExecution
;addressis0x1000.BecausethenextavailableloadaddressinLR_HIGHmustincrease
;monotonicallythelinkercannotgiveER_LOWaLoadAddresslowerthan0x10000000
}
12345678910111213141516171819202122232425262728293031
使用 FIXED 属性创建根区域
您可以FIXED
在执行区分散文件中使用该属性来创建在固定地址加载和执行的根区。FIXED
用于在单个加载区域内创建多个根区域,因此通常是单个 ROM 设备。例如,您可以使用它来将函数或数据块(例如常量表或校验和)放置在 ROM 中的固定地址,以便可以通过指针轻松访问。
例如,如果您指定将一些初始化代码放置在 ROM 的开头并在 ROM 的末尾放置一个校验和,则某些内存内容可能未被使用。使用*或.ANY模块选择器来填充初始化块末尾和数据块开头之间的区域。
为了使您的代码更易于维护和调试,建议您在分散文件中使用最少的布局规范,并将函数和数据的详细布局留给链接器。
您不能指定已部分链接的组件对象。例如,如果您将对象obj1.o
、obj2.o
和部分链接obj3.o
在一起以产生obj_all.o
,则在生成的对象中会丢弃组件对象名称。因此,您不能按名称引用其中一个对象,例如,obj1.o
。您只能引用组合对象obj_all.o
。
注意在某些情况下,使用FIXED
和 单个加载区域是不合适的。指定固定位置的其他方式是:
- 如果您的加载程序可以处理多个加载区域,请将 RO 代码或数据放在其自己的加载区域中。
-
如果您不要求函数或数据位于 ROM 中的固定位置,请使用
ABSOLUTE
代替FIXED
。然后加载器将数据从加载区复制到 RAM 中的指定地址。ABSOLUTE是默认属性。 -
要将数据结构放置在内存映射 I/O 的位置??,请使用两个加载区域并指定
UNINIT. UNINIT
确保内存位置不会被初始化为零。
在特定地址放置函数和数据
通常,编译器从单个源文件生成 RO、RW 和 ZI 节。这些区域包含源文件中的所有代码和数据。要将单个函数或数据项放置在固定地址,您必须使链接器能够将函数或数据与其余输入文件分开处理。
链接器有两种方法可以让您将段放置在特定地址:
- 您可以创建一个分散文件,该文件在所需地址处定义一个执行区,并带有仅选择一个段的段描述。
-
对于特殊命名的段,链接器可以从段名中获取放置地址。这些专门命名的部分称为
__at
段。
要将函数或变量放置在特定地址,必须将其放置在其自己的段中。有几种方法可以做到这一点:
- 将函数或数据项放在其自己的源文件中。
-
使用到地方变量在一个单独的部分,在一个特定的地址
__attribute__((at(address)))
-
用于在指定段中放置函数和变量
__attribute__((section("name")))
-
使用
AREA
汇编语言中的指令。在汇编代码中,最小的可定位单元是AREA
. -
使用
--split_sections
编译器选项为源文件中的每个函数生成一个 ELF 部分。此选项会导致某些函数的代码大小略有增加,因为它降低了在函数之间共享地址、数据和字符串文字的可能性。但是,当您指定armlink --remove
这可以帮助减少最终固件镜像整体大小,使链接器能够删除未使用的函数。
在没有分散加载的情况下将变量放置在特定地址的示例
此示例显示如何修改源代码以将代码和数据放置在特定地址,并且不需要分散文件:1、创建main.c包含以下代码的源文件:
#include externintsqr(intn1);
intgSquared__attribute__((at(0x5000)));//Placeat0x5000intmain()
{
gSquared=sqr(3);
printf("Valuesquaredis:%d
",gSquared);
}
12345678910
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、编译并链接源:
armcc-c-gfunction.c
armcc-c-gmain.c
armlink--mapfunction.omain.o-osquared.axf
123
--map
选项用于生成内存映射文件即.map文件,同样--autoat
是默认值
在此示例中,__attribute__((at(0x5000)))
指定将全局变量gSquared
放置在绝对地址处0x20000
。gSquared
被放置在执行区ER$$.ARM.__AT_0x00005000
和加载区中LR$$.ARM.__AT_0x00005000
。
The memory map shows:
...
LoadRegionLR$$.ARM.__AT_0x00005000(Base:0x00005000,Size:0x00000000,Max:0x00000004,ABSOLUTE)
ExecutionRegionER$$.ARM.__AT_0x00005000(Base:0x00005000,Size:0x00000004,Max:0x00000004,ABSOLUTE,UNINIT)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000050000x00000004ZeroRW15.ARM.__AT_0x00005000main.o
123456789
使用分散加载将变量放置在指定段中的示例
此示例显示如何使用分散文件修改源代码以将代码和数据放置在特定部分中:1、创建main.c包含以下代码的源文件:
#include externintsqr(intn1);
intgSquared__attribute__((section("foo")));//Placeinsectionfoointmain()
{
gSquared=sqr(3);
printf("Valuesquaredis:%d
",gSquared);
}
12345678910
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、创建scatter.scat包含以下加载区域的分散文件:
LR10x00000x20000
{
ER10x00x2000
{
*(+RO);restofcodeandread-onlydata
}
ER20x80000x2000
{
main.o
}
ER30x100000x2000
{
function.o
*(foo);PlacegSquaredinER3
}
RAM0x200000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x200000
{
*(+RW,+ZI)
}
ARM_LIB_STACK0x800000EMPTY-0x10000
{
}
ARM_LIB_HEAP+0EMPTY0x10000
{
}
}
1234567891011121314151617181920212223242526
该ARM_LIB_STACK
和ARM_LIB_HEAP
都需要,因为程序将与半主机库链接。4、编译并链接
armcc-c-gfunction.c
armcc-c-gmain.c
aarmlink--map--scatter=scatter.scatfunction.omain.o-osquared.axf
123
内存映射显示:
LoadRegionLR1(Base:0x00000000,Size:0x00001778,Max:0x00020000,ABSOLUTE)
...
ExecutionRegionER3(Base:0x00010000,Size:0x00000004,Max:0x00002000,ABSOLUTE)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000100000x00000004DataRW15foomain.o
...
12345678
注意
如果*(foo)从分散文件中省略,则该部分将放置在相同类型的区域中。在这个例子中就是RAM区。
使用分散加载将变量放置在特定地址的示例
1、创建main.c包含以下代码的源文件
#include externintsqr(intn1);
//Placeataddress0x10000constintgValue__attribute__((section(".ARM.__at_0x10000")))=3;
intmain()
{
intsquared;
squared=sqr(gValue);
printf("Valuesquaredis:%d
",squared);
}
12345678910111213
2、创建function.c包含以下代码的源文件:
intsqr(intn1)
{
returnn1*n1;
}
1234
3、创建scatter.scat包含以下加载区域的分散文件:
LR10x0
{
ER10x0
{
*(+RO);restofcodeandread-onlydata
}
ER2+0
{
function.o
*(.ARM.__at_0x10000);PlacegValueat0x10000
}
RAM0x200000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x200000
{
*(+RW,+ZI)
}
ARM_LIB_STACK0x800000EMPTY-0x10000
{
}
ARM_LIB_HEAP+0EMPTY0x10000
{
}
}
12345678910111213141516171819202122
该ARM_LIB_STACK
和ARM_LIB_HEAP
都需要,因为程序将与半主机库链接。
4、编译并链接
armcc-c-gfunction.c
armcc-c-gmain.c
armlink--no_autoat--scatter=scatter.scat--mapfunction.omain.o-osquared.axf
123
内存映射显示变量放置ER2在地址处的执行区中0x11000:
...
ExecutionRegionER2(Base:0x00001598,Size:0x0000ea6c,Max:0xffffffff,ABSOLUTE)
BaseAddrSizeTypeAttrIdxESectionNameObject
0x000015980x0000000cCodeRO3.textfunction.o
0x000015a40x0000ea5cPAD
0x000100000x00000004DataRO15.ARM.__at_0x10000main.o...
12345678
在这个例子中,ER1的大小是未知的。因此,gValue可能放在ER1或 中ER2。要确保将gValue其放置在ER2中,您必须包含相应的选择器ER2并与--no_autoat
命令行选项链接。如果省略--no_autoat
, gValue将被放在一个单独的加载区域LR$$.ARM.__AT_0x00010000
,包含执行区域ER$$.ARM.__AT_0x00020000
变量指定段
方式一
intvariable__attribute__((section("foo")))=10;
1
FLASH0x240000000x4000000
{
...;restofcode
ADDER0x08000000
{
file.o(foo);selectsectionfoofromfile.o
}
}
123456789
方式二
//placevariable1inasectioncalled.ARM.__at_0x00008000intvariable1__attribute__((at(0x8000)))=10;
//placevariable2inasectioncalled.ARM.__at_0x8000intvariable2__attribute__((section(".ARM.__at_0x8000")))=10;
12345
ER_FLASH0x80000x2000
{
*(+RO)
*(.ARM.__at_0x8000);
}
12345
函数地址指定
intsqr(intn1)__attribute__((section(".ARM.__at_0x20000")));
intsqr(intn1)
{
returnn1*n1;
}
123456
注意
- 如果不使用分散加载,则该部分将放置在加载区的默认ER_RW执行区中LR_1
- 如果源码中使用了未定义段名(分散加载文件中无此段名),则该部分将放置在定义的 RW 执行区中
-
--autoat
or--no_autoat
不影响放置
使用分散加载显式放置命名部分
以下示例显示如何使用分散加载显式放置命名部分:
LR10x00x10000
{
ER10x00x2000;RootRegion,containinginitcode
{
init.o(INIT,+FIRST);placeinitcodeatexactly0x0
*(+RO);restofcodeandread-onlydata
}
RAM_RW0x400000(0x1FF00-0x2000);RW&ZIdatatobeplacedat0x400000
{
*(+RW)
}
RAM_ZI+0
{
*(+ZI)
}
DATABLOCK0x1FF000xFF;executionregionat0x1FF00
{;maximumspaceavailablefortableis0xFF
data.o(+RO-DATA);placeROdatabetween0x1FF00and0x1FFFF
}
}
1234567891011121314151617181920
在这个例子中,分散加载描述放置:
- 初始化代码放在文件的INIT段中init.o。此示例显示该INIT段中的代码首先放置在地址 处0x0,然后是 RO 代码的其余部分以及除对象中的 RO 数据之外的所有 RO 数据data.o。
- RAM 中的所有全局 RW 变量位于0x400000
- data.o中的所有RO-DATA数据放置在0x1FF00
使用.ANY模块选择器放置未分配的段
链接器尝试将输入节放入特定的执行区。对于无法解析的任何输入部分,并且这些部分的放置不重要,您可以使用.ANY
分散文件中的模块选择器。
在大多数情况下,使用单个.ANY
选择器等同于使用*
模块选择器。但是不同的是,您可以.ANY
在多个执行区中指定。
放置未分配段的默认规则
默认情况下,链接器使用以下条件放置未分配的段:
-
在当前拥有最多可用空间的执行区中放置一个未分配的段。您可以使用执行区域属性指定用于未分配段的最大空间量
ANY_SIZE
。 - 按大小降序对部分进行排序。
使用多个.ANY选择器时的放置规则
如果分散文件中存在多个.ANY
选择器,则链接器采用最大大小的未分配段并将该段分配给具有足够可用空间的最具体的.ANY
执行区。例如,.ANY(.text)
被判断为比.ANY(+RO)
更具体。
如果多个执行区具有相同的特性,则该段将分配给具有最多可用剩余空间的执行区。
例如:
-
如果您有两个同样特定的执行区,其中一个的大小限制为0x2000,另一个没有限制,则所有段都分配给第二个无界
.ANY
区域。 -
如果你有两个同样的特定执行区,其中一个大小限制为0x2000和另一个大小限制为0x3000,然后第一个段将被分配到
第二个.ANY
(区域大小限制0x3000),直到第二个.ANY
剩余的大小减少到0x2000。从这一点开始,section在两个.ANY
执行区域之间交替分配。
.ANY优先段
如果您有多个.ANY
带有选择器的部分,您可以给出优先顺序,其中是从零向上的正整数。最高优先级被赋予具有最高整数的选择器。.ANYnum
以下示例显示了如何使用:.ANYnum
lr10x80001024
{
er1+0512
{
.ANY1(+RO);evenlydistributedwither3
}
er2+0256
{
.ANY2(+RO);Highestpriority,sofilledfirst
}
er3+0256
{
.ANY1(+RO);evenlydistributedwither1
}
}
123456789101112131415
控制多个.ANY选择器的输入段的放置
.ANY通过使用不同的放置算法或不同的排序顺序,您可以修改链接器在使用多个选择器时放置未分配输入段的方式。以下命令行选项可用:
-
--any_placement=algorithm
, 其中algorithm是first_fit
,worst_fit
,best_fit
, 或next_fit
之一 -
--any_sort_order=order
,其中order是cmdline
或descending_size
之一
first_fit当您想要按顺序填充区域时使用。best_fit当您想最大程度地填充区域时使用。worst_fit当您想要均匀填充区域时使用。使用相同大小的区域和部分worst_fit循环填充区域。当您需要更具确定性的填充模式时,请使用 next_fit。
如果链接器尝试将区域填充到其极限,就像使用first_fit和 一样best_fit,它可能会过度填充该区域。这是因为在将节分配给.ANY选择器之前,链接器生成的内容(例如填充和单板)是未知的。如果发生这种情况,您可能会看到以下错误:
Error: L6220E: Execution region regionname size (size bytes) exceeds limit (limit bytes).错误:L6220E:执行区regionname大小(size字节)超过限制(limit字节)。
该--any_contingency
选项可防止链接器将区域填充到最大值。它为链接器生成的内容保留了该区域大小的一部分,并且仅当其他区域没有空间时才填充此应急区域。默认情况下为first_fit
和best_fit
算法启用它,因为它们最有可能表现出这种行为。
指定允许放置未分配段的最大尺寸
执行区属性使您能够指定armlink可以用未分配的节填充的区域中的最大大小。ANY_SIZE max_size
使用此关键字时请注意以下限制:
-
max_size
必须小于或等于区域大小 -
您可以
ANY_SIZE
在没有.ANY
选择器的区域上使用,但它会被armlink忽略
当ANY_SIZE存在时,armlink:
- 不覆盖给定的.ANY大小。也就是说,它不会降低优先级,然后尝试在稍后放入更多段。
- 从不重新计算意外事件。
- 从不分配应急空间中的段。
ANY_SIZE不需要--any_contingency
指定。但是,无论何时--any_contingency
指定和ANY_SIZE
未指定,armlink 都会尝试调整意外情况。目标是:
- 永远不会溢出一个.ANY区域
- 永远不要拒绝在应急保留空间中放置一个段。
如果您--any_contingency
在命令行上指定,则对于已ANY_SIZE
指定的区域将忽略它。它通常用于未ANY_SIZE
指定的区域。
以下示例显示了如何使用ANY_SIZE
:
LOAD_REGION0x00x3000
{
ER_10x0ANY_SIZE0xF000x1000
{
.ANY
}
ER_20x0ANY_SIZE0xFB00x1000
{
.ANY
}
ER_30x0ANY_SIZE0x10000x1000
{
.ANY
}
}
123456789101112131415
在这个例子中:
ER_1为链接器生成的内容保留了0x100。ER_2为链接器生成的内容保留了0x50。这和--any_contingency
的自动应急保留类似。ER_3没有预留空间。因此,100%的区域被填满,没有应急保留。省略ANY_SIZE参数会导致98%的区域被填满,只有2%的应急保留。
使用 __at 在外设寄存器上放置
要将未初始化的变量放置在外设寄存器上,您可以使用 ZI__at
部分。假设一个寄存器可用于0x10000000,定义一个__at名为.ARM.__at_0x10000000
. 例如:
intfoo__attribute__((section(".ARM.__at_0x10000000"),zero_init));
1
ER_PERIPHERAL0x10000000UNINIT
{
*(.ARM.__at_0x10000000)
}
1234
使用自动放置,并假设附近没有其他执行区0x10000000,链接器会自动创建一个UNINIT属性为 at的区域0x10000000。该UNINIT属性创建一个包含未初始化数据或内存映射 I/O 的执行区。
预留一个空区域
可以EMPTY在执行区分散加载描述中使用该属性来为堆栈保留一块空内存。
内存块不构成加载区的一部分,而是在执行时分配使用。因为它是作为虚拟 ZI 区域创建的,所以链接器使用以下符号来访问它:
-
Image$$region_name$$ZI$$Base
-
Image$$region_name$$ZI$$Limit
-
Image$$region_name$$ZI$$Length
如果长度为负值,则该地址被视为区域的结束地址。这必须是绝对地址而不是相对地址。
在以下示例中,执行区定义STACK 0x800000 EMPTY -0x10000定义了一个名为的区域STACK,该区域从 address 开始并在 address0x7F0000结束0x800000:
LR_10x80000;loadregionstartsat0x80000
{
STACK0x800000EMPTY-0x10000;regionendsat0x800000becauseofthe
;negativelength.Thestartoftheregion
;iscalculatedusingthelength.
{
;Emptyregionusedtoplacestack
}
HEAP+0EMPTY0x10000;regionstartsattheendofprevious
;region.Endofregioncalculatedusing
;positivelength
{
;Emptyregionusedtoplaceheap
}
...;restofscatter-loadingdescription...
}
12345678910111213141516
注意
为EMPTY执行区域创建的虚拟ZI区域在运行时不会初始化为零。
如果地址是相对的(+offset)形式并且长度是负的,链接器会产生一个错误。下图显示了该示例的图解表示。
图 9. 为堆栈保留一个区域
在本例中,链接器生成符号:
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=0x10000123456
该EMPTY属性仅适用于执行区。链接器生成警告并忽略EMPTY加载区定义中使用的属性。
链接器检查用于该EMPTY区域的地址空间是否与任何其他执行区域不一致。
在分散文件中使用预处理命令
您可以通过 C 预处理器传递分散文件。这允许访问 C 预处理器的所有功能。
使用分散文件中的第一行指定链接器调用以处理文件的预处理器命令。命令的格式如下:
#!preprocessor[
pre_processor_flags
]
123
最典型的命令是#! armcc -E
. 这会通过armcc预处理器传递分散文件。
你可以:
- 将预处理指令添加到分散文件的顶部
- 在分散文件中使用简单的表达式评估。
例如,分散文件file.scat, 可能包含:
#!armcc-E#defineADDRESS0x20000000#include"include_file_1.h"
lr1ADDRESS
{
...
}
123456789
链接器解析预处理后的分散文件并将指令视为注释。
您还可以将分散文件的预处理与–predefine命令行选项结合使用。对于这个例子:
-
修改file.scat以删除指令。
#define ADDRESS 0x20000000
-
指定命令:
armlink --predefine="-DADDRESS=0x20000000" --scatter=file.scat
在分散文件中使用表达式求值以避免填充
使用ALIGN,ALIGNALL或FIXED在分散的文件属性可导致在镜像中的大量填充的。要删除此填充,您可以使用表达式计算来指定加载区和执行区的起始地址。内置函数AlignExpr可用于帮助您指定地址表达式。
避免在分散文件中填充的示例以下分散文件生成带有填充的图像:
LR10x4000
{
ER1+0ALIGN0x8000
{
...
}
}
1234567
使用ALIGN关键字ER1
与0x8000
加载视图和执行视图中的边界对齐。要在加载视图中对齐,链接器必须插入0x4000填充字节。
以下分散文件生成没有填充的图像:
LR10x4000
{
ER1AlignExpr(+0,0x8000)
{
...
}
}
1234567
使用AlignExpr
的结果+0
与0x8000
边界对齐。这将创建一个执行区,其加载地址为0x4000但执行地址为0x8000。
审核编辑:郭婷
-
代码
+关注
关注
30文章
4744浏览量
68343
原文标题:在分散文件中使用表达式求值以避免填充
文章出处:【微信号:技术让梦想更伟大,微信公众号:技术让梦想更伟大】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论