文章目录
1 SCF文件
keil编译器在链接的时候,是根据分散加载(.scf后缀的文件)来确定程序的加载域和运行域的。
- 加载域就是程序运行前在flash中具体分区情况
- 执行域就是程序运行后,程序在flash和ram中的分区情况。
当程序运行后,RW段中的数据会被复制到RAM中,同时还会初始化一个ZI段用来存放没有初始化和被初始化为零的相关变量。
1.1 程序的存储与运行
应用程序具有静止状态和运行状态。
- 静止态的程序被存储在非易失存储器中,如 RT1052 的外部 FLASH,因而系统掉电后也能正常保存。
- 当程序在运行状态的时候,程序常常需要修改一些暂存数据,由于运行速度的要求,这些数据往往存放在内存中 (RAM),掉电后这些数据会丢失。
- 因此,程序在静止与运行的时候它在存储器中的表现是不一样的
加载完后 RAM 区准备完毕,正式开始执行主体程序。
2 SCT分散加载文件
打开一个STM32的工程,找到工程配置的入口,选择Target。
再选择Linker
我们去掉第一个勾,看到Scatter File那一行可以修改和编辑,以下是分散加载文件的具体内容:
分号后面是注释。
LR_IROM1 0x08000000 0x00040000 {
加载域
这是代码的加载域,从Target的配置看,ROM的起始地址和范围分别就是0x08000000 0x00040000,而LR_IROM1为加载域名称,代表这块存储区域。
- 在 map 文件中的描述会使用加载域名称来标识空间,名字可自行定义。

- base_address: 指定当前加载域中的对象在被连接时的地址。 base_address 必须满足加载域的对齐约束。与 +offset 二选一使用。
- +offset: offset 为具体的阿拉伯数字。表示当前加载域比前一个加载域的末尾偏移的字节数。例如,+0。偏移量的值必须能被 4 整除。
- 属性列表:属性列表说明了加载域的是否为绝对地址、N 字节对齐等属性,该配置是可选的。加载域可以继承先前加载域的属性。主要有以下这些
- 最大容量:最大容量说明了这个加载域可使用的最大空间,该配置也是可选的。
- 如果指定了 max_size 值,则当该加载域中的内容超过 max_size 时,armlink 会生成错误。不指定 max_size 值,默认为 0xFFFFFFFF。
属性
ABSOLUTE: 指定将内容放置在链接后不会更改的 base_address 所表示的固定地址上。这是默认值 ,除非使用了 PI 或 RELOC。
ALIGN alignment: 指定对齐约束。alignment 为阿拉伯数字,最小为 4,必须是 2 的整数次幂。例如,ALIGN 4。 base_address 的值必须符合该对齐约束。如果使用了 +offset,连接器将计算并使用对齐后的地址。
NOCOMPRESS: RW 数据压缩默认情况下处于启用状态。 使用 NOCOMPRESS 关键字可以指定在最终镜像中不得压缩加载域的内容。
OVERLAY: 使用 OVERLAY 关键字可以在同一地址具有多个加载域。 ARM 工具不提供覆盖机制。 要在同一地址使用多个加载域,必须提供自己的叠加层管理器。该属性不能被继承。
PI: 表示当前域与位置无关。 内容不依赖于任何固定地址,并且在链接后无需任何额外处理即可移动。
如果镜像中包含仅执行节(XO),则不支持此属性。
PROTECTED: 该关键字将阻止以下情况:
Overlapping of load regions.
Veneer sharing.
String sharing with the --merge option.
RELOC: 指出当前域是可重定位的。 内容取决于固定地址。 输出重定位信息,以使内容可以通过另一个工具移动到另一个位置。
如果镜像中包含仅执行节(XO),则不支持此属性。
属性的继承,参考下面的示例:
LR1 0x8000 PI ; 默认为 ABSOLUTE
{
…
}
LR2 +0 ; LR2 从 LR1 继承 PI 属性
{
…
}
LR3 0x1000 ; LR3 不继承 LR2 的任何属性,因为它没有相对基地址,恢复默认为 ABSOLUTE
{
…
}
执行域
加载域括起来的,为执行域。本文件有两个执行域:ER_IROM1和RW_IRAM1。

类似的,0x08000000和0x00040000为ER_IROM1的起始地址和大小。
- base_address: 指定当前执行域中的对象在连接时的地址。 base_address 必须字对齐。与 +offset 二选一使用。 在执行域上使用ALIGN会使加载地址和执行地址都对齐。
- attribute_list: 指出当前执行域的属性列表。执行域可以继承先前执行域的属性
*.o (RESET, +First)
*.o表示选择所有的.o文件,均放在这里,RESET为节区名,+First表示将RESET节区放在最前面。
- RESET在启动文件.s文件可以看到。是工程启动代码中定义的向量表
- 该向量表中定义的堆栈顶和复位向量指针必须要存储在内部 FLASH 的前两个地址,这样RT1052 才能正常启动

(InRoot$$Sections)
一个链接器支持的特殊选择符号,这是将标准库里,有使用到的相关文件或节区,放到这里,挨着上面的*.o
.ANY (+RO)
表示将剩下的,所有的RO(readeonly,只读的)属性的节区放这里
.ANY (+RW +ZI)
表示剩下的,所有的RW、ZI属性的节区
“输入节区描述”说明了哪些节区要存储到这些空间

- 使用语句“bsp_led.o”可以选择bsp_led.o 文件
- 使用语句“*.o”可以选择所有 o 文件
- 使用“*.lib”可以选择所有 lib 文件
- .ANY”选择语句的优先级最低的,代表其余所有
节区特性:节区特性可以使用“+FIRST”或“+LAST”选项配置它要存储到的位置
- 通常重要的节区会放在头部,而 CheckSum(校验和)之类的数据会放在尾部。
输入符号样式:同样地,使用输入符号样式可以选择要控制的符号,符号样式需要使用“:gdef:”来修饰。
- 可以使用“*(:gdef:Value_Test)”来控制选择符号“Value_Test”
输入节区属性:通过在模块选择样式后面加入输入节区属
- 每个节区属性描述符前要写一个“+”号,使用空格或“,”号分隔开

链接器根据.scf 文件链接,链接后各个节区、符号的具体地址信息可以在 map 文件中查看。
属性
主要有以下这些:
ABSOLUTE:指定将内容放置在链接后不会更改的 base_address 所表示的固定地址上。
ALIGN alignment:指定对齐约束。alignment 为阿拉伯数字,最小为 4,必须是 2 的整数次幂。例如,ALIGN 4。 base_address 的值必须符合该对齐约束。如果使用了 +offset,连接器将计算并使用对齐后的地址。
执行域上的 ALIGN 属性将导致加载地址和执行地址都对齐。
ALIGNALL value:增加执行域中各节的对齐方式。value 的值必须是 2 的正幂,并且必须大于或等于 4。
ANY_SIZE max_size:指定 armlink 可以用未分配的节填充的执行域内的最大大小。max_size 必须小于或等于域的大小。
EMPTY [–]length:在执行域中保留给定大小的空内存块,通常由堆或堆栈使用。 带有 EMPTY 属性的域中不能放置任何节。
FILL value:创建包含 value 值的链接器生成的区域(例如,FILL 0xFFFFFFFF)。FILL 属性可以替换以下组合:EMPTY ZEROPAD PADVALUE。
FIXED:固定地址。 链接器会尝试使执行地址等于加载地址。 这使得该域成为根区域。 如果不可能,则链接器会产生错误。
NOCOMPRESS:RW 数据压缩默认情况下处于启用状态。 使用 NOCOMPRESS 关键字,可以指定执行域中的 RW 数据不得在最终镜像中压缩。
OVERLAY:用于地址范围重叠的节。 如果连续的执行域具有相同的 +offset,那么它们将被赋予相同的基地址。
PADVALUE value:定义用于填充的值。例如,EXEC 0x10000 PADVALUE 0xFFFFFFFF EMPTY ZEROPAD 0x2000 表示创建一个大小为 0x2000 且使用 0xFFFFFFFF 填充的域。
PI:该域仅包含与位置无关的节。 内容不依赖于任何固定地址,并且在链接后无需任何额外处理即可移动。
如果镜像中包含仅执行节(XO),则不支持此属性。
SORTTYPE algorithm:指定执行域的排序算法,例如 ER1 +0 SORTTYPE CallTree。该属性的优先级高于通过连接器参数 --sort 算法 的方式。
UNINIT:用于创建包含未初始化数据或内存映射 I/O 的执行域。
ZEROPAD:零初始化的段作为零填充块写入 ELF 文件。只有根执行域可以使用 ZEROPAD 属性进行零初始化。在非根执行域中使用 ZEROPAD 属性会生成警告,并忽略该属性。
关于属性的继承,参考下面的示例:
LR1 0x8000 PI
{
ER1 +0 ; ER1 从 LR1 继承 PI
{
…
}
ER2 +0 ; ER2 从 ER1 继承 PI
{
…
}
ER3 0x10000 ; ER3不继承,因为它没有相对基地址,将恢复使用默认值 ABSOLUTE
{
…
}
输入节
输入节描述指定将哪些输入节放到执行域中。
语法
input_section_description ::=
module_select_pattern
[ "(" input_section_selector ( "," input_section_selector )* ")" ]
input_section_selector ::=
("+" input_section_attr | input_section_pattern | input_symbol_pattern | section_properties
module_select_pattern: 模块匹配器。用来指出输入节所在的模块。可以使用通配符。通配符 * 匹配零个或多个字符;通配符 ? 匹配任何单个字符。匹配不区分大小写。分散文件中不能有两个* 选择器。
-
* 匹配任何模块或库 *.o 匹配任何对象模块 math.o 与 math.o 模块匹配 *armlib* 匹配 ARM 提供的所有C库 "file 1.o" 与 file 1.o 相匹配。注意有空格时需要双引号。 *math.lib 匹配以math.lib结尾的任何库路径。例如,C:\apps\lib\math\satmath.libinput_section_selector: 输入节选择器。主要有以下几种:
-
input_section_attr: 与输入节相匹配的属性选择器。每个 input_section_attr 后前面都会有个 + 。由一对小括号包裹,多个属性以逗号间隔开。选择器不区分大小写。主要有以下这些: RO-CODE RO-DATA RO 用来同时选择RO-CODE和RO-DATA RW-DATA RW-CODE RW 用来同时选择 RW-CODE 和 RW-DATA XO ZI ENTRY, that is, a section containing an ENTRY point 可以识别以下同义词: CODE for RO-CODE CONST for RO-DATA TEXT for RO DATA for RW BSS for ZI 可以识别以下伪属性: FIRST LAST -
input_section_pattern: 输入节选择器,用来指出选择的输入节。不区分大小写。可以使用通配符。通配符 * 匹配 0个或多个字符;通配符 ? 匹配任何单个字符。可以使用引号括起来。
-
input_symbol_pattern: 可以通过输入节定义的全局符号的名称来选择输入节。这使我们可以从部分链接的对象中选择具有相同名称的各个节。
-
section_properties: 可以是 +FIRST,+LAST 和 OVERALIGN value。其中,OVERALIGN 中的 value 必须为 2 的正幂,并且必须大于或等于 4
2.1 SCF文件示例
/*********************** 第一部分,宏定义 ************************/
#define m_flash_config_start 0x60000000
#define m_flash_config_size 0x00001000
#define m_ivt_start 0x60001000
#define m_ivt_size 0x00001000
#define m_interrupts_start 0x60002000
#define m_interrupts_size 0x00000400
#define m_text_start 0x60002400
#define m_text_size 0x01FFDC00
#define m_data_start 0x20000000
#define m_data_size 0x00020000
#define m_data2_start 0x20200000
#define m_data2_size 0x00040000
/********************** 第二部分,设置堆、栈空间大小 ***********/
/* Sizes */
#if (defined(__stack_size__))
#define Stack_Size __stack_size__
#else
#define Stack_Size 0x0400
#endif
#if (defined(__heap_size__))
#define Heap_Size __heap_size__
#else
#define Heap_Size 0x0400
#endif
/********************* 第三部分 ****************************/
LR_m_rom_config m_flash_config_start m_flash_config_size //加载域
{
RW_m_config_text m_flash_config_start m_flash_config_size //执行域
{ ; load address = execution address
* (.boot_hdr.conf, +FIRST)
}
}
/********************* 第四部分 **************************/
LR_m_rom_ivt m_ivt_start m_ivt_size //加载域
{ ; load region size_region
RW_m_ivt_text m_ivt_start m_ivt_size //执行域
49 { ; load address = execution address
50 * (.boot_hdr.ivt, +FIRST)
51 * (.boot_hdr.boot_data)
52 * (.boot_hdr.dcd_data)
53 }
54 }
55
56 /********************** 第五部分 *************************/
57 LR_m_text m_interrupts_start m_text_start+m_text_size-m_interrupts_size //加
载域
58 { ; load region size_region
59 VECTOR_ROM m_interrupts_start m_interrupts_size//执行域
60 { ; load address = execution address
61 * (RESET,+FIRST)
62 }
63 ER_m_text m_text_start m_text_size //执行域
64 { ; load address = execution address
* (InRoot$$Sections)
66 .ANY (+RO)
67 }
68 RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size //执行域
69 { ; RW data
70 .ANY (+RW +ZI)
71 * (NonCacheable.init)
72 * (NonCacheable)
73 }
74 ARM_LIB_HEAP +0 EMPTY Heap_Size //执行域
75 { ; Heap region growing up
76 }
77 ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size //执行域
78 { ; Stack region growing down
79 }
80 }
2.1.1 宏定义
2.1.2 堆栈空间
定义了程序的栈空间(Stack_Size)和堆空间(Heep_Size)
大小
2.2.3 分散加载
指定了代码和数据下载到哪里以及程序运行时代码和数据的存储位置。
默认的分散加载配置中仅分配了系统启动相关代码、中断向量表、Code、RO-data、RW-data 及ZI-data 这些大区域的地址
3 SCF文件什么时候编写
一:什么时候使用scatter file:
1、存在复杂的地址映射:例如代码和数据需要分开存放在多个区域。
2、存在多个存储器类型:包含Flash, ROM, SDRAM,快速SRAM。我们根据代码与数据的特性把他们发在不同的存储器中,比如中断处理部分放在快速SRAM内部来提高响应速度,而把不常用到的代 码放到速度比较慢的Flash内。
3、函数的地址固定定位:可以利用Scatter file实现把某个函数放在固定地址,而不管其应用程序是否已经改变或重新编译。
4、利用符号确定堆和堆栈。
5、内存映射的IO:利用scatter file可以实现把某个数据段放在精确的地址处。因此对于嵌入式系统来说scatter file是必不可少的,因为嵌入式系统采用了ROM, RAM和内存映射的IO。
如:堆放到SDRAM里面去
LR_IROM1 0x08000000 0x00100000 { ; load region size_region 加载域,起始地址是0x08000000,大小是0x00100000
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address 执行地址,就是链接地址
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00030000 { ; RW data
.ANY (+RW +ZI)
}
}
ARM_LIB_HEAP 0xC0000000 0x80000 { ; 指定堆栈地址
RW_HEAP 0xC0000000 0x80000 {
.ANY (HEAP)
}
}
3.1 控制加载区与运行区
把程序代码存储到单位容量价格便宜的 NAND flash 中,程序的执行区设定为 SRAM 中的位置。
- NAND flash 中的代码是不能像内部 FLASH 的代码那样直接提供给内核运行的
- 这样链接器就会生成一个配套的分散加载代码,该代码会把 NAND flash 中的代码加载到 SRAM 中,内核再从 SRAM中运行主体代码
- 大部分运行 Linux 系统的代码都是这样加载的
3.2 通过scf文件定制存储空间
3.2.1 查看全局变量的默认地址

编译成功后我们可以通过分散加载文件以及 *.map 文件查看变量的执行域。
#define m_data_start 0x2000000
#define m_data_size 0x00020000
RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size
{ ; RW data
.ANY (+RW +ZI)
* (NonCacheable.init)
* (NonCacheable)
}
变量 for_test 属于 RW 类型,程序运行时它会被加载到 0x20000000 之后的地址处。
*.map 文件中
Global Symbols
2
3 Symbol Name Value Ov Type Size Object(Section)
4
5 /* 一下代码省略 */
6 __Vectors_Size 0x00000400 Number 0 startup_mimxrt1052.o ABSOLUTE
7 SystemCoreClock 0x20000000 Data 4 system_mimxrt1052.o(.data)
8 g_xtalFreq 0x20000004 Data 4 fsl_clock.o(.data)
9 g_rtcXtalFreq 0x20000008 Data 4 fsl_clock.o(.data)
10 for_text 0x20000018 Data 17 main.o(.data)
变量 for_text 变量运行时被加载到 0x20000018 地址处,大小为 17 字节
3.2.2 通过修改分散加载设置
1 __attribute__((section("section_for_test")))
2
3 /* 定义一个全局变量 */
4 char for_test[] = "hello the world!";
5
6 int a = 10;
7 int b = 20;
关键字“attribute((section(“section_for_test”)))”将变量 for_test 输入节区样式设置为“section_for_test”
- 需要注意的是,变量 a 和 b 不会被设置
#define for_test_start 0x61000000
#define for_test_size 0x00001000
#define m_data_start 0x20000000
#define m_data_size 0x00020000
/****************** 第二部分 ******************/
#define m_data2_start 0x20200000
#define m_data2_size 0x00040000
/****************** 第三部分 *****************/
Load_section for_test_start for_test_size
{
/***************** 第四部分 ***************/
run_section_for_test m_data2_start m_data2_size
{ ; 执行域位于 DTCM
*(section_for_test)
}
}
使用宏定义指定新增加载域的起始地址 for_test_start 为 0x61000000(Flash 的 16M地址处),大小 for_test_size 为 0x00001000 即 4K 字节
设置加载域,加载域名字“Load_section”是用户自行定义,加载域的起始地址和大小使用宏定义指定。
设置执行域,执行域名字“run_section_for_test”是用户自行定义,执行域的起始地址和大小使用宏定义指定。 “*(section_for_test)”语句表示所有文件中的使用“section_for_test”指定的变量
编译、运行。最后查看 *.map 文件。
/****************************** 第一部分 *********************/
Load Region Load_section (Base: 0x61000000, Size: 0x00000014, Max: \0x00001000, ABSOLUTE)
Execution Region run_section_for_test (Base: 0x20200000, Size: \
0x00000014, Max: 0x00040000, ABSOLUTE)
Base Addr Size Type Attr Idx E Section Name Object
0x20200000 0x00000011 Data RW 1260 section_for_test main.o
/********************** 第二部分 ******************/
Image Symbol Table
Local Symbols
Symbol Name Value Ov Type Size Object(Section)
section_for_test 0x20200000 Section 17 main.o(section_for_test)
/********************* 第三部分 ******************/
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
for_test 0x20200000 Data 17 main.o(section_for_test)
说明:
- 第一部分,执行域和加载域的说明,以加载域“Load_section”为例,加载域的基地址为0x61000000,最大空间 0x00001000,使用的大小为 0x00000014。
- 第二部分,输入节区样式说明。在程序中定义了“section_for_test”输入节区样式。这行语句表示“section_for_test”输入节区样式的值为 0x20200000 即起始地址,大小为 17 个字
- 第三部分,变量“for_test”的说明,从代码中可知该变量执行域位于 0x20200000 地址处,大小为 17 个字节,该变量来自 main.c 文件,属于“section_for_test”输入节区样式。
本文详细介绍了STM32工程中SCF文件的作用,包括程序的存储与运行机制、分散加载文件的结构、宏定义、堆栈空间设置,以及何时编写SCF文件和如何通过它定制存储空间,如控制加载区与运行区、查看和修改全局变量地址。
5684

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



