您的位置 首页 芯闻

ARM系列之涣散加载描述符(scatte)文件的使用

在上一篇中,提到了分散加载描述符的应用场景。一般对于简单的代码分布,不需要使用这样的文件,直接借助于编译器中的simple选项,配置入口…

在上一篇中,提到了涣散加载描绘符的运用场景。一般关于简略的代码散布,不需求运用这样的文件,直接借助于编译器中的simple选项,装备进口地址,RW和RO地址就能够运转了。如下图所示:

可是关于一些杂乱的场景,就需求涣散家在描绘符文件,比方:

1 . 定位方针外设

  运用涣散加载,能够将用户界说的结构体或代码定位到指定物理地址上的外设,这种外设能够是定时器、实时时钟、静态SRAM或许是两个处理器间用于数据和指令通讯的双端口存储器等。在程序中不用直接拜访相应外设,只需拜访相应的内存变量即可完结对指定外设的操作,由于相应的内存变量定位在指定的外设上。这样,对外设的拜访看不到相应的指针操作,对结构体成员的拜访即可完结对外设相应存储单元的拜访,让程序员感觉到似乎没有外设,只需内存。

  例如,一个带有两个32位寄存器的定时器外设,在体系中的物理地址为Ox04000000,其C言语结构描绘如下:

  要运用涣散加载将上述结构体定位到Ox04000000的物理地址,能够将上述结构体放在一个文件名为timer_regs.c中,并在涣散加载文件中指定即可,如下:

  特点UNINIT是避免在运用程序发动时对该履行段的ZI数据段初始化为零。

  在程序衔接后,经过Image map文件可查看该ZI数据段的存储器分配状况:

  Execution Region TIMER(Base:Ox04000000,Size:0x00000008,Max:0xffffffff,ABSOLUTE,UNINIT)Base Addr Size Type Attr Idx E Section Name 0bi ectOx04000000 0x00000008 Zero RW 32.bss tlmer_regs.o从Image map文件能够看出,该TIMER履行区定位在物理地址0x04000000,即结构体timer_regs定位在Ox04000000,因而,在程序中对结构体的操作便是对定时器的操作。

2 . 界说超大型结构体数组

  涣散加载机制在供给将指定代码和数据定位在指定物理地址的才干的一同,也供给了一种代码切割机制——能够将指定的零初始化段(ZI段)从可履行代码中别离出来。这样终究生成的烧入ROM或Flash中的镜像文件就不包括那部分切割了的零初始化段,即便该零初始化段再大,也不影响终究生成的镜像文件的巨细。但不选用涣散加载机制,零初始化段在编译衔接后是直接生成到镜像文件中的。它的巨细直接影响终究要烧写的文件的巨细,且零初始化段的巨细还取决于内存的巨细,它不能大到超越内存的巨细;而选用涣散加载机制,能够将某个零初始化段定位到非内存地址的一个存储器外设上,如NVRAM(非易失性随机存储器)。

  笔者曾在一个实践工程中选用这种涣散加载机制,将一个2MB的结构体数组定位到外部NVRAM中,用于记载设备在作业进程中收集到的数据;而在本体系中,ARM处理器的内存只需256 KB,Flash存储器也只需2 MB。假如不选用涣散加载,程序底子无法运转,也不能烧写到Flash中。

  选用涣散加载,把对杂乱外设的拜访变成对结构体数组的拜访,使程序代码精简易懂。对程序员来说,对结构体数组的操作仍是和内存变量的操作相同的。

3. 某些特别运用,需求将代码段或许数据段的部分放置在指定方位,便利更新,或许其他加密等原因。

4. 某些特别运用,需求固定函数地址的时分,能够将那个函数放于固定的区域,而不论其他程序有误改动。

5. 关于程序中的一些装备型的变量,或许需求会集放置在一个区域,便利下次直接更新那块存储空间。

——————-o———————————————–

下面侧重描绘涣散描绘符的写法,涣散描绘文件的类型为 .scf 。写完后,能够经过ADS进行加载。

涣散装载(Scatlerloading)
在实践的嵌入式体系中,ADS供给的缺省存储器映射是不能满足要求的。用户的方针硬件一般有多个存储器设备坐落不同的方位,而且这些存储器设备在程序装载和运转时或许还有不同的装备。
Scattertoading能够经过一个文本文件来指定一段代码或数据在加载和运转时在存储器中的不同方位。这个文本文件scatterfile在命令行中由-scatter开关指定,例如:
armlink_scatterscat.scffilel.ofile2.0
在scatterfile中能够为每一个代码或数据区在装载和履行时指定不同的存储区域地址,Scatlertoading的存储区块能够分红二种类型:
装载区:当体系发动或加载时运用程序的寄存区。
履行区:体系发动后,运用程序进行履行和数据拜访的存储器区域,体系在实时运转时能够有一个或多个履行块。
映像中一切的代码和数据都有一个装载地址和运转地址(二者或许相同也或许不同,视详细状况而定)。在体系发动时,C函数库中的__main初始化代码会履行必要的仿制及清零操作,使运用程序的相应代码和数据段从装载状况转入履行状况。
1.scatter文件语法
scatter文件是一个简略的文本文件,包括一些简略的语法。
My_Region0x00000x1000
{
thecontextofregion
}
每个块由一个头标题开端界说,头中至少包括块的姓名和开端地址,别的还有最大长度和其他一些特点选项。块界说的内容包括在紧接的一对花括号内,依赖于详细的体系状况。
一个加载块有必要至少含有一个履行块;实践中一般有多个履行块。
一个履行块有必要至少含有一个代码或数据段;这些一般来自源文件或库函数等的方针文件;通配符号*能够匹配指定特点项中一切没有在文件中界说的余下部分。
2.简略涣散加载样例
图8所示样例中,只需一个加载块,包括了一切的代码和数据,开端地址为0。这个加载块总共对应两个履行块。一个包括一切的RO代码和数据,履行地址与装载地址相同;一同另一个开端地址为0x10000的履行块,包括一切的RW和ZI数据。这样当体系开端发动时,从第一个履行块开端运转(履行地址等于装载地址),在履行进程中,有一段初始化代码会把装载块中的一部分代码转移到别的的履行块中。
下面是这个scatter描绘文件,该文件描绘了上述存储器映射办法。
LOAD_ROM0x4000

EXE_ROM0x00000x4000;Rootregion

*〈+RO〉;Allcodeandconstantdata

RAM0x100000x8000

*〈+RW,+ZI〉;Allnon-constantdata


3.在涣散文件中放置方针
在大多数运用中,并不是像前例那样,简略地把一切特点都放在一同,用户需求操控特定代码和数据段的放置方位。这能够经过在scatter文件中对单个方针文件进行界说完结,而不是只简略地依托通配符。
为了掩盖规范的衔接器布局规矩,咱们能够运用+FIRST和+LAST涣散加载指令。典型的比方是在履行块的开端处放置中止向量表格:
LOAD_ROM0x00000x4000

EXEC_ROM0x00000x4000

vectors.o〈Vect,+FIRST〉
*〈+RO〉

;moreexecregions…

在这个scatter文件中,确保了vextors.o中的Vect域被放置于地址0x0000。
4.RootRegion(根区)
根区是一个履行块,它的加载地址与履行地址是共同的。每个scatter文件至少有一个根区。涣散加载有一个约束:创立履行块的代码和数据(即完结仿制和清零的代码和数据)无法自行仿制到另一个方位。因而,在根区中有必要含有下面的部分:
_main.o,包括仿制代码/数据的代码;
衔接器输出变量$$Table和ZISection$$Table,包括被仿制代码/数据的地址。
由于上面两个部分的特点是只读的,因而他们被*〈+RO〉通配符语法匹配。假如*〈+RO〉被用在了非根区中,则在根区中有必要显式地指明另一个RO区域。
下面是一个比方:
LOAD_ROM0x00000x4000

EXE_ROM0x00000x4000;rootregion

_main.o〈+RO〉;copyingcode
*〈Region$$Tabl0e〉;RO/RWaddressestocopy
*〈ZISection$$Table〉;ZIaddressestozero

RAM0x100000x8000

*〈+RO〉;allotherROsections
*〈+RW,+ZI〉;allRWandZIsections

——————————————-

放置仓库和heap
Scatterloading机制供给了一种指定代码和静态数据布局的办法。下面介绍怎么放置运用程序的仓库和heap。
*_user_initial_stackheap重定向
运用程序的仓库和heap是在C库函数初始化进程中建立起来的。能够经过重定向对应的子程序来改动仓库和heap的方位,在ADS的库函数中,即_user_initial_stackheap()函数。
_user_initial_stackheap()能够用C或汇编来完结,它有必要回来如下参数:
r0:heap基地址;
r1:仓库基地址;
r2:heap长度约束值(需求的话);
r3:仓库长度约束值。
当用户运用涣散装载功用的时分,有必要重调用_user_initial_stackheap(),不然衔接器会报错:
Error: L6218E: Undefined symbol Image$$ZI$$Limit (referred from sys_stackheap.o)

*存储器模型
ADS供给了两种实时存储器模型。缺省时为one-region,运用程序的仓库和heap坐落同一个存储器区块,运用的时分相向成长,当在heap区分配一块存储器空间时需求查看仓库指针。另一种状况是仓库和heap运用两块独立的存储器区域。关于速度特别快的RAM,可挑选只用来作仓库运用。为了运用这种two-region模型,用户需求导入符号use_two_region_memory,heap运用需求查看heap的长度约束值。
对这两种模型来说,缺省状况下对仓库的成长都不进行查看。用户能够在程序编译时运用 -apcs/swst 编译器选项来进行软件仓库查看。假如运用two-region模型,有必要得在履行_user_initial_stackheap时指定一个仓库约束值。


图9 重定向_user_initial_stackheap()


图10 根本初始化进程


图11 ROM/RAM重定向和映射


表1

体系复位和初始化
现在状况,一般假定程序从C库函数的初始化进口_main开端履行。实践上,一切的嵌入式程序在发动时都要履行一些体系级的初始化操作。在此评论这方面的内容。
初始化进程
图10中显现了一个根据ARM的嵌入式体系的根本初始化进程。能够看到,在_main之前加入了一个复位处理模块reset handler,它在体系上电复位时当即发动。标识为$sub$$main的新代码块在进入主程序之前履行。
复位处理模块reset handler一般是一小段汇编代码,在体系复位时履行。它至少完结运用程序中运用到的一切处理器形式的仓库初始化作业。关于含有本地存储器体系的内核(比方含cache的ARM内核),装备作业也有必要在这一段初始化进程中完结。当完结体系初始化之后,一般程序会跳向_main,开端C库函数的初始化进程。
体系初始化进程一般还包括别的一些内容,中止使能等,这些大多安排在C库函数的初始化完结之后履行。$sub$$main()完结这部分功用。
向量表(vector table)
一切的ARM体系都有一张中止向量表当出现反常需求处理时,有必要调用向量表。向量表一般要坐落0地址处。


表2


表3


表4


表5


表6


表7


表8


表9


表10

存储器装备
*ROM/RAM重定向
当体系发动的时分,为了确保0地址处有正确的发动代码存在,需求非易失性的存储器。
一种简略的办法,便是把体系0x0000开端的一块地址分配给ROM。其缺陷是,由于ROM的拜访速度比RAM慢许多,当履行中止呼应需求从中止向量表跳转时,会给体系功用带来丢失;一同,在ROM中的向量表内容也不能被用户程序动态修正。
别的一种可行的计划如图11所示。ROM坐落地址0x1000开端的当地,可是在体系复位时又被存储器操控器映射到0x0000地址处。这样当体系发动之后,在地址0x0000看到的是ROM,体系履行这块ROM中的发动代码,发动代码跳转到实在的ROM的地址,并让存储器操控器移除对ROM的地址映射。这时0x0000地址处的存储器又康复回了RAM。__main中的代码把向量表copy到0x0000处的RAM中去,使得反常时能被正确呼应。
表1为ARM汇编中履行ROM/RAM重定向和映射的一个比方。它以ARM公司的Integrator渠道为根底的,该办法适用于相似ROM/RAM重定向办法的一切渠道。第一条指令完结从ROM的映射地址(0x00000)到实在地址的跳转。地址标号instruct_2是ROM的实在地址(0x180004)。然后经过设置Integrator渠道上的相应操控寄存器,移除ROM的地址映射。代码在体系一发动就被履行。一切关于地址重定向/映射的操作有必要在C库函数初始化之前完结。

*本地存储器装备
许多ARM处理器都有片上存储器体系,如cache和严密耦合存储器(TCM)、存储器办理单元(MMU)或存储器维护单元(MPU)。这些设备都要在体系初始化进程中正确装备,而且有一些特别的要求需求考虑。
由前文可知,_main中的C库函数初始化代码担任程序运转时的存储器体系设置。因而,整个存储器体系自身有必要得在__main之前完结初始化作业,如MMU或MPU有必要在reset handler里边完结装备。
严密耦合存储器(TCM)的初始化相同须在_main之前完结(一般在MMU/MPU之前),由于一般程序都需求把代码和数据涣散装入TCM。需求留意的是当TCM被使能后,不再拜访被TCM屏蔽的存储器。
关于cache的共同性问题,假如cache在_main之前使能的话,那么当_main里边进行从装载区到履行区的代码和数据复制时(由于在复制进程中指令和数据在本质上都是被当作数据处理),指令会出现在数据缓冲区。避免此问题的办法是在C库函数初始化完结后再使能cache。
*Scatter loading与存储器装备
无论是经过ROM/RAM重定向仍是MMU装备的办法,假如体系在发动和运转时存储器散布不共同,scatterloading文件中的界说就要依照体系重定向后的存储器散布状况进行。
以上文ROM/RAM重定向为例:
LOAD_ROM 0x10000 0x8000
{
EXE_ROM 0x10000 0x8000
{
reset_handler.o (+RO, +FIRST)

}
RAM 0x0000 0x4000
{
vectors.o (+RO, +FIRST)

}
}
装载区LOAD_ROM被放置在0x10000处,代表了重定向之后代码和数据的装载地址。

仓库的初始化
程序中或许用到的处理器形式,都需求界说一个仓库指针。
在表2中,仓库坐落stack_base标识的地址中。这个符号能够是存储器体系中的一个直接地址,也能够在别的的汇编文件中界说,由scatter文件来界说分配地址。表2代码为FIQ和IRQ形式各分配了一个256字节的仓库,用户能够用相同的办法为其他形式也分配仓库。最简略的办法便是进入相应的形式,然后为SP寄存器指定相应的值。假如想运用软件仓库查看,还有必要指定一个仓库长度约束值。
仓库指针和仓库约束的数值会作为参数主动传递到C库函数的初始化代码__user_initial_stackheap中,在__user_initial_stackheap中不应该修正这些值。
硬件初始化 $sub$$main()
一般来说,应该把一切的体系初始化代码与主运用程序别离开来,可是有几个破例,比方cache和中止的使能,需求在C库函数初始化之后履行。
表3代码显现了怎么运用 $sub和 $supper 。衔接器把呼叫main()的函数替换成呼叫$sub$$main(),完结cache和中止的使能,并终究跳向main()。

履行形式考虑
为主运用程序挑选一个处理器履行形式十分重要,这取决于体系的初始化代码。
许多在发动进程中运用到的功用,如MMU/MPU的装备、中止的使能等,只能在特权级形式下进行。假如需求在特权极形式下运转自己的运用程序,只需在退出初始化进程之前改动到相应的形式就行了,没有其他任何问题。
假如运用user形式,有必要确保一切只能在特权形式下履行的功用完结之后,才干进入user形式。由于system形式和user形式运用相同的寄存器组,reset handler应该从system形式退出,_user_initial_stackheap在system形式下完结运用程序仓库的初始化。这样在处理器进入user形式后,一切的仓库空间都现已被正确设置好了。

对存储器布局的进一步考虑
在scatter文件中分配硬件地址
尽管能够在一个scatter文件中描绘代码和数据的涣散布局,可是方针硬件中的外设寄存器,仓库和heap装备依然直接选用硬件地址在程序源代码中进行设置。假如把一切存储器地址相关的信息都在scatter文件中进行界说,避免在源文件中引证肯定硬件地址,对程序的工程化办理是有大长处的。
*在scatter文件中界说方针外设地址
一般外设寄存器的地址在程序文件或头文件中界说,也能够声明一个结构类型指向外设寄存器,结构的地址定位在scatter文件中完结。
举例来说,方针定时器上有2个32位的寄存器,能够用表4来映射这些寄存器。为了把结构放置在指定的存储器地址上面,创立一个新的履行区(见表5)。scatter文件便把timer_regs结构定位在了地址0x40000000。
留意,在发动进程傍边这些寄存器的内容不需求清零,改动寄存器的内容或许影响体系状况。在履行区上加UNINIT特点能够避免ZI数据在初始化进程中被清零。
在scatter文件中分配仓库和heap
在许多状况下,用scatter文件来界说仓库和heap的地址会带来一些长处,主要有:一切的存储器分配信息会集在一个文件里;改动仓库和heap的地址只需从头衔接就行了,不需求从头编译。
*显式地放置符号
在ADS1.2环境下,这是最简略的办法。在前文中引证过2个符号stack_base和heap_base,这2个符号在汇编模块中创立,在scatter文件中各自的履行区里定位(见表6)。
表7文件中,heap基地址定位在0x20000上,仓库基地址坐落0x40000。现在heap和仓库的方位就能够十分便利地进行修改了。
*运用衔接器发生的符号
这种办法需求在方针文件中指定好heap和仓库的长度。这在必定程度上削弱了本节最初描绘的两个长处。
首先在汇编源程序中界说heap和仓库的长度。关键词SPACE用来保存一块存储器空间,NOINT则能够阻挠清零操作(见表8)。留意在这里的源文件中并不需求地址标号。
然后这些部分就能够在scatter文件中对应的履行区里定位了(见表9)。衔接器发生的符号指向每一个履行区的基地址和长度约束,这些符号能够被_user_initial_stackheap调用的重定向代码运用。在代码中运用DCD来给这些值界说更有意义的姓名,能够增强代码的可读性(见表10)。
文件把heap基地址定位在0x15000,仓库地址定位在0x4000。Heap和仓库的方位能够经过修改对应履行区的地址便利地改动。

————————o—————————————

样例如下所示:

涣散加载描绘文件供ARM-ADS链接器运用,用来决议各个代码段和数据段的存储方位,下面为一个增加注释后的.scf文件比方:

;YL-LPC2294片内FLASH涣散加载文件
;Internal Flash 256kBytes, Address range:0x00000000~0x0003ffff
;Internal SRAM 16KBytes, Address range:0x40000000~0x40003fff
;External Flash 2MBytes,SST39VF1601,Address range:0x80000000~0x401fffff
;External SRAM 512KBytes,IS61LV25616,Address range:0x81000000~0x81080000
ROM_LOAD 0x0;ROM_LOAD:> ;0x0:Start address for ROM_LOAD region.
{
ROM_EXEC 0x00000000;ROM_EXEC:> ;0x00000000:Start address for the execture region.
{
Startup.o (vectors, +First)
* (+RO) ;Place all code and RO data into this exec region,
;and make sure the “vectors” section from “Startup.o”
;be placed first.
}

IRAM 0x40000000 ;The second execute region;start address is 0x40000000.
{
Startup.o (+RW,+ZI) ;Place all RW and ZI data from Startup.o here.
}

ERAM 0x81068000 ;The third execute region;Start address:0x81068000.
{
* (+RW,+ZI) ;All reset RW/ZI data to be placed here.
}

HEAP +0 UNINIT ;The fourth execute region;Start address:Follow the
;end of ERAM region.
{
heap.o (+ZI) ;All ZI data from heap.o to be placed here.
}

STACKS 0x40004000 UNINIT ;The fifth execute region.
{
stack.o (+ZI) ;All ZI data from stack.o to be placed here.
}
}

一般一个简略的涣散加载描绘文件包括三部分:Loader region、Execute region、Input section。各部分的格局及界说细节拜见文件:ADS_LinkerGuide.pdf

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/xinwen/275175.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部