土银触手宇宙生命:Scatter File的用法

来源:百度文库 编辑:中财网 时间:2024/04/28 14:03:49

对于嵌入式开发来说,scatterfile显得异常重要,尤其是想把某段内容链接到指定的地址区域的时候,这些内容可以是code、const常量和变量。

如果是ARM平台的话,在ARM的linker guide里有详细介绍scatterfile的用法。其实DSP程序的开发也会有类似scatter file的东西,记得当时用TIDSP的时候有个叫做cmd文件的东西,里面会要求指定各个段的链接地址,包括起始地址和size。从理论上来说,可以指定每一个变量,每一段代码链接的位置,当然,其实许多时候并没有必要这样做。但是scatterfile确实提供了这样一种精确控制的方法。记得有看过一点tms3202812的例子程序,好像在cmd文件中进行了精细的控制,为每个寄存器定义了一个名字(一个变量),然后在cmd文件中指定了这个变量的链接链接地址,这样就可以实现操作这个变量即是操作指定的寄存器,用起来很方便。(只是记得是这样,具体没有仔细看)

在基于ARM的嵌入式开发中,scatterfile是一个文本文件,其为linker所用,linker会按照指定的原则来进行链接。为了讲解如何将某个变量或者某段代码链接到指定位置,我们先来看几个概念。

段(Section):段分为输入段(input section)和输出段(outputsection),段是连接器操作的基本单位。

输入段(InputSection):输入段作为linker的输入,分布在多个目标文件或者库中。

输出段(OutputSection):Linker的输出是一个可执行的映像,在这个映像中各个变量等被链接到指定的地址区域,这些地址区域就是scatterfile中指定的执行区。

段的属性:段的属性有三种,包括Read-Only(简称RO,只读,包括只读的数据和代码)、Read-Write(简称RW,可读写)、Zero-Initialized(简称ZI,初始化为0的可读写数据,通常未初始化的数据也包含在内)。另外段还可以有一个名字,通过这个名字来区分同一个段的不同部分。

讲完段(section)以后,我们看一下以下几个概念,Image(映像)、Load region(加载区)、Executionregion(执行区)。

 

Image(映像):Linker将目标文件(object)和库(lib)链接之后的输出即是Image,Image通常是可执行的二进制文件(当然也可能是不可执行的资源文件等)。在Image中通常包含了只读的code和data、初始化的数据。(可以想一下为什么不包含ZI数据?)

LoadRegion(加载区):在系统上电以后,Image被加载到目标系统中(通常是bootloader将Image搬到RAM中),这个时候Image还没有开始执行(即内核文件还未解压,可以想一下为什么需要解压),此时各个段在RAM中的区域就是LoadRegion。

 

ExecutionRegion(执行区):系统上电加载到RAM中以后开始执行,这个时候内核就需要解压。为什么需要解压?之前有讲到Image并没有包含ZI数据,因为ZI数据都是0,只要在解压的时候将这些变量初始化为0就行了,没有必要把这些0值都放在Image里面来占用空间;而RW数据都是初始化的数据,这些变量如果不保存一个初始值linker就不知道该初始化为多少。内核解压就是要将RW数据和ZI数据在指定的区域进行初始化,初始化为初始值或者0。这些指定的区域就是ExecutionRegion(执行区)。另外RO(只读的数据和代码)也有可能会搬动。

对于scatter file的用法这里不会做过多介绍,具体内容可以参考ADS LinkerGuide,在ARM网站上就有。下面主要讲如何将指定的段链接到指定的地址区域上去。

这里假设我有一个文件叫做example.c,编译结束后会生成目标文件example.obj,我打算将RW数据链接到0×80000000开始的4KB(0×1000)区域内,将ZI数据链接到0×40000000开始的8KB(0×2000)区域内,ROdata和code保留在原有位置不动。这样的设置只要设置scatter file即可达到目的,scatterfile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ROM 0x0 0x1000000    ;该行指定映像加载区的起始位置为0x0,最大32MB
{
RAM_A 0x0 0x100000 ;该行指定执行区的起始位置在0x0,最大1MB
{
Example.obj (+RO)
* (+RO) ;使用通配符*将其余文件的RO段也放在RAM_A区域内
}
RAM_B 0x80000000 0x1000 ;RW数据放在2GB开始的4KB区域
{
Example.obj (+RW)
}
RAM_C 0x40000000 0x2000 ;ZI数据放在1GB开始的8KB区域
{
Example.obj (+ZI)
}
……
}

这里面需要注意的一点是ROM和RAM_A的起始位置必须要相同,RAM_A是系统上电后要执行的第一段代码,中断向量表和内核解压的代码也就在这里面。如果这个区域链接到了其他的位置,那么系统从ROM的起始地址开始执行,第一条指令就不是RAM_A的第一条指令了。

上面的例子是一种简单的情况,如果我有这样一种需求,我想把example.c文件中定义的某个数组和部分的ROData和Code链接到内部RAM(假设从0×20000000开始的32KB区域),而把其他的RW和ZI变量链接到到外部RAM(假设从0×40000000开始的32MB区域)。对于这种需求就需要将同一个段中不同的部分进行区分,这就需要为段命名。可按照如下步骤进行操作:

1、在将要链接到INTSRAM_CODE区域的code用下面一对预编译指令包起来

1
2
3
#pragma arm section code = "INTSRAMCODE"

#pragma arm section code

2、在要链接到INTSRAM_CONST区域的常量用下面一对预编译指令包起来

1
2
3
#pragma arm section rodata = "INTSRAMCONST"

#pragma arm section rodata

3、在要链接到INTSRAM_DATA区域的变量用下面一对预编译指令包起来

1
2
3
#pragma arm section rwdata = "INTERNRW" 

#pragma arm section rwdata

4、设置scatter file如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ROM 0x0 0x1000000
{
RAM_A 0x0 0x100000
{
* (+RO) ;默认情况下所有的code都放在RAM_A中
}
INTSRAM_CODE 0x20000000 0x1000 ;内部RAM的前4KB区域放指定的code
{
Example.obj (INTSRAMCODE) ;名为INTSRAMCODE段中code都会放在这里
}
INTSRAM_CONST +0x0 0x4000 ;前一个区域之后接着就是16KB的INTSRAM_CONST区域
{
Example.obj (INTSRAMCONST) ;名为INTSRAMCONST的rodata都会放在这里
}
INTSRAM_DATA +0x0 0x2000 ;前一个区域之后紧接着就是8KB的INTSRAM_DATA区域
{
Example.obj (INTERNRW) ;名为INTERNRW的rwdata都会放在这里
}
EXTRAM 0x40000000 0x1000000 ;起始于0x40000000的32MB外部RAM
{
* (+RW +ZI) ;其他的RW和ZI数据都会默认放在这里
}
}

5、这样设定后重新编译链接工程即可。

需要注意的几点:

1、上面声明的一些section name,可以任意起名字,只要保持与scatterfile中设置相同即可,当然起一些有意义的名字更容易理解。

2、可以将code、rodata、rwdata、zidata组合在一起写,只要将要搬的内容包起来即可,如下所示

1
2
3
#pragma arm section code = "INTSRAMCODE", rodata = "INTSRAMCONST", rwdata = "INTSRAMRW" , zidata = "INTSRAMZI"

#pragma arm section code, rodata, rwdata , zidata

本文结束!