分散加载Scatter Loading是一种由链接器Linker提供的、用于精确控制程序各个段如代码、数据在目标存储器如 Flash、RAM中加载地址和执行地址的机制。其核心在于将单一的、线性的加载视图Load View映射到可能不连续且具有不同特性的物理存储空间形成执行视图Execution View。它通过一个称为分散加载描述文件Scatter Loading Description File通常为.sct文件的文本脚本指导链接器完成这一复杂的内存布局。分散加载的核心原理加载域与执行域分散加载的运作基于两个核心概念加载域Load Region和执行域Execution Region。概念描述类比加载域程序映像Image最初被存储烧录的存储器区域。一个加载域对应一个物理存储块如 Flash具有起始地址和大小。仓库的“货架”货物代码/数据初始存放的位置。执行域程序在运行时其代码或数据实际被访问执行或读写的存储器区域。一个加载域可以包含多个执行域它们可以位于相同或不同的物理地址。工厂的“生产线”或“工作台”货物被搬移至此以供使用。链接器的工作流程分为两步加载时将编译器生成的各个目标文件.o中的输入段Input Section如.text,.data合并并按照分散加载脚本的指示放置到指定的加载域地址生成最终的二进制映像文件如.axf,.bin。运行时启动时在main()函数执行前由启动代码Startup Code中的__main函数C库初始化例程负责将需要移动的数据如已初始化的全局变量.data段从其加载地址如 Flash拷贝到其执行地址如 RAM并将未初始化的静态变量区域.bss段在 RAM 中清零。对于代码段.text和只读数据段.rodata如果其加载地址和执行地址相同通常在 Flash 中原地执行XIP则无需移动。分散加载描述文件.sct语法详解以 ARM CompilerKeil MDK为例一个.sct文件的基本结构如下LR_IROM1 0x08000000 0x00010000 { ; 定义一个加载域 (Load Region) ER_IROM1 0x08000000 0x00010000 { ; 定义一个执行域 (Execution Region) *.o (RESET, First) ; 输入节描述将RESET段放在最前 *(InRoot$$Sections) ; 特殊的库段必须放在根域 .ANY (RO) ; 所有RO只读代码和常量内容 } RW_IRAM1 0x20000000 0x00005000 { ; 另一个执行域位于RAM .ANY (RW ZI) ; 所有RW已初始化变量和ZI零初始化变量内容 } }关键组成部分解析加载域定义LR_IROM1 0x08000000 0x00010000LR_IROM1加载域名可自定义。0x08000000加载域的起始地址STM32 Flash 起始地址。0x00010000加载域的最大长度64KB。执行域定义ER_IROM1 0x08000000 0x00010000嵌套在加载域内定义了代码/数据在运行时的位置。起始地址和长度属性与加载域可以不同。如果省略地址链接器会自动安排在加载域之后如果省略长度则使用加载域的剩余空间。输入节描述模块选择模式*.o (RESET, First)选择所有目标文件中的RESET段并使用First属性强制将其放置在该执行域的首位。这通常用于放置中断向量表。*(InRoot$$Sections)这是一个必须包含在根执行域地址固定的执行域的特殊符号集合包含了 C 库初始化等关键代码。.ANY (RO)选择所有未被前面模式匹配的模块中的只读RO段。RO是属性选择器代表.text代码和.rodata只读数据。RW和ZI分别代表已初始化的读写数据段和零初始化数据段它们通常需要被分配到 RAM 中的执行域。分散加载的主要应用场景与用法示例1. 配合 Bootloader 实现应用程序重定位这是最经典的应用。Bootloader 固定在 Flash 起始地址用户应用程序APP需要从后续地址开始存放。这需要修改 APP 工程的分散加载文件。APP 的分散加载文件 (app.sct) 示例/* 假设 Bootloader 占用 0x08000000 - 0x08003FFF (16KB) */ LR_IROM1 0x08004000 0x0000C000 { ; APP加载域从0x08004000开始长度48KB ER_IROM1 0x08004000 0x0000C000 { ; 执行域地址与加载域相同XIP *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00005000 { ; RAM域不变 .ANY (RW ZI) } }同时在 APP 的main()函数开始需要重设中断向量表偏移寄存器如 ARM Cortex-M 的SCB-VTOR为0x08004000确保中断能正确跳转到新的向量表。2. 将关键代码或数据加载到高速内存如 CCM RAM、TCM执行为了提升性能可以将对实时性要求极高的函数如中断服务程序、算法核心循环放到零等待周期的 SRAM如 STM32 的 CCM RAM中执行。实现步骤方法一通过分散加载文件指定LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { .ANY (RO) ; 大部分代码放在Flash } ER_CCM 0x10000000 0x00001000 { ; CCM RAM执行域起始地址0x10000000 my_fast.o (RO) ; 将my_fast.c文件中的所有代码段放到CCM *(.FastSection) ; 或者将所有放在.FastSection段的内容放到CCM } RW_IRAM1 0x20000000 0x00005000 { .ANY (RW ZI) } }在 C 代码中需要使用__attribute__将特定函数或变量分配到自定义段。// 将函数放到名为 .FastSection 的段中 __attribute__((section(.FastSection))) void critical_isr(void) { // 关键中断处理代码 }方法二在代码中直接指定绝对地址不推荐灵活性差// 将变量定位到特定地址 uint32_t __attribute__((at(0x20001000))) high_speed_buffer[1024];3. 实现非连续存储器的利用当芯片具有多块不连续的物理内存时如两块独立的 SRAMSRAM1 和 SRAM2分散加载可以高效地管理它们。LR_IROM1 0x08000000 0x00100000 { ER_IROM1 0x08000000 0x00100000 { .ANY (RO) } RW_IRAM1 0x20000000 0x00010000 { ; SRAM1 .ANY (RW ZI) ; 默认分配到这里 } RW_IRAM2 0x20010000 0x00008000 { ; SRAM2 large_buffer.o (RW) ; 将某个模块的大缓冲区单独放到SRAM2 } }4. 将函数或变量固定在特定地址例如需要在两个独立的应用程序如 Bootloader 和 APP之间共享一段数据或者为某个特定的函数提供固定的入口地址以供调用。LR_IROM1 0x08000000 0x00100000 { ER_IROM1 0x08000000 { .ANY (RO) } ER_SHARED 0x0800FC00 FIXED 0x400 { ; FIXED属性固定该执行域的地址和大小 shared_data.o (RW) ; 共享数据模块 } RW_IRAM1 0x20000000 0x00010000 { .ANY (RW ZI) } }使用FIXED属性可以确保该执行域的地址不会被链接器优化或移动。在 C 代码中对应的变量或函数通过自定义段名被引导至此。在 Keil MDK 中的配置步骤打开Options for Target - Linker选项卡。取消勾选Use Memory Layout from Target Dialog。在Scatter File输入框中选择或输入自定义的.sct文件路径。重新编译工程链接器将依据此文件进行链接。总结与注意事项分散加载是进行复杂内存管理、性能优化和实现高级功能如 Bootloader、双系统的基石。其本质是通过脚本将程序的逻辑段与物理存储的地址和特性进行解耦和精确映射。在使用时必须清晰理解加载域与执行域的区别以及启动时代码和数据的搬移过程。对于简单的项目IDE 默认的链接脚本通常足够但当项目涉及多存储区域、性能关键代码、固件升级IAP或特定地址约束时掌握并编写分散加载文件就成为嵌入式开发者的必备技能。调试时可以通过生成的.map文件来验证各段的最终布局是否符合预期。参考来源ARM Cortex-M底层技术六分散加载的简单介绍分散加载的简单介绍STM32 分散加载ARM Cortex-M底层技术十三手把手教你写分散加载MDK 分散加载文件剖析(一)分散加载的实现