目录
[1 什么是分散加载文件?](#1 什么是分散加载文件?)
[sct 分散加载文件: 内存布局管理](#sct 分散加载文件: 内存布局管理)
[2 分散加载文件在什么时候起作用?](#2 分散加载文件在什么时候起作用?)
[3 创建自己的sct分散加载文件](#3 创建自己的sct分散加载文件)
[4 分散加载文件的格式解析(个人理解)](#4 分散加载文件的格式解析(个人理解))
[4 分散加载文件的格式解析(deepseek)](#4 分散加载文件的格式解析(deepseek))
[1.核心结构解析:3 个关键区块](#1.核心结构解析:3 个关键区块)
[(2)执行域 1:ER_IROM1(代码区)](#(2)执行域 1:ER_IROM1(代码区))
[(3)执行域 2:RW_IRAM1(数据区)](#(3)执行域 2:RW_IRAM1(数据区))
[RAM 执行域:RW_IRAM1(数据区)](#RAM 执行域:RW_IRAM1(数据区))
[第二个加载域:LR_IROM2(自定义 Flash 区)](#第二个加载域:LR_IROM2(自定义 Flash 区))
[5 C语言__attribute__关键字](#5 C语言__attribute__关键字)
[6 一些可能存在的疑问与解答](#6 一些可能存在的疑问与解答)
[1. gcc+makefile编译时,对应.sct文件的链接文件是什么类型?](#1. gcc+makefile编译时,对应.sct文件的链接文件是什么类型?)
[2. 有时候代码在Sram中运行比在Flash中运行耗时是为什么?](#2. 有时候代码在Sram中运行比在Flash中运行耗时是为什么?)
[Flash 加速读取指令机制:](#Flash 加速读取指令机制:)
[3. 基于芯片内部指令集流水线、RTOS操作系统特征,如何进行局部调试和优化(关于指令集流水线)](#3. 基于芯片内部指令集流水线、RTOS操作系统特征,如何进行局部调试和优化(关于指令集流水线))
[4. cache命中率的影响因素](#4. cache命中率的影响因素)
[5. SCT文件会把整个调度系统(函数+全局变量)都放到RAM里吗?](#5. SCT文件会把整个调度系统(函数+全局变量)都放到RAM里吗?)
[6. SCT是如何控制代码和数据放置的?](#6. SCT是如何控制代码和数据放置的?)
1 什么是分散加载文件?
分散加载文件通常以.sct结尾,英文名是:Linker Control File,scatter loading,链接器根据这个文件的配置来分配各个节区的地址空间,并且生成分散加载代码,因此我们只要修改分散加载文件,链接器就能自动帮我们确定代码、变量等这些内容在内存中(Flash和RAM上)的地址。
sct 分散加载文件: 内存布局管理
-
核心问题:编译好的代码和数据具体放在芯片内存的哪个地址?比如:
-
中断向量表应该放在哪里?
-
程序代码(Flash)和数据(RAM)如何分布?
-
如何将某个函数或变量放到一个特定的高速内存区?
-
如何配置堆栈的空间?
-
-
sct 文件:是 ARM 编译器(ARMCC、ARMClang)使用的链接脚本,全称是 Scatter-Loading Description File。它精确地指导链接器,将程序的各个段(如
.text,.data,.bss)放置到指定的物理地址上。 -
范畴:链接阶段、内存映射、存储管理。这是嵌入式开发(尤其是资源受限、没有MMU的MCU)特有的、至关重要的知识。
2 分散加载文件在什么时候起作用?
它主要是在链接阶段产生作用,如果你之前用过gcc+makefile编译mcu项目,那可以看到在目录中会有.ld结尾
的文件,这个文件和我们这里的分散加载文件作用类似,都是在链接阶段起作用。
编译链接流程:
3 创建自己的sct分散加载文件
在原有的sct的目录下新建一个文件夹放我们自己的sct文件,点击Edit就可以开始编辑sct文件了。

可以这样找到sct文件


4 分散加载文件的格式解析(个人理解)

在默认的 sct 文件配置中仅分配了 Code、RO-data、RW-data 及 ZI-data 这些大区域的地址,链接时各个节区(函数、变量等)直接根据属性排列到具体的地址空间。
sct 文件中主要包含描述加载域及执行域的部分,一个文件中可包含有多个加载域,而一个加载域可由多个部分的执行域组成。同等级的域之间使用花括号"{}"分隔开,最外层的是加载域,第二层"{}"内的是执行域。

个人理解总结
-
加载域会将大括号内的东西全部加载到指定地址上,但是具体要从那里读取代码读取数据执行,要看执行域的地址。
-
如果加载域在flash,执行域在SRAM上,就会在上电时自动从flash上copy数据到SRAM上,之后要读取数据/指令就会在SRAM上进行。
-
相当于加载域上有所有东西,如果执行域和加载域地址一样就不用copy数据,如果不一样就搬运数据到执行域地址,等真要用的时候到执行域上面拿。
需要注意的点
-
执行地址在Flash区域的代码,其加载地址和执行地址必须对齐,不然会导致访问时出错。
-
简单来说,假设一个加载域的地址是0x0800 0000,而这个加载域中有两个执行域,一个在flash,地址为0x0800 0000,叫执行域F;另一个在RAM上,地址为0x2000 0000,叫执行域R;此时flash的执行域应该写在前面,不然就会导致访问时出错。
-
出错原理:如果执行域R写在加载域大括号中的第一个位置,执行域R的数据会放入加载域的地址上,放完了执行域R的数据之后才会放执行域F的数据,问题是,我们一开始给执行域F规定的地址是从0x0800 0000开始,但此时0x0800 0000的位置上已经放了执行域R的数据,假设执行域R的数据大小为0x74,则执行域F的真正数据加载地址就是0x0800 0074,此时加载地址与执行地址就形成了"错位",而程序在执行时,还是会从0x0800 0000中去取执行域F的第一条代码(此时执行域F的第一条代码真正加载位置在0x0800 0074),就会导致程序执行出错。
4 分散加载文件的格式解析(deepseek)
这个 sct 文件是 ARM Cortex-M 系列芯片的分散加载描述文件,用于定义程序在编译后,代码和数据在 Flash(ROM)与 RAM 中的存储地址和分配规则。

加载域名 起始地址 大小{ ;加载区域大小(分号后面是注释)
运行域名 起始地址 大小 ;执行地址
{
中断向量表起始地址,+First表示强制放到首地址
ARM相关库,InRoot$$Sections即ARM库的链接器标号,主要作用是进行重定位COPY RW到RAM时,
提供RW在flash上的起始地址,
然后在RW区后面创建ZI区域。库函数__main函数中有这个段。它是 __main()的一部分。
编译文件RO只读在该区域
}
运行内存名字 起始地址 大小
{
编译可读可写,静态区
}
}
1.核心结构解析:3 个关键区块
整个文件由「加载域(Load Region)」和其包含的「执行域(Execution Region)」组成,加载域对应物理存储(如 Flash),执行域对应实际运行时的存储(如 Flash 或 RAM)。
(1)加载域:LR_IROM1
这是整个程序的总加载区域,所有代码最终会被烧录到这个区域。
-
0x08000000:加载起始地址,是 STM32 等主流 Cortex-M 芯片的Flash 默认起始地址。 -
0x00080000:加载区域大小,即 512KB(16 进制0x80000= 十进制 524288 字节 = 512KB),代表 Flash 的可用空间。 -
作用:规定了程序 "烧录到哪里" 以及 "最多能烧录多大"。
(2)执行域 1:ER_IROM1(代码区)
这是只读 代码和数据的执行区域,与加载域地址相同,说明代码直接在 Flash 中运行(无需复制到 RAM)。
-
0x08000000:执行起始地址,与加载地址一致,对应 Flash 地址。 -
0x00080000:执行区域大小,与加载区域大小一致,限制 Flash 中可运行的代码总量。 -
内部包含的内容(按优先级顺序):
-
*.o (RESET, +First):所有目标文件的「复位向量表(RESET)」,且强制放在该区域的最开头(芯片上电后最先读取复位向量)。 -
*(InRoot$$Sections):链接器内部使用的关键段,确保 C 库初始化等核心代码被正确包含。 -
.ANY (+RO):所有目标文件的「只读数据段(RO)」,包括代码指令、const 修饰的常量。 -
.ANY (+XO):所有目标文件的「可执行只读数据段(XO)」,通常是编译器优化后的只读代码或数据。
-
(3)执行域 2:RW_IRAM1(数据区)
这是可读写数据的执行区域,对应芯片的 RAM,程序运行时的变量会存放在这里。
-
0x20000000:执行起始地址,是 STM32 等 Cortex-M 芯片的RAM 默认起始地址。 -
0x00020000:执行区域大小,即 128KB(16 进制0x20000= 十进制 131072 字节 = 128KB),代表 RAM 的可用空间。 -
内部包含的内容:
-
.ANY (+RW +ZI):所有目标文件的「可读写数据段(RW)」和「零初始化数据段(ZI)」。-
RW:已初始化的全局变量 / 静态变量(如
int a = 10;),上电后会从 Flash 复制到 RAM。 -
ZI:未初始化的全局变量 / 静态变量(如
int b;),上电后会被自动清零。
-
-
2.关键规则与作用
-
地址映射规则:明确 Flash(0x08000000 开始)存代码和只读数据,RAM(0x20000000 开始)存可读写变量,避免存储冲突。
-
大小限制规则:Flash 最大用 512KB,RAM 最大用 128KB,超过会导致编译报错,防止程序超出芯片硬件资源。
-
优先级规则:复位向量表强制放 Flash 最开头,确保芯片上电后能正确找到启动入口,这是 Cortex-M 芯片启动的硬性要求。
3.分散加载文件解析
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00070000 { ; load region size_region
ER_IROM1 0x08000000 0x00070000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
*.o (myram)
}
}
LR_IROM2 0x08070000 0x00010000 { ; load region size_region
ER_IROM2 0x08070000 0x00010000 { ; load address = execution address
*.o (myflash)
}
}
这个 sct 文件是在之前基础上扩展的分散加载描述文件,新增了一个独立的加载域和执行域,用于管理自定义数据段(myflash和myram)。下面详细解析其结构和作用:
一、整体结构概述
文件包含两个加载域(LR_IROM1、LR_IROM2) ,每个加载域下各包含一个执行域(ER_IROM1、ER_IROM2),同时保留了原有的 RAM 执行域(RW_IRAM1)。核心变化是新增了LR_IROM2加载域和myflash、myram自定义段,用于将特定数据分配到指定的 Flash 或 RAM 区域。
二、各区域详细解析
第一个加载域:LR_IROM1(主程序区)
-
加载地址 :
0x08000000(Flash 起始地址,同前) -
大小 :
0x00070000(448KB,相比之前的 512KB 减少了 64KB,预留空间给新增的 LR_IROM2) -
作用:存储程序主体(代码、复位向量、只读数据等)。
-
其包含的执行域
ER_IROM1:-
地址和大小与 LR_IROM1 一致(
0x08000000,0x00070000),说明代码直接在 Flash 中运行。 -
内容与之前基本相同:
- 复位向量表(
RESET)、链接器核心段(InRoot$$Sections)、只读代码 / 数据(+RO、+XO)。
- 复位向量表(
-
RAM 执行域:RW_IRAM1(数据区)
-
地址 :
0x20000000(RAM 起始地址,同前) -
大小 :
0x00020000(128KB,未变) -
新增内容:
*.o (myram)-
含义:所有目标文件中标记为
myram的段,会被强制分配到这个 RAM 区域。 -
用途:可通过代码中的
__attribute__((section("myram")))修饰变量,将其指定存放在此 RAM 区域(例如:int buf[1024] __attribute__((section("myram")));)。
-
第二个加载域:LR_IROM2(自定义 Flash 区)
-
加载地址 :
0x08070000(Flash 偏移地址,位于 LR_IROM1 之后:0x08000000 + 0x00070000 = 0x08070000) -
大小 :
0x00010000(64KB,正好是 LR_IROM1 减少的空间,避免地址重叠) -
作用:专门用于存储标记为
myflash的自定义数据(非程序主体,如校准参数、配置表等)。 -
其包含的执行域
ER_IROM2:-
地址和大小与 LR_IROM2 一致(
0x08070000,0x00010000),数据直接存储在该 Flash 区域,运行时无需复制到 RAM。 -
内容:
*.o (myflash)-
含义:所有目标文件中标记为
myflash的段,会被强制分配到这个 Flash 区域。 -
用途:通过
__attribute__((section("myflash")))修饰常量,将其固定存放在此区域(例如:const char config_data[] __attribute__((section("myflash"))) = "calib_param";)。
-
-
三、关键变化与用途
-
Flash 空间拆分:将原 512KB Flash 拆分为两部分:
-
主程序区(448KB,
0x08000000~0x0806FFFF):存代码和默认只读数据。 -
自定义区(64KB,
0x08070000~0x0807FFFF):存专用数据(如固件参数、日志模板等),避免与主程序混杂。
-
-
自定义段管理:
-
myflash段:强制存放在指定 Flash 区域,适合存储需要持久化、不随程序更新的只读数据(如硬件校准值)。 -
myram段:强制存放在指定 RAM 区域,适合管理特殊用途的变量(如高频访问的缓存、DMA 缓冲区等)。
-
-
地址冲突避免 :两个加载域(LR_IROM1 和 LR_IROM2)的地址范围连续且不重叠(
0x08000000~0x0806FFFF和0x08070000~0x0807FFFF),确保编译和运行时无地址冲突。
四、适用场景
此配置适合需要在 Flash 中划分专用区域存储自定义数据的场景,例如:
-
嵌入式设备的出厂参数(需固定地址,方便读取);
-
程序中需要独立管理的大段只读数据(如字库、图片资源);
-
对 RAM 有特殊分区要求的场景(如隔离普通变量和 DMA 缓冲区)。
如果需要进一步指定myflash或myram的地址范围(而非默认占满分配的空间),可以在段定义中细化,例如:*.o (myflash) 0x08070000 0x00008000(限制myflash仅用前 32KB)。
示例解析:
cpp
__attribute__ ((used, section ("myflash")))
void flash_function_pre()
{
int i = 0;
i = i / 3;
return;
}
void flash_function()
{
int i = 0;
i = i / 3;
return;
}
__attribute__ ((used, section ("myram")))
void sram_function(void)
{
int i = 0;
i = i / 3;
return;
}
__attribute__ ((used, section ("myram")))
void sram_function_pre(void)
{
int i = 0;
i = i / 3;
return;
}
uint32_t call_flash_func_time_record(void)
{
uint32_t start_tick = HAL_GetTick();
uint32_t end_tick = 0;
uint32_t i_counter = 10000000;
void (* volatile fpointer_1)(void) = flash_function;
void (* volatile fpointer_2)(void) = flash_function_pre;
while( i_counter > 0 )
{
fpointer_1();
fpointer_2();
i_counter --;
}
end_tick = HAL_GetTick();
return (end_tick - start_tick);
}
__attribute__ ((used, section ("myram")))
uint32_t call_sram_func_time_record(void)
{
uint32_t start_tick = HAL_GetTick();
uint32_t end_tick = 0;
uint32_t i_counter = 10000000;
void (* volatile fpointer_1)(void) = sram_function;
void (* volatile fpointer_2)(void) = sram_function_pre;
while( i_counter > 0 )
{
fpointer_1();
fpointer_2();
i_counter --;
}
end_tick = HAL_GetTick();
return (end_tick - start_tick);
}
通过attribute((section("xxx")))将 4 个函数分别指定到sct文件定义的自定义段中:
-
flash_function_pre():被section("myflash")修饰,存放在LR_IROM2对应的 Flash 区域(0x08070000~0x0807FFFF)。 -
flash_function():未修饰,默认存放在主 Flash 区域(ER_IROM1,0x08000000~0x0806FFFF)。 -
sram_function()和sram_function_pre():被section("myram")修饰,存放在RW_IRAM1对应的 RAM 区域(0x20000000~0x2001FFFF)。 -
附加
used属性:确保编译器不会因 "未被直接调用" 而优化删除这些函数(因为通过 函数指针 间接调用)。
简单说,used属性的核心作用是强制编译器 "保留" 某个函数 / 变量,哪怕代码里没有直接调用它,也不会把它当成 "无用代码" 删掉 ------ 这正好适配你代码里 "用函数指针间接调用" 的场景。
1.先搞懂:编译器为什么会 "删代码"?
编译器在编译时会做 "死代码消除(Dead Code Elimination)" 优化。它会扫描代码,判断哪些函数 / 变量 "没用":
-
对函数来说,"没用" 的标准是没有任何地方直接调用它 (比如没写过
flash_function_pre();这种直接调用)。 -
编译器默认不知道 "函数指针会间接调用它",会误判这类函数是 "死代码",最终不把它编译到最终的可执行文件(.bin/.hex)里。
举个例子:如果flash_function_pre()没加used属性,编译器看到代码里只有fpointer_2 = flash_function_pre;(赋值给指针),没有flash_function_pre();(直接调用),就会认为这个函数没用,把它从编译结果里删掉。
2.再看: used 属性是怎么 "保住" 代码的?
attribute((used))是 GCC 编译器的一个特殊属性,它给编译器发了一个明确的指令:
-
"这个函数 / 变量是有用的,哪怕你没看到直接调用,也必须把它保留在最终的代码里,不能删!"
-
这样一来,被
used修饰的flash_function_pre()和sram_function_pre(),即使只被赋值给函数指针,也能正常编译到指定的myflash或myram段里,后续用指针调用时才不会出现 "找不到函数" 的错误(比如硬件异常、程序跑飞)。
3.结合代码更直观
代码里,flash_function_pre()和sram_function_pre()的调用逻辑是 "间接的":
-
先把函数地址赋值给指针:
fpointer_2 = flash_function_pre; -
再通过指针调用:
fpointer_2();
如果没加used,编译器在第一步时,会因为没看到flash_function_pre()的直接调用,先把它删掉;到第二步用指针调用时,指针指向的地址是 "空的" 或者 "无效的",程序就会出错。而加了used之后,函数被保留,指针能正确指向函数地址,调用才会正常执行。
简单总结:used属性就是给编译器 "开特例",专门应对 "间接调用" 这种编译器默认识别不了的场景,确保关键代码不被误删。
5 C语言__attribute__关键字
参考文章:【外部】C语言__attribute__的使用_c attribute-CSDN博客
在C语言中,attribute 是GNU C编译器提供的一种特性,用于向编译器提供函数、变量和类型的额外信息。这些信息可以帮助编译器进行优化、错误检查或控制对齐方式等。attribute可以设置函数属性、变量属性和类型属性。
通过 attribute,我们可以将函数或变量放入指定的内存区域中。
6 一些可能存在的疑问与解答
1. gcc+makefile编译时,对应.sct文件的链接文件是什么类型?
在使用gcc和 makefile进行编译时,.sct文件通常指的是用于链接阶段的链接脚本 文件。在ARM编译器中,.sct 文件通常被用作链接器脚本(scatter file),而在gcc环境中,其对应的文件类型为 链接脚本,文件扩展名通常为.ld(Linker Script)
2. 有时候代码在Sram中运行比在Flash中运行耗时是为什么?

从Flash运行代码时,取 指令 由I-code总线完成,I-code在读取Flash时不仅有预取指加速,通常还有预取指令和缓存机制,延迟小,吞吐高。
所谓缓存机制,在于stm32f4中,增加了一个缓冲区,可以将flash中的指令或者数据提前取到缓冲区中,cpu访问缓冲区的速度比访问ram的速度更快。但是如果缓冲区中的指令是随机的,比如我们在FreeRTOS中有很多的判断,这些判断是随机的,那么cache就要重新从flash里面缓存指令,这时候从flash取指令就要等待,那么就比Sram要慢。这里说的cache就是下文的指令缓存存储器。

而从 SRAM 运行代码时,走的是System-Bus(S-Bus),和其他外设共享宽带,效率会低一些。

-
在这个图中,I-Code总线走的是专用的AHB instruction bus(紫线),虽然通过Flash interface,但它是Cortex-M 内部的 专用通道,只用于Fetch指令,I-Code路径有更高优先级,对时序优化得更好。SRAM访问是通过S-Bus+AHB system bus(蓝线),S-Bus是通用型数据访问路径,与外设、DMA、外部SRAM等共用资源,总线冲突和竞争导致性能下降。
-
另外,Flash interface 本身有预取缓存(prefetch buffer)和ART(自适应实时加速器)即使最终都走AHB,它可以将指令预先加载进内部buffer,隐藏Flash的访问延迟。
Flash 加速读取指令机制:
以下来源:《STM32F4xx参考手册》



下面是上图的指令缓存存储器的英文原文:

3. 基于芯片内部指令集流水线、RTOS操作系统特征,如何进行局部调试和优化(关于指令集流水线)
三级流水线一般指 取值 译码 执行。
-
关于如何优化cache,比如,状态机中最常用的一种方法:Switch Cace法,适合状态机不是很复杂时使用。因为switch-case 的原理是从上到下挨个比较,越靠后,查找耗费的时间就越长,所以要注意状态和事件在各自的 switch 语句中的安排顺序,出现频率高或者实时性要求高的状态和事件的位置应该尽量靠前。
-
这里的详细解释是,CPU的流水线会提前加载Switch Case中的前的第一个Case(没错,它就是猜这里的第一个Case执行的概率最大),如果猜错了,它就会扔掉所有的已经加入流水线的指令,造成效率降低。猜错了,它就要重新去Flash里面去取指令,但是Flash重新取指令是需要等待时间的,而SRAM是不需要的。
4. cache命中率的影响因素
空间层面:
- 空间局部性是指如果一个存储单元被访问,那么它附近的存储单元很可能也会被访问。在Cache中,这种特性体现为当CPU读取一个数据时,Cache会把包含这个数据的一整个Cache行(包含相邻数据)读取进来。
时间层面:
- 时间局部性是指如果一个存储单元被访问,那么它很可能在不久的将来会被再次访问。在Cache中,频繁访问的数据会一直保留在Cache中(只要Cache替换策略没有把它替换掉),从而提高命中率。顺序访问与随机访问:顺序访问数据结构(如数组)通常比随机访问数据结构(如链表)具有更高的Cache命中。
5. SCT文件会把整个调度系统(函数+全局变量)都放到RAM里吗?
不会。SCT是链接器控制内存布局的配置文件,它定义的是哪些代码和数据放在哪块内存区域。一般调度器的函数代码(比如任务切换函数)会被放在Flash中执行,而全局变量(如任务控制块、调度标志等)则会被放在RAM 中供运行时读写,这样来加快单片机的运行速度。
6. SCT是如何控制代码和数据放置的?
SCT文件通过定义Load Region(加载区)和Execution Region(执行区)来控制段的布局。比如 .text 段(代码)通常放在Flash, . data(已初始化变量)和 .bss(未初始化变量)放在RAM。链接器根据这些规则将目标文件中相应的段布局到指定地址。