目录
1 分散加载文件.sct
2 如何生成.sct文件
3 *(InRoot$$Sections) 说明
4 如何修改分散加载文件
5 已经初始化变量的初值,存储位置
6 +RW +ZI和+RO如何执行
1 分散加载文件.sct
MDK的分散加载主要是通过.sct文件实现的,链接器根据.sct文件的配置分配各个节区地址,生成分散加载代码,因此通过修改该文件可以定制具体节区的存储位置。
那为什么需要分散加载文件呢?不用可以吗?
一般情况下,可以不独自编写分散加载文件,ARM链接器直接按照默认的方式来生成映像文件即可,但是在某些场合,希望将某些数据放在指定的位置,此时分散加载文件就发挥了非常发的作用。比如在下面几种情况:
- 复杂内存映射:如果必须将代码和数据放在多个不同的内存区域中,则需要使用详细指令指定将哪些数据放在哪个内存空间中。
- 不同类型的内存:许多系统都包含多种不同的物理内存设备,如闪存、 ROM、 SDRAM 和快速 SRAM。分散加载描述可以将代码和数据与最适合的内存类型相匹配。例如,可以将中断代码放在快速 SRAM 中以缩短中断等待时间,而将不经常使用的配置信息放在较慢的闪存中。
- 位于固定位置的函数:可以将函数放在内存中的固定位置,即使已修改并重新编译周围的应用程序。
- 使用符号标识堆和堆栈:链接应用程序时,可以为堆和堆栈位置定义一些符号。
2 如何生成.sct文件
在Options->Targets->Linker界面下面去掉默认选项,然后edit... 就可以在编辑界面中看到.sct文件了。
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00020000 {
//定义一个加载域,域地址0x08000000,域大小为0x00020000
//load region size_region 所有代码需要下载到0x08000000 开始的区域中,且这个区域大小只有0x00010000
ER_IROM1 0x08000000 0x00020000 {
//load address = execution address 第一个运行时域必须和加载域起始地址相同,其大小一般也相同
//只能是只读的代码段和只读数据段
*.o (RESET, +First) //启动代码的首次执行地址,RO执行域名称为ER_IROM1,
//将 RESET 段最先加载到本域的起始地址外
//首次执行的地址为RESET标号所表示的地址,RESET 存储的是向量表
//对应启动文件中的AREA RESET, CODE, READONLY
*(InRoot$$Sections)
//稍后文件中会单独讲到
.ANY (+RO)
//加载所有匹配目标文件的只读属性数据,包含:Code、 RW-Code、 RO-Data。
}
RW_IRAM1 0x20000000 0x00005000 {
//再定义一个运行时域,域基址0x20000000
//RW data 执行域是以0x20000000 开始的长度为0x00004000 一段区域
.ANY (+RW +ZI) //其中包括的是哪些文件
}
}
3 *(InRoot$$Sections) 说明
.sct本身并不能对映像实现“解压缩”,编译器读入.sct文件之后,会根据其中的各种地址生成启动代码,实现对映像的加载,而这一段代码就是*(InRoot$$Sections)它是__main()的一部分。这就是在汇编启动代码的最后跳转到__main()而不是跳向main()的原因之一。
起始地址与加载域重合的执行域称为root region,*(InRootSections)必须放在这个执行域中,否则链接的时候会报错。
4 如何修改分散加载文件
如果需要修改分散加载文件,并使其生效可以通过以下步骤操作进行:
- 修改Options->Targets->Target 的onchip 的rom或者ram,容量地址必须和选择的芯片一致
- 修改某个文件的存储属性,在工程窗口中右击文件名字 Options for file ,然后出现窗口可以修改文件的存储属性
- 修改完,重新编译,点开.sct文件就可以看到.sct已经被修改了,然后可以看map文件可以看到相关变量函数地址信息已经改变。
5 已经初始化变量的初值,存储位置
int tick = 20; 会放在哪里呢?
编译完成
已经初始化的变量,是被放入RW属性的输入节中,而这些变量的初值,是被放入ROM/Flash中的。
那这些初值是谁在何时将它们恢复到RAM中的?
ZI属性输入节中的变量所在RAM又是谁在何时给用零初始化的呢?
接下来继续下一个章节来继续梳理《7 +RW +ZI和+RO如何执行》
6 +RW +ZI和+RO如何执行
- 硬件复位后,第一步是执行复位处理程序,程序的入口在启动代码里(启动代码里面讲过);
- 初始化堆栈指针、执行完用户定义的底层初始化代码(SystemInit函数)后,接下来的代码调用了__main函数;
- __main函数会调用一些列的C库函数,完成代码和数据的复制、解压缩以及ZI数据的零初始化,数据的解压缩和复制,其中就包括将储存在ROM/Flash中的已初始化变量的初值复制到相应的RAM中去。(解释了上一章节的问题:那这些初值是谁在何时将它们恢复到RAM中的?)
- 对于一个变量,它可能有三种属性,用const修饰符修饰的变量最可能放在RO属性区,已经初始化的变量会放在RW属性区,那么剩下的变量就要放到ZI属性区。默认情况下,ZI数据的零初始化会将所有ZI数据区初始化为零,这是每次复位后程序执行C代码的main函数之前,由编译器自主完成。
- 要在C代码中设置一些变量在复位后不被零初始化,那一定不能任由编译器“自主完成”,要用一些规则,约束一下编译器。这时候我们的.sct的作用就显示出来了。在分散加载文件中,使用UNINIT来修饰一个运行时域。可以避免__main对该区节的ZI数据进行零初始化。(解释了上一章节的问题:ZI属性输入节中的变量所在RAM又是谁在何时给用零初始化的呢?);