Scatter File的用法 === 对于嵌入式开发来说,scatter file显得异常重要,尤其是想把某段内容链接到指定的地址区域的时候,这些内容可以是code、const常量和变量。 如果是ARM平台的话,在ARM的linker guide里有详细介绍scatter file的用法。其实DSP程序的开发也会有类似scatter file的东西,记得当时用TI DSP的时候有个叫做cmd文件的东西,里面会要求指定各个段的链接地址,包括起始地址和size。从理论上来说,可以指定每一个变量,每一段代码链接的 位置,当然,其实许多时候并没有必要这样做。但是scatter file确实提供了这样一种精确控制的方法。记得有看过一点tms3202812的例子程序,好像在cmd文件中进行了精细的控制,为每个寄存器定义了一 个名字(一个变量),然后在cmd文件中指定了这个变量的链接链接地址,这样就可以实现操作这个变量即是操作指定的寄存器,用起来很方便。(只是记得是这 样,具体没有仔细看) 在基于ARM的嵌入式开发中,scatter file是一个文本文件,其为linker所用,linker会按照指定的原则来进行链接。为了讲解如何将某个变量或者某段代 码链接到指定位置,我们先来看几个概念。 段(Section):段分为输入段(input section)和输出段(output section),段是连接器操作的基本单位。 输入段(Input Section):输入段作为linker的输入,分布在多个目标文件或者库中。 输出段(Output Section):Linker的输出是一个可执行的映像,在这个映像中各个变量等被链接到指定的地址区域,这些地址区域就是 scatter file中指定的执行区。 段的属性:段的属性有三种,包括Read-Only(简称RO,只读,包括只读的数据和代码)、Read- Write(简称RW,可读写)、Zero-Initialized(简称ZI,初始化为0的可读写数据,通常未初始化的数据也包含在内)。另外段还可以 有一个名字,通过这个名字来区分同一个段的不同部分。 讲完段(section)以后,我们看一下以下几个概念,Image(映像)、Load region(加载区)、Execution region(执行区)。 Image(映像):Linker将目标文件(object)和库(lib)链接之后的输出即是 Image,Image通常是可执行的二进制文件(当然也可能是不可执行的资源文件等)。在Image中通常包含了只读的code和data、初始化的数 据。(可以想一下为什么不包含ZI数据?) Load Region(加载区):在系统上电以后,Image被加载到目标系统中(通常是bootloader将Image搬到RAM中),这 个时候Image还没有开始执行(即内核文件还未解压,可以想一下为什么需要解压),此时各个段在RAM中的区域就是Load Region。 Execution Region(执行区):系统上电加载到RAM中以后开始执行,这个时候内核就需要解压。为什么需要解压?之前有讲到Image并没有 包含ZI数据,因为ZI数据都是0,只要在解压的时候将这些变量初始化为0就行了,没有必要把这些0值都放在Image里面来占用空间;而RW数据都是初 始化的数据,这些变量如果不保存一个初始值linker就不知道该初始化为多少。内核解压就是要将RW数据和ZI数据在指定的区域进行初始化,初始化为初 始值或者0。这些指定的区域就是Execution Region(执行区)。另外RO(只读的数据和代码)也有可能会搬动。 对于scatter file的用法这里不会做过多介绍,具体内容可以参考ADS Linker Guide,在ARM网站上就有。下面主要讲如何将指定的段链接到指定的地址区域上去。 这里假设我有一个文件叫做example.c,编译结束后会生成目标文件example.obj,我打算将RW数据链接到0×80000000开始的 4KB(0×1000)区域内,将ZI数据链接到0×40000000开始的8KB(0×2000)区域内,RO data和code保留在原有位置不动。这样的设置只要设置scatter file即可达到目的,scatter file如下所示: 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文件中定义的某个数组和部分的RO Data和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,可以任意起名字,只要保持与scatter file中设置相同即可,当然起一些有意义的名字更容易理解。 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