1. 基础知识
为了充分理解分散加载文件的魅力,需要对工程编译后的内容有详细的了解。
Keil 编译后的内容如下所示:
1)Code:为程序代码部分;
2)RO-Data:表示程序定义的常量及 const 型数据;
3)RW-Data:表示已经初始化的静态变量,变量有初值;
4)ZI-Data:表示未初始化的静态变量,变量无初值。
当 Keil 工程编译完成后,查看其 map 文件,可得到结果如下程序清单。
====================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
125196 8026 4384 9108 74868 2185831 Grand Totals
125196 8026 4384 428 74868 2185831 ELF Image Totals (compressed)
125196 8026 4384 428 0 0 ROM Totals
====================================================================
Total RO Size (Code + RO Data) 129580 ( 126.54kB)
Total RW Size (RW Data + ZI Data) 83976 ( 82.01kB)
Total ROM Size (Code + RO Data + RW Data) 130008 ( 126.96kB)
====================================================================
由map文件可以看出:
ROM(Flash) Size = Code+RO-Data+RW-Data = 126.96KB;
RAM Size = RW-Data+ZI-Data = 82.01KB。
为什么上述的 RW-Data 既占用 Flash 又占用 RAM 呢?变量不是放在 RAM 中的吗?为
什么还会占用 Flash?因为 RW 数据不能像 ZI 那样“无中生有”的,ZI 段数据只要求其所在的区域全部初始化为零,所以只需要程序根据编译器给出的 ZI 基址及大小来将相应的 RAM清零。但 RW 段数据却不这样做,所以编译器为了完成所有 RW 段数据赋值,其先将 RW 段的所有初值,先保存到 Flash 中,程序执行时,再 Flash 中的数据搬运到 RAM 中,所以 RW 段即占用 Flash 又占用 RAM,且占用的空间大小是相等的。
这里有必要再了解一下,ZI 和 RW 段数据的赋值在某个工程中是在什么地方实现的呢?首先变量必先要初始化才能使用,否则初值不正确,而在 main() 函数后变量已经可以正常使用,那就是说变量的初始化是在之前完成的,查看这之前的代码只有 __main() 一个函数,除了赋初值外,都还做了什么呢?
函数__main()主要由以下两部分功能组成,如下所示:
1) __main():完成代码和数据的拷贝,并把 ZI 数据区清零。代码拷贝可将代码拷贝到另外一个映射空间并执行 (例如将代码拷贝到 RAM 执行); 数据拷贝完成 RW 段数据赋值;数据区清零完成 ZI 段数据赋值。以上的代码和分散加载文件密切相关。
2) _rt_entry():进行 STACK 和 HEAP 等的初始化。最后_rt_entry跳进 main()函数入口。当 main()函数执行完后, _rt_entry 又将控制权交还给调试器。
2. 什么是分散加载文件?
分散加载(scatter)文件是一个文本文件,它可以用来描述ARM连接器生成映像文件时所需要的信息。
如果不用scatter文件指定,那么ARM连接器会按照默认的方式来生成映像文件,一般情况下我们是不需要使用分散加载文件的,但在某些场合,我们希望把某些数据放在指定的地址处,那么这时候scatter文件就发挥了非常大的作用。而且scatter文件用起来非常简单好用。
在分散加载文件中可以指定下列信息:
1)各个加载时域(load region)加载时的起始地址(load address)和最大尺寸(max size);
2)各个加载时域的属性。
3)从每个加载时域中分割出来的运行时域。
4)各个运行时域(excution region)的运行起始地址(excution address)和最大尺寸(max size)。
5)各个运行时域的存储访问特性。
6)各个运行时域的属性。
7)各个运行时域中包含的输入段。
3. 为什么需要分散加载文件?
一般情况下,我们可以不独自编写分散加载文件,ARM连接器直接按照默认的方式来生成映像文件即可,但是在某些场合,我们希望将某些数据放在指定的位置,此时分散加载文件就发挥了非常发的作用。
比如在下面几种情况就充分体现了分散加载文件的优势:
1)复杂内存映射:如果必须将代码和数据放在多个不同的内存区域中,则需要使用详细指令指定将哪些数据放在哪个内存空间中。
2)不同类型的内存:许多系统都包含多种不同的物理内存设备,如闪存、 ROM、 SDRAM 和快速 SRAM。分散加载描述可以将代码和数据与最适合的内存类型相匹配。例如,可以将中断代码放在快速 SRAM 中以缩短中断等待时间,而将不经常使用的配置信息放在较慢的闪存中。
3)位于固定位置的函数:可以将函数放在内存中的固定位置,即使已修改并重新编译周围的应用程序。
4)使用符号标识堆和堆栈:链接应用程序时,可以为堆和堆栈位置定义一些符号。
4. 分散加载文件的基本特点
1)编译后输出的映像文件中各段是首尾相连的,中间没有空闲的区域,他们的先后关系是根据链接时参数的先后次序决定的armlinker -file1.o file2.o …
2)scatter用于将编译后的映像文件中的特定段加载到多个分散的指定内存区域。
3)两类域(region):执行域(execution region)和加载域(load region)。
4)加载域,该映像文件开始运行前存放的区域,即当系统启动或加载时应用程序存放的区域。
5)执行域,映像文件运行时的区域,即系统启动后,应用程序进行执行和数据访问的存储器区域,系统在实时运行时可以有一个或多个执行块。
6)scatter本身并不能对映像实现“解压缩”,编译器读入scatter文件之后会根据其中的各种地址生成启动代码了,实现对映像的加载,而这一段代码就是*(InRoot$$Sections)它是__main()的一部分。这就是在汇编启动代码的最后跳转到__main()而不是跳向main()的原因之一。
7)起始地址与加载域重合的执行域称为root region,*(InRootSections)必须放在这个执行域中,否则链接的时候会报错。
5. 分散加载文件的语法
分散加载文件一般由1个加载时域和1到多个运行时域组成(当然也可以包含2个以上的加载时域)。其大致的结构如下图所示:

5.1 加载时域描述
加载时域语法格式如下所示:
load_region_name(base_address|("+"offset))[attribute_list][max_size]
{
execution_region_description+
}
1)load_region_name:为本加载时域的名称,名称可以按照用户意愿自己定义,该名称中只有前 31 个字符有意义。它仅仅用来唯一的标识一个加载时域,而不像运行时域的名称除了唯一的标识一个运行时域外,还用来构成连接器连接生成的连接符号。
2)base_designator:用来表示本加载时域的起始地址,可以有下面两种格式中的一种:
base_address:表示本加载时域中的对象在连接时的起始地址,地址必须是字对齐的;
+offset:表示本加载时域中的对象在连接时的起始地址是在前一个加载时域的结束地址后偏移量 offset 字节处。本加载时域是第一个加载时域,则它的起始地址即为 offset, offset 的值必须能被 4 整除。
3)attribute_list:指定本加载时域内容的属性,包含以下几种, 默认加载时域的属性是ABSOLUTE。
PI – 位置无关属性。
RELOC – 重定位。
OVERLAY – 覆盖。
ABSOLUTE – 起始地址由base_designator指定(默认属性)。
4)max_size:指定本加载时域的最大尺寸。如果本加载时域的实际尺寸超过了该值,连接器将报告错误, 默认取值为 0xFFFFFFFF。
5)execution_region_description:表示运行时域,后面有个+号,表示其可以有一个或者多个运行时域,关于运行时域的介绍请看后面。
5.2 运行时域描述
运行时域语法格式如下所示:
exec_region_name(base_address|"+"offset)[attribute_list][max_size|" "length]
{
input_section_description*
}
1)exec_region_name: 为为本加载时域的名称,名称可以按照用户意愿自己定义, 该名称中只有前 31 个字符有意义。它除了唯一的标识一个运行时域外,还用来构成连接器生成的连接符号。
2)base_designator:用来表示本加载时域的起始地址,可以有下面两种格式中的一种:
base_address:表示本运行时域中的对象在连接时的起始地址,地址必须是字对齐的;
+offset:表示本运行时域中的对象在连接时的起始地址是在前一个加运行时域的结束地址后偏移量 offset 字节处。本运行时域是第一个加载时域,则它的起始地址即为 offset, offset 的值必须能被 4 整除。
3)attribute_list:指定本加载时域内容的属性,包含以下几种, 默认加载时域的属性是ABSOLUTE。
PI – 位置无关属性。
RELOC – 重定位。
OVERLAY – 覆盖。
ABSOLUTE – 起始地址由base_designator指定(默认属性)。
FIXED – 固定地址。此时该域加载时域地址和运行时域地址是相同的,而且都是通过base_designator指定的,而且base_designator必须是绝对地址或者offset为0。s
4)max_size:指定本运行时域的最大尺寸。如果本运行时域的实际尺寸超过了该值,连接器将报告错误, 默认取值为 0xFFFFFFFF。

本文围绕ARM分散加载文件展开,先介绍Keil编译后内容及相关数据段特点,阐述分散加载文件概念、使用场景、基本特点和语法。还通过多种实例,如一个加载域多个执行域、多块RAM和Flash配置等,说明其应用,并解释加载时域与第一个运行时域的关系。
详述&spm=1001.2101.3001.5002&articleId=114018759&d=1&t=3&u=c0bfb44ceb1c498d9e52c58f594acc8d)
1635

被折叠的 条评论
为什么被折叠?



