摘要 从ARM ELF目标文件主要构成出发,详细介绍了分散加载的基本原理、分散加载文件的语法、分散加载时连接器生成的预定义符号及要重新实现的函数等;以定位目标外设和定义超大型结构体数组两项应用案例加以说明,并给出完整的工程实例和Bootloader代码。这些都已经在实际工程中多次应用和验证,是笔者实际工程的萃取。
关键词 分散加载 嵌入式系统 Scatter Loading Bootloader ARM ELF
引言
在当今的嵌入式系统设计中,ARM处理器以价格便宜、功耗低、集成度高、外设资源丰富和易于使用的特点而得到广泛的应用;在速度和性能方面已达到或超过了部分PC104嵌入式计算机的性能,而成本却比相应的PC104计算机低很多,广泛应用于手机、GPS接收机、地图导航、路由器、以太网交换机及其他民用和工业电子设备。
在一个采用ARM处理器的实时嵌入式系统中,目标硬件常常由Flash、SRAM、SDRAM和NVRAM(非易失性RAM)等存储器组成,并定位于不同的物理地址范围,那么,怎样通过软件更好地访问和利用这些不同的存储器并让系统高效地运行?分散加载(Scatter loading)就提供了这样一种机制。它可以将内存变量定位于不同的物理地址上的存储器或端口,通过访问内存变量即可达到访问外部存储器或外设的目的;同时通过分撒加载,让大多数程序代码在高速的内部RAM中运行,从而使得系统的实时性大大增强。
1.ARM ELF目标文件的主要构成
ARM ELF(Executable and Linking Format)目标文件主要由.Text段、.Data段、.BSS段构成,其他段如.debug段、.comment段等与本文关系部大,不作介绍。
.Text 段由可执行代码组成,段类型为Code,属性为RO;
.Data 段由已初始化数据组成,段类型为Data, 属性为RO;
.BSS 段由未初始化数据组成,段类型为Zero, 属性为RW,在应用个程序启动时对该段的数据初始化为零。如果在分散加载文件中指定了UNINIT属性,则在应用程序启动时不初始化该段。
2. 分散加载的基本原理
假设一个采用ARM处理器的实时嵌入式系统目标硬件的存储器由ROM存储器和RAM存储器组成。当一个嵌入式在仿真环境下调试完毕,需要脱机运行的时候,就需要将源程序编译连接成可执行目标代码并烧写到ROM存储器中。由于ROM存储器存取数据的速率比RAM存储器慢,因此,让程序在ROM存储器中运行。CPU每次取指令和取数据操作都要访问ROM存储器,这样需要在CPU的总线周期中插入等待周期,通过降低总线的速率来满足访问慢速的ROM存储器,这样识别会降低CPU的运行速率和效率,因此,分散加载就显得非常重要。
ARM的连接器提供了一种分散加载机制,在连接时可以根据分散加载文件(.scf文件)中指定的存储器分配方案,将可执行镜像文件分成指定的分区并定位于指定的存储器物理地址。这样,当嵌入式系统在复位或重新上电时,在对CPU相应寄存器进行初始化后,首先执行ROM存储器的Bootloader代码,根据连接时的存储器分配方案,将相应代码和数据由加载地址拷贝到运行地址,这样,定位在RAM存储器的代码和数据就在RAM存储器中运行,而不再从ROM存储器中取数据或取指令,从而大大提高了CPU的运行速率和效率。
(二)结构
Scatlertoading的存储区块可以分成二种类型:
装载区:当系统启动或加载时应用程序的存放区。
执行区:系统启动后,应用程序进行执行和数据访问的存储器区域,系统在实时运行时可以有一个或多个执行块。
(三)分散加载时连接器生成的预定义符号
在编译连接时如果指定了分散加载文件(.scf文件),在连接后会自动生成如下变量:
(四)具体例子说明
;ROM_LOAD 为加载区的名称,其后面的0x00000000 表示加载区的起始地址(存放程序代码的起始地址)
ROM_LOAD 0x0
{
;ROM_EXEC 描述了执行区的地址,放在第一块位置定义
ROM_EXEC 0x00000000
{
;从起始地址开始放置向量表(即Startup.o(vectors, +First),其中Startup.o 为Startup.s 的目标文件)
;+First表示Vector段放在最前面
;AREA vectors, CODE, READONLY
Startup.o (vectors, +First)
;接着放置其它代码(即* (+RO)),* 是通配符,类似WINDOW下搜索用的通配符
* (+RO)
}
;变量区IRAM 的起始地址为0x40000000
IRAM 0x40000000
{
;放置Startup.o (MyStacks)
Startup.o (MyStacks)
}
;+0表示接着上一段,UNINIT 表示不初始化
STACKS_BOTTOM +0 UNINIT
{
;放置AREA StackBottom, DATA, NOINIT
Startup.o (StackBottom)
}
;接着从0x40004000 开始,放置 AREA Stacks, DATA, NOINIT,UNINIT 表示不初始化
STACKS 0x40004000 UNINIT
{
Startup.o (Stacks)
}
;外部RAM从0x80000000开始为变量区
;如果片外RAM起始地址不为0x8000 0000,则需要修改mem_.scf文件
ERAM 0x80000000
{
* (+RW,+ZI)
}
;+0表示接着上一段,UNINIT 表示不初始化
HEAP +0 UNINIT
{
;放置堆底, AREA Heap, DATA, NOINIT
Startup.o (Heap)
}
;接着在外部0x80080000 放置堆顶
;这个地址是片外RAM 的结束地址,根据实际情况修改
HEAP_BOTTOM 0x80080000 UNINIT
{
Startup.o (HeapTop)
}
}
以上分散文件的可引用的段名
|Image
Limit|
|Image
Limit|
。。。。。。。。。。。。。。。。
;重定向__user_initial_stackheap 函数
;分配新的bottom_of_heap地址等,R0-R3是函数必须的返回值,返回bottom_of_heap的值
;通过分散加载描述文件,重定向其位置,bottom_of_heap等已经在Startup.s中定义为DATA类型
__user_initial_stackheap
LDR r0,=bottom_of_heap
; LDR r1,=StackUsr
LDR r2,=top_of_heap
LDR r3,=bottom_of_Stacks
MOV pc,lr