一.什么是分散加载及其作用
1.简单来说MCU是将Flash,SRAM等存储介质映射成一个虚拟地址来访问的而映射的虚拟地址是划分了很多个区域,再不自己定义分散加载文件时,Keil MDK工程的默认程序各个部分的摆放如下图所示(示例,其他Cortex-M系列MCU的程序摆放与之类似):
而分散加载文件就是可以通过这个脚本文件来自己定义各个不同的位置,哪里存的是代码、哪里存的是数据,去哪个特定的地址找到下一步需要运行的函数的东东,就是高速编译器把每一个编译好的函数、数据放到具体的哪一个物理地址,分散加载脚本有以下几种用途
a:Bootloader & 程序升级
一个Bootloader程序和一个用户程序,那么这就需要调整分散加载文件,以达成在一个Flash里面同时摆放两个不同程序的目的。
b:访问扩展存储&对存储区的划分
如果你要把外扩的存储用于运行代码/扩展RW数据段等用途,简单来说就是把片内地址映射到片外,需要按照寻址空间的方式来访问扩展存储的话,比如扩展Nor-Flash、扩展SDRAM、扩展SRAM等,那就需要分散加载配合。
所以分散加载的根本目的就是:
<1>把RO-data数据段、RW数据段、从片内程序存储区里面(一般是片内Flash),搬到片内程序运行区(一般是片内SRAM);
<2>在片内程序运行区(一般是片内SRAM)内分配ZI数据段运行需要的空间并把这段数据初始化为0;
<3>初始化堆栈;
<4>对于有些指定加载到程序运行区(一般是片内SRAM)的RO数据段,把他们加载到程序运行区(一般是片内SRAM)里面。
要了解分散加载文件前首先需要对以下各个概念进行了解。
Code:为程序代码部分;
RO-Data:表示程序定义的常量及 const 型数据;
RW-Data:表示已经初始化的静态变量,变量有初值;
ZI-Data:表示未初始化的静态变量,变量无初值
二、分散加载的基本结构
首先列举下KEIL中默认的分散加载文件如下图:
分散加载文件的基本节构,如下:
LOAD_ROM_1 0x0000 【加载域描述】这段是要告诉链接器,你的程序是存在哪里?我从哪里去找需要执行的代码。
{
EXEC_ROM_1 0x0000 【运行域描述】这段是要告诉链接器,你的程序在哪里执行,在ARM Cortex-M系列的绝大多 数MCU中加载域以及运行域是在同一个空间上的,即片内Flash。
{
program1.o (+RO) 【输入节描述】就是告诉链接器,具体把哪一个以及怎么把这一个obj文件放到运行域里面
}
DRAM 0x18000 0x8000 【运行域描述】这里指的是RAM空间的运行域,下面会解释为什么这里会有两个运行域
{
program1.o (+RW,+ZI) 【输入节描述】告诉链接器,去哪里找执行程序是需要使用的变量以及数据
}
}
LOAD_ROM_2 0x4000 【另一个加载域描述】同一个工程可以有多个加载域,就好像同一台电脑可以装几个操作系统
{
EXEC_ROM_2 0x4000 【另一个运行域描述】
{
program2.o (+RO) 【另一个输入节】
}
SRAM 0x8000 0x8000 【另一个运行域描述】
{
program2.o (+RW,+ZI) 【另一个输入节】
}
}
所以分散加载可以简单理解为的最基本结构就是至少3个域(这个事实上不对,但是对于大多数Cortex-M系列MCU的分散加载可以这样简单理解): 至少一个加载域、建议两个运行域(一个RO运行域、一个RW+ZI运行域), 就是你要告诉链接器至少3个信息,即:从哪里加载程序(至少一个域)、在哪里运行程序(至少一个域)、在哪里读写程序运行中用到的变量(至少一个域,实际上也可以跟运行程序的域在一起,但强烈建议分开)。
三、分散加载的语法
分散加载的语言属于C语言也不是汇编语言而是一种特殊的脚本语言,他有自己的语法规则而且他不能被调试意味着写错了只能自己慢慢找错误
1、加载域描述
加载域
load_region_name (base_address | ("+" offset)) [attribute_list] [max_size]
{
execution_region_description+
}
2、执行域描述
执行域
exec_region_name (base_address | "+" offset) [attribute_list] [max_size | length]
{
input_section_description*
}
3、输入节描述
输入节:
input_section_description ::=
module_select_pattern
[ "(" input_section_selector ( "," input_section_selector )* ")" ]
input_section_selector ::=
("+" input_section_attr | input_section_pattern | input_symbol_pattern)
三、代码加载到片内SRAM中运行&部分规则
代码加载到SRAM上运行就意味着RO、RW、ZI都要放到SRAM上,意味着SRAM将要占用大量内存,但是当你程序没有用到该部分代码时可以共用这一部分内存
“InRoot$$Sections”与“根区”的说明:
分散加载受到的一个限制是负责创建执行区的代码和数据不能将自己复制到另一个位置,分散加载需要有一个根区,根区本质是一个RO执行域,其执行地址与其加载地址相同。
根区中需要包含以下几个节:
<1>复制代码和数据的程序代码,也就是__main.o和__scatter*.o(__scatter.o、__scatter_copy.o、__scatter_zi.o等)
<2>执行压缩的__dc*.o(同样包含很多以__dc开头的obj文件)
<3>Region$$Table节,包含要复制和压缩的带么和数据的地址
如果要在其他非根区内指定*(+RO)那么需要使用“InRoot$$Sections”来在根区显示的指定这些节。简单来说,就是每个分散加载都要有一个根区,根区需要指定“InRoot$$Sections”节或者等效的其他节(简单来说就是分散加载、__main、数据压缩这些东西必须要放到根区)。
不用“InRoot$$Sections”来指定根区,那么如下的编写方式也是OK的:
要注意的是__main.o、__scatter.o、__scatter_copy.o 等不能被加载到第二块 Flash的运行时域 ER_IROM2,也就是说这几项目数据只能加载到 ER_IROM1 的运行时域。因为分散加载文件有一项很强大的功能,就是可以将Flash的代码拷贝到RAM中运行,这一段拷贝代码就存在于__main()函数中,但拷贝代码不能拷贝自身,所以必须规定有一个运行时域中存放的代码是不会被拷贝的,这个指的就是第一个运行时域。
三、分散加载的单独函数/变量的指定加载
编译器通过单个源文件生成RO、RW和ZI节。要将单个函数或者数据固定放在特定的地址上,我们必须允许链接器单独处理这个函数或数据并且与其他的部分分开。
<1>使用--split_sections这个编译器选项,为每个函数单独生成一个节(分散加载操作以节为单位),再单独分配这些节;
<2>使用__attribute__((at(address)))来指定放置;
<3>使用__attribute__((section("name")))来放置一个被命名的节;
第一种较为吃资源不讨论
使用__attribute__((at(address)))来指定放置 例如
int var1 __attribute__((at(0x10000000))) = 0x55;
代码直接向0x10000000地址分配一个变量,并赋值0x55;这种方式会有所限制
• __at 节地址范围不能重叠,除非将重叠节放在不同的重叠区中
• 不允许在与位置无关的运行域中使用 __at 节
• 不能在 System V 和 BPABI 可执行文件和 BPABI DLL 中使用 __at 节
• __at 节地址必须是其对齐边界的倍数
• __at 节忽略所有 +FIRST 或 +LAST 排序约束。
使用__attribute__((section("name")))来放置一个被命名的节
int var __attribute__((section("mySection"))) = 0x55;
构建了一个名叫“mySection”的节,修改对应的分散加载文件:
LR_IROM1 0x00000000 0x00008000 {
ER_IROM1 0x00000000 0x00008000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
ER_mySection 0x10000000
{
main.o(mySection)
}
RW_IRAM1 0x1000000 0x00002000 {
.ANY (+RW +ZI)
}
ARM_LIB_STACK 0x10002000 EMPTY -0x200{}
}
使用__attribute__((section("name")))同样可以放置函数如下
__attribute__((section("mySection"))) void GPIOInit (void)
{
xxxx
xxxx
}
四、Flash 特殊要求应用
普通方式生成的 Bin文件有两个,也可以用FIXED来修饰只生成一个 Bin文件的方式来加载两个时域,但这种方式也有一个缺点,就是其是以填充的方式产生的成一个 Bin 文件,当两个运行时域地址相距很大时,就会导致填充出来的 Bin 文件非常大,所以不适合于双 Flash 应用。
LR_IROM1 0x00000000 0x00040000 {
ER_IROM1 0x00000000 0x200 {
*.o (RESET, +First)
}
ER_IROM2 0x200 FIXED 0x3FD00 { ; FIXED 修饰,让其和第一个运行域关联起来,合成一个运行域
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x00008000 {
.ANY (+RW +ZI)
}
}