深入解析SCT分散加载文件
在原有的sct的目录下新建一个文件夹放我们自己的sct文件
可以这样找到sct文件
1、什么是分散加载文件?
分散加载文件通常以.sct结尾,英文名是:Linker Control File,scatter loading,链接器根据这个文件的配置来分配各个节区的地址空间,并且生成分散加载代码,因此我们只要修改分散加载文件,链接器就能自动帮我们确定代码、变量等这些内容在内存中(Flash和RAM上)的地址。
2、分散加载文件在什么时候起作用?
它主要是在链接阶段产生作用,如果你之前用过gcc+makefile编译mcu项目,那可以看到在目录中会有.ld结尾
的文件,这个文件和我们这里的分散加载文件作用类似,都是在链接阶段起作用。
编译链接流程:
3.分散加载文件的格式
这个 sct 文件是 ARM Cortex-M 系列芯片的分散加载描述文件,用于定义程序在编译后,代码和数据在 Flash(ROM)与 RAM 中的存储地址和分配规则。
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.适用场景与芯片推测
从地址和大小来看,这个 sct 文件大概率适用于STM32F103 系列芯片(如 STM32F103VET6,Flash 512KB、RAM 64KB,或 STM32F103ZET6,Flash 512KB、RAM 128KB),是该系列芯片的通用分散加载配置。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************LR_IROM1 0x08000000 0x00070000 { ; load region size_regionER_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_regionER_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)。
示例解析:
__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
属性就是给编译器 “开特例”,专门应对 “间接调用” 这种编译器默认识别不了的场景,确保关键代码不被误删。