分散加载文件概念
对于分散加载文件的概念,在《ARM体系结构与编程》书第11章有明确介绍。
分散加载文件(即 scatter file,后缀为 .scf)是一个文本文件,通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO、RW、ZI等数据的存放地址。
如果不用 SCATTER文件指定,那么ARM连接器会按照默认的方式来生成映像文件,一般情况下我们是不需要使用分散加载文件的。但在某些场合,我们希望把某些数据放在知道那个的地址处,那么这时候SCATTER文件就发挥了非常大的作用,而且SCATTER文件用起来非常简单好用。我越看这个分散加载文件越感觉它的作用和uboot的连接脚本lds一样。
分散加载文件的格式
分散加载描述文件是一个文本文件,它向链接器描述目标系统的存储器映射。如果通过命令行使用链接器,则描述文件的扩展名并不重要。分散加载文件指定:
① 每个加载区的加载地址和最大尺寸;
② 每个加载区的属性;
③ 从每个加载区派生的执行区;
④ 每个执行区的执行地址和最大尺寸;
⑤ 每个执行区的输入节。
从描述文件的格式就可以看出加载区、执行区和输入节的层次关系。
分散加载文件基本点
① 编译后输出的映像文件中各段是首尾相连的,中间没有空闲的区域,他们的先后关系是根据链接时参数的先后次序决定的armlinker -file1.o file2.o ...
② scatter用于将编译后的映像文件中的特定段加载到多个分散的指定内存区域
③ 有两类域(region):执行域(execution region,一般是ram区域)和加载域(load region,一般是rom区域)
④ 加载域:就是编译之后得到的二进制文件烧写到rom中的这一段区域,所有的代码R0、预定义变量RW、堆栈之类和清不清空无关紧要的大片内存区域ZI,都包括在其中。
⑤ 执行域:就是把加载域进行“解压缩”后的样子。比如:RO没有变动还是在ROM中,RW被移到了SRAM中,而ZI被放置在SDRAM中
⑥ scatter本身并不能对映像实现“解压缩”,编译器读入scatter文件之后会根据其中的各种地址生成启动代码了,实现对映像的加载,而这一段代码就是*(InRoot$$Sections)它是__main()的一部分。这就是在汇编启动代码的最后跳转到__main()而不是跳向main()的原因之一。
⑦ 起始地址与加载域重合的执行域称为root region,*(InRoot$$Sections)必须放在这个执行域中,否则链接的时候会报错。*(+RO)包含了*(InRoot$$Sections),所以如果在root region 中用到了*(+RO)可以不再指定*(InRoot$$Sections)。
scatter文件分析:
⑧ 程序正确编译生成目标文件以后,就会链接成可执行的文件,这个过程中,要用到分散加载文件,它决定可执行代码在存储器中存放的位置,这在复杂的程序(例如VIVI读Linux内核的引导)中时很重要的。
文件1:
LR_ROM1 0x00000000 0x00080000 ; 第一个加载域,名字为LR_ROM1,起始地址为0x0,
{ ; 大小为0x80000
ER_ROM1 0x00000000 0x80000000 ; 加载域中的运行时域,名字为ER_ROM1,起始地址为
{ ; 0x0,大小为0x80000,如上述第⑦条所示
vectors.o (VECT,+First) ;将vectors.c编译后生成的文件vector.o中的代码、init.c编译
init.o (INIT) ;后生成的init.o中的代码以及所有编译生成的RO属性的代码全部存放在
*(+RO) ; 运行时域ER_ROM1指定的地址范围内,存放方式:顺序存放。
} ;真的和uboot的lds链接脚本文件一样呢
RW_RAM1 0x40000000 0x0000e800 ;这是第二个运行时域,功能同上。起始地址0x40000000
{ ;大小为0xe800。符号 * 是代表具有()里面指定睡醒的全部数据
*(+RW,+ZI) ; 与 * 功能相似的有 .ANY。.ANY的作用就是可以把已经被指定的具有
} ; RW、ZI属性的数据排除。
ARM_LIB_HEAP 0x40007000 EMPTY 0x00000100 {} ; 指定堆地址
ARM_LIB_STACK 0x40008000 EMPTY -0x00000e00 {} ; 指定栈地址;为什么有 ‘-’号呢?不懂
}
文件2:
LR_ROM1 0x08000000 0x00004000 ;load region size_region第一个加载域,起始地址0x08000000,
{ ; 大小为0x4000,这是网上的例子,地址怎么能是0x80000000呢
ER_ROM1 0x08000000 0x00004000 ;load address=execution address第一个运行域,起始地址是
{ ; 0x08000000,大小为0x4000
*.o (RESET,+First) ;IAP第一阶段还是在Flash中运行,我很想知道RESET这个标号是根据什么写
*(InRoot$$Sections) ; 注意这里没有用*(+RO)
startup_stm32f10x_md.o
}
ER_ROM2 0x20008000 0x00004000 ;load address=execution address第二个运行时域
{
.ANY (+RO) ;IAP第二阶段加载到SDRAM运行。奇了怪了,为什么又加载到SDRAM中了?
}
RW_RAM1 0x20000000 0x00008000 ;RW data把可读写的数据和初始化为0的数据放在内存
{ ;SDRAM的开头。咱们ARM9的SDRAM地址不是0x30000000
.ANY(+RW,+ZI) ;吗?现在我严重怀疑这里是STM32的SDRAM的地址因为这个
} ;文件2是针对STM32的。
}
让MDK自己分配---选择Linker-usexxx
文件3
我多贴几个文件,因为这样可以作对比,让自己更清楚,反正都是我在网上搜的,我看一个文件就懂一点东西。
ROM_LOAD 0x00000000 ;ROM_LOAD是加载域的名称,你也可以自己取。这里只有一个加载域,
{ ;也可以有多个(rom地址不连续的情况)
ROM 0x00000000 0x003fffff ;ROM、SRAM、SDRAM1、SDRAM2是执行域,有多个。第一个执行域
{ ;必须和加载域地址重合,因为ARM的复位地址就是加载域的起始地址!
vectors.o (+RO,+FIRST) ; vector.o 中断向量表放在最开头
* (InRoot$$Sections) ; *(InRoot$$Sections)是复制代码的代码
* (+RO) ;具有RO属性的所有的代码
}
SRAM 0x00400000 0x003fffff
{
* (+RW,+ZI)
}
SDRAM1 0x41000000 UNINIT ;UNINIT关键字表示复制代码的的代码
{
stack.o (+ZI)
}
SDRAM2 +0 UNINIT ;起始地址= +0表示紧接着上一段开始的连续地址
{
heap.o (+ZI)
}
}
注释:
1、ROM_LOAD是加载域,这个名字可以随便起,你也可以叫ROM_LOAD1。加载域只有一个,也可以有多个(ROM地址不连续的情况)
2、ROM、SRAM、SDRAM1、SDRAM2是执行域,有多个。第一个执行域必须和加载域地址重合,因为ARM的复位地址就是加载域的起始地址(有bootstrap的话加载地址就是bootstrap执行完后的跳转地址)
3、vectors.o (+RO、+FIRST)中断向量表放在最开头。我用mini2440开发裸机时,也没有给中断向量表单独写文件啊,全都是放在S3C2440.s启动代码里了,我在想是不是该把s3c2440.o放在最开头!
4、ROM 0x00000000 0x003fffff:加载域名 起始地址 最大允许长度;其中‘最大允许长度’也可以省略,但缺点就是编译器不会检查段是否溢出和别的段重叠了。起始地址用“+0”代替表示紧接着上一段开始的连续地址。
5、*(InRoot$$Sections)是复制代码的代码
6、 UNINIT关键字表示不进行初始化清零。
7、在一个scatter文件中,同一个.o文件不可能出现2次,即使是在2个不同的加载域中也不可以,否则会报错:Ambiguous selections found for *.o
到现在已经贴了3个文件了,相信大家大致了解这个scatter分散加载文件了,但是对我来说,仅限于看懂、知道,还是不会自己写,哎,理论与现实的差距啊!现在我尝试着写一下自己的scater文件。我写这个文件是按照韦东山裸机时的写lds文件的思路来的,注意啊,这个文件是我自己写的,还没有正确验证呢,等我后来正确验证了,再回过头来修改补充!
文件4
自己尝试着写。
LR_ROM1 0x00000000
{
ER_ROM1 0x00000000 0x00200000
{
S3C2440.o (+RO,+FIRST)
nand.o (+RO)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_RAM1 0x40000000 0x00020000 ;加载范围自己定
{
.ANY (+RW +ZI) ;它们之间的逗号我还不确定该不该加;我现在确定了,不加!
}
}
下边的三段话是我从网上摘抄的。
前边三个分散加载文件是从别的地方拷过来的,用在自己的程序中可能会有问题,因为如果不修改它的话就固定了加载地址和运行地址,如果程序简单又比较小的话可能不会有问题,但是如果不修改它的话就固定了加载地址和运行地址,如果程序简单又比较小的话可能不会有问题,但是如果程序代码比较大,超出了那三个加载文件的定义大小可能就会出问题,解决办法很简单,直接修改.sct文件直到适合你的代码。
更好的办法是自己定义一个分散加载文件,在MDK中勾选Use Memory Layout from Target Dialog,那么加载文件就是从你定义的ROM和RAM等地址得到的,如果不勾选的话就是通过你自己指定的加载文件来加载。
如果分散加载文件不对的话,可能出现的问题就是明明是在SRAM中调试程序(我想我找到困惑自己很久的问题了,就是是用Jlink调试程序的时候,程序下载到SRAM,而不是SDRAM!!),但是却能神奇的通过flash downloader下载到Flash中去,刚开始也是不解,后来才发现是加载文件有错误,我使用了一个指定的flash.sct文件,这样的话我设置的irom和iram都无效了,编译器直接根据我指定的flash.sct来分布代码和加载代码,又查看了一下flash.sct文件是加代码加载到flash笛子空间的,这就是为什么在工程中也能通过Flash Downloader工具烧写代码带Flash中去的原因。
分析到这里,我对分散加载文件有一定了解了,但是还是脑子很乱。可是我脑子里知道,要想真正的搞明白这个东西,还要了解咱们在配置MDK时候用到的Target选项和LInker选项,并且很高兴的是,我大概的有些谱了,咱们在弄工程的时候,一般要先配置,但是为什么要配置这些选项,这些选项都有什么作用,这就很重要了,下一节学习MDK中的与分散加载地址有关的选项,将这节联系起来,慢慢弄吧,这两天因为要不要出去培训弄的心里老是安静不下来呢