一、简介
连接器(Linker)产生的map文件包含大量的关于“链接,定位”过程的信息。该map文件由一些单元组成。各个单元的详解如下。
1、Page Header
页头。map文件一般是文本格式的,一个map文件中会分成好多的页。每一页的头部都包含链接器的版本号,链接日期,时间,以及页码。
2、Command Line
这部分是:完整的用于调用linker的命令。这一这里显示的是命令行调用linker时的输入参数,格式等。
3、CPU Details
关于所选择的CPU,CPU模式,存储模式的信息。如果用到了浮点运算的话,这里也显示支持浮点库的信息。
4、DPP Registers
DPP寄存器的值。NCONST和NDATA的位置。
5、Input Modules
输入模块,包含在链接过程中要使用的所有的模块名,库函数名,以及链接最终要产生的模块的名称。
6、Interrupt Procedures
中断程序,包含所有的中断服务程序。map文件中列出了中断处理函数名(function name),中断向量编号(interrupt vector number),中断向量名(interrupt vector name)。
7、Memory Map
存储器映射表 列出了程序中所有的代码段的开始和结束地址(starting and ending address),类型( type), 重定位类型(relocation type), 对齐方式(alignment), tgroup, 分组(group), 组合方式(combination type), 段类型(class), 单元名称(name of each section)等信息。
8、Group List
组列表列出了包含在每个组(Group)中的单元(Section)。
9、Public Symbols
公共符号列别列出了所有的公共符号的值(value),名称(name),表示形式(representation),任务组(tgroup),类(class),段名(section name)。
10、Symbol Table
符号表包含了链接器输入模块中包含的符号的信息,包括符号的值(value),类型(type),表示形式(representation),长度(length),任务组(tgroup),名称等。
11、Cross Reference
交叉引用 包含交叉引用的段名称和它的表示形式(representation (LABEL, VAR, CONST)),以及所属的任务组。还包括该段是在那个模块中定义的以及哪个模块访问了它。
12、Warnings and Errors
Problems encountered while linking a program generate errors and warnings that are output to the screen and to the map file.
二、文件分析流程
1、Section Cross References(部分交叉引用)
主要是各个源文件生成的模块之间相互引用的关系。
stm32f10x.o(STACK) refers (Special) to stkheap2.o(.text) for __use_two_region_memory
比如上面这句话。stm32f10x.o是stm32f10x.s生成的目标文件模块,(STACK)是文件内定义的一个段,链接器把它视为一个Section输入段。它引用了模块stkheap2.o输入段(.text)里面的一个全局符号__use_two_region_memory(可能是一个函数或变量)。
stm32f10x_vector.o(.text) refers to __main.o(!!!main) for __main
__main.o(!!!main) refers to kernel.o(.text) for __rt_entry
kernel.o(.text) refers to usertask.o(.text) for main
上面这几个对于程序比较重要。用户在启动代码中调用了__main.o模块中的__main函数,__main又调用了kernel.o中的__rt_entry函数,最后kernel.o又调用了用户定义的main主函数。
2、Removing Unused input section from the image
就是讲库中没有用到的函数从可执行映像中删除掉,减小程序体积。
Removing os_mbox.o(.text), (1094 bytes).
Removing os_mutex.o(.text), (1744 bytes).
Removing os_sem.o(.text), (1016 bytes).
3、Image Symbol Table
Local Symbols(局部符号)
Symbol Name Value Ov Type Size Object(Section)
../../angel/boardlib.s0x00000000 Number0boardinit1.o ABSOLUTE
../../angel/handlers.s0x00000000 Number0__scatter_copy.o ABSOLUTE
../../angel/kernel.s0x00000000 Number0 kernel.o ABSOLUTE
../../angel/rt.s0x00000000 Number 0rt_raise.o ABSOLUTE
../../angel/scatter.s0x00000000 Number0 __scatter.o ABSOLUTE
../../angel/startup.s0x00000000 Number0 __main.o ABSOLUTE
../../angel/sys.s0x00000000 Number0 sys_exit.o ABSOLUTE
../../angel/sysapp.c0x00000000 Number0 sys_wrch.o ABSOLUTE
../../armsys.c0x00000000 Number0 _get_argv.o ABSOLUTE
../../division_7m.s0x00000000 Number0rtudiv10.o ABSOLUTE
../../fpinit.s0x00000000 Number0fpinit.o ABSOLUTE
../../heapalloc.c0x00000000 Number0hrguard.o ABSOLUTE
../../printf.c0x00000000 Number0 _printf_outstr_char.o ABSOLUTE
../../signal.c0x00000000 Number0 defsig_exit.o ABSOLUTE
../../stdlib.c0x00000000 Numbe0 exit.o ABSOLUTE
../../stkheap.s0x00000000 Number 0heapext.o ABSOLUTE
以上是一些系统内部的局部符号,还有用户的一些局部符号
Global Symbols(全局符号)
Symbol NameValueOv TypeSizeObject(Section)
_terminate_user_alloc- UndefinedWeakReference
_terminateio- UndefinedWeakReference
__Vectors0x08000000Data4stm32f10x_vector.o(RESET)
__main0x08000131 Thumb Code8__main.o(!!!main)
__scatterload0x08000139Thumb Code0__scatter.o(!!!scatter)
__scatterload_rt20x08000139Thumb Code44__scatter.o(!!!scatter)
这些是一些系统的全局符号
Font8x160x08001a82Data2048tft018.o(.constdata)
Font8x80x08002282Data2056tft018.o(.constdata)
codeGB_160x08002a8aData770tft018.o(.constdata)
Region$$Table$$Base0x08002dc0Number0 anon$$obj.o(Region$$Table)
Region$$Table$$Limit0x08002de0Number0anon$$obj.o(Region$$Table)
后面这两个符号我认为很重要。在运行库代码将可执行映像从加载视图转变为可执行视图的过程中起到了关键作用。Number是指它并不占据程序空间,而只是一个具有一定数值的符号,类似于程序中用define和EQU定义的。所以这里,先通过仿真调试,看这两个数值在程序中怎么用。
果然,在刚开始执行程序时,R10和R11的值就已经被赋值成了这两个值。
很快就将0x08002dc0到0x08002dcf处的16个字节,4个双字加载到了R0-R3,我们可以分析一下里面的内容,R0就是程序加载视图的RW区的起始地址(0x08002de0),R1就是要输出的执行视图的RW区的地址(0x20000000),R2就是要复制的RW数据的个数,R3是复制数 ( __scatterload_copy)的地址,类似于一个回调函数。接下来就要用0x0800011E 4718 BX r3这条指令去执行复制工作。
接下来又将0x08002dd0到0x08002ddf处的16个字节,4个双字加载到了R0-R3,我们可以分析一下里面的内容,R0就是程序加载视图的RW区的起始地址(0x08002de0+0x20=0x08002e00),R1就是要输出的执行视图的RW区的地址(0x20000020),R2就是要复制的RW数据的个数,R3是ZI区域建立函数( __scatterload_zeroinit )的地址。执行完成后,程序就会进入BL.W __rt_entry处进行库的初始化工作。
经过分析,现在对于程序的加载映像和执行映像有了较深的理解:程序的RO_Code加上RO_Data总共是0x2DC0这么大,地址范围0x0800,0000到0x0800,2DBF。然后在0x0800,2DC0-2DCF共16个字节放了RW加载映像地址(0x0800,2DE0)、执行映像地址(0x2000,0000)、RW长度(0x20)和将该段数据从加载映像复制到执行映像的函数地址。在0x0800,2DD0-2DDF共16个字节放了ZI加载映像地址(0x0800,2E00)、执行映像地址(0x2000,0020)、ZI长度(0x480)和建立ZI、HEAP和STACK执行映像的函数地址。
在上面的第二个阶段,将ZI清零阶段,程序的ZI长度实际上只有0x20,而库代码留出了0x60的长度。因此数据区的顶端为0x2000,00A0-1。接下来从0x2000,00A0开始为堆的起始地址,堆长度加上程序栈长度为0x2000,04A0,这就是堆栈顶端,也是__initial_SP的初始值。
程序进入_rt_entry后,还要对heapstack进行处理,但我没有看到什么有用的变化。从中对库留出的ZI数据区进行了一些处理,我暂时也看不明白。好了,调试就到这里,回到map文件分析的正途。
4、Memory Map of the image(映像的内存分布)
Image Entry point : 0x080000ed //程序的入口点:这里应该是RESET_Handler的地址
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00002e00, Max: 0x00020000, ABSOLUTE)
//程序的加载映像地址和长度,2e00=2dc0(代码和常数)+0x20(Region Table是RW的加载和执行地址、ZI与HEAPSTACK的执行地址)+0x20(已经初始化的数据)
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00002de0, Max: 0x00020000, ABSOLUTE) //这段RO区域的加载映像和执行映像一致
Base AddrSizeTypeAttr IdxE Section NameObject
0x080000000x000000ECDataRO 3RESETstm32f10x.o
0x080000EC0x00000008CodeRO 191* !!!main__main.o(c_w.l)
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x000004a0, Max: 0x00005000, ABSOLUTE) //RW数据区 ZI数据区 Heap和Stack数据区
Base Addr Size Type AttrIdxE Section NameObject
0x20000000 0x00000001 Data RW100.datatft018.o
0x20000040 0x00000060 Zero RW212 .bsslibspace.o(c_w.l)
0x200000a0 0x00000000 Zero RW2 HEAPstm32f10x.o
0x200000a0 0x00000400 Zero RW1 STACKstm32f10x.o
5、Image component sizes
这是指出各个模块的输入段的大小
Code (inc. data)RO Data RW Data ZI Data DebugObject Name
97258010322416 can.o
8241680 1501791candemo.o
928880004529stm32_init.o
5218236010242700stm32f10x.o
18363248741 08076tft018.o
最后给出总长度:这个11744应该=0x2dc0,1184应该0x4a0。11776应该是=0x2e00。
TotalROSize (Code + RO Data)11744 (11.47kB)
TotalRWSize (RW Data + ZI Data) 1184 (1.16kB)
TotalROMSize (Code + RO Data + RW Data)11776 (11.50kB)
三、分散加载描述文件(Scatter Loading Description Files)
1 启动文件
通常我们将处理器复位开始执行第一条指令到进入C语言的main( )函数之前执行的那段汇编代码称为启动代码。
Ø 异常向量表定义:
每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间,只能放置一条ARM指令,使程序跳转到存储器的其他地方,再执行中断处理。
ARM芯片上电或复位后,系统进入管理模式、ARM状态、PC(R15寄存器)指向0x00000000地址处。中断向量表为每一个中断设置1个字的存储空间,存放一条跳转指令,通过这条指令使PC指针指向相应的中断服务程序入口,继而执行相应的中断处理程序。
Ø 地址重映射及中断向量表的转移:
ARM7处理器在复位后从地址0读取第一条指令并执行,因此系统上电后地址0必须是非易失的ROM/FLASH,这样才能保证处理器有正确可用的指令。为了加快对中断的处理以及实现在不同操作系统模式下对中断的处理,这就需要重新映射中断向量表、Bootblock和SRAM空间的一小部分。ARM具有非常灵活的存储器地址分配特性。ARM处理器的地址重映射机制有两种情况:
① 专门的寄存器完成重映射(Remap),只需对相应的Remap寄存器相应位设置即可。
② 没有专门的Remap控制寄存器需要重新改写用于控制存储器起始地址的块(Bank)寄存器来实现Remap。
Ø 堆栈初始化:
启动代码中各模式堆栈空间的设置是为中断处理和程序跳转时服务的。当系统响应中断或程序跳转时,需要将当前处理器的状态和部分重要参数保存在一段存储空间中,所以对每个模式都要进行堆栈初始化工作,给每个模式的SP定义一个堆栈基地址和堆栈的容量。堆栈的初始化有两种方法:第一种方法是结合ADS开发套件中的分散加载文件来定义堆栈。第二种方法是最简单也是最常用的一种就是直接进入对应的处理器模式,为SP寄存器指定相应的值。下面给出了用第二种方法初始化管理模式和中断模式堆栈的程序:
MSR CPSR_c, #0xD3 /切换到管理模式,并初始化管理模式的堆栈/
LDR SP, Stack_Svc
MSR CPSR_c, #0xD2 /切换到IRQ模式,并初始化IRQ模式的堆栈/
LDR SP,Stack_Irq
…
Ø 设置系统时钟频率以及切换工作模式:
时钟是芯片各部分正常工作的基础,应该在进入main()函数前设置。
Ø 存储器系统配置:
主要是对系统存储器控制器(MMU)的初始化。由于存储器控制器并不是ARM架构的一部分,不同芯片商的实现不同。由于运算能力和寻址能力的强大,基于ARM内核烦人微处理器系统初始化都需要外扩展各种类型的出出气。初始化一般包括:
一般包括存储器类型、时序可总线宽度的配置、存储器地址的配置。
Ø 初始化应用程序的执行环境:
一个ARM映像文件由RO、RW和ZI三个段组成。RO为代码段,RW是已初始化的全局变量,ZI是未初始化的全局变量。映像文件一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到速度跟快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去的。所谓应用程序执行环境的初始化,就是完成必要的从ROM到RAM的数据传输和内容清0。
l 将RW从ROM搬到RAM;
l 将ZI所在的RAM区域全部清0,因为ZI并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来讲相应的RAM区清0。
Ø 进入main( ):
至此,系统各部分的初始化基本完成,可以直接从启动代码转入到应用程序的main()函数入口。从启动代码转入到应用程序的实例代码如下:
IMPORT main
LDR R0,=main
BX R0
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()函数,有点像WIN32的main函数。
2ARM映像文件
Section:.text .data .bss .symtab
Ø ELF格式文件
ARM各种源文件(汇编、C/C++程序)经过ARM编译器编译后生成ELF格式的目标文件。这些目标文件和相应的C/C++运行时经过ARM链接器处理后,生成ELF格式的映像文件(image)。这种ELF格式的映像文件可以被写入嵌入式设备的ROM中。
ELF文件类型:
l 可执行文件;
l 可重定位文件(目标文件);
l 共享object(共享库)。
Ø ELF文件物理结构
在ARMIDE中:
l 只读的代码段称为Code段,即通常的.text段。
l 只读的常量数据段,被称作RO Data段,即通常的.const data段。
以上两个统称为RO段(Read Only),存放在ROM或Flash等非易失性器件中。
l 可读可写的初始化的全局变量和静态变量段,被称作RW段(Read Write),即通常的.data段。
l 可读可写的未初始化的全局变量和静态变量段,被称作ZI段,即通常的.bss段。这个段的变量要初始化为0,故称作是ZI段。在运行时,RW和ZI段必须重新装载到可读可写的RAM中。
RO |
Code |
.text段 |
只读的代码段 |
RO-Data |
.const data段 |
只读的常量数据段 | |
RW |
RW-Data |
.data段 |
可读可写的初始化的全局变量和静态变量段 |
ZI |
ZI-Data |
.bss段 |
可读可写的未初始化的全局变量和静态变量段 |
Ø ARM映像文件是一个层次性结构的文件,其中包含域(region)、输出段(output section)和输入段(input section)。各部分关系如下:
l 一个映像文件至少由一个域组成;
l 每个人域包含一个或多个输出段;
l 每个输出段包含一个或多个输入段(RO、RW、ZI);
l 各输入段包含了目标文件中的代码和数据。
输出段的属性与其包含的输入段的属性相同。各输出段的排列顺序由其属性决定。RO属性输出段排在最前,其次是RW属性输出段,最后是ZI属性输出段。一个域通常映射到一个物理存储器上,如ROM和RAM等。
3ARM映像文件的地址映射
ARM映像文件各部分在存储系统中的地址有两种:
l 一种是映像文件开始运行之前的地址(运行前输出段在ROM/RAM中的分布状态),称为加载地址或加载域、装载域;
l 一种是映像文件运行时的地址(运行时输出段在ROM/RAM中的分布状态),称为运行地址或运行域;
之所以存在2种地址,大多数情况下。映像文件在执行前把它装在到ROM中,而当前运行时域里的有些输出段必须复制到RAM中,程序才能正常运行。所以,在装载和运行时,有些段处在不同的位置(地址空间)。
可以看出,映像文件中ZI段在装载域中是不存在的,在运行域里才建立的;映像文件运行时,第一件工作就是把RW输出段复制到RAM里的正确位置,第二件工作是建立ZI输出段并初始化为0。这就是应用程序执行环境的初始化。这样,在运行时,代码和数据分布到了不同地址空间,形成三个运行域:RO运行域、RW运行域和ZI运行域。
4ARM映像文件的入口点
ARM映像文件的入口点位于ELF文件头部(描述文件的总体格式),ARM映像文件的入口点有2种类型:初始入口点(initialentry point)和普通入口点(entry point)。
每个映像文件只有一个唯一的初始入口点,就是当程序运行时,要执行的第一条指令的地址。如果映像文件是被操作系统加载的,操作系统正式通过跳转到该初始入口点处执行来加载该映像文件。
Ø 初始入口点条件
通常ARM映像文件的初始入口点是在链接是生成的,所以在连接之前必须定义初始入口点。初始入口点必须满足2个条件:
l 初始入口点必须位于映像文件的运行域内;
l 包含初始入口点的运行域不能被覆盖,即在加载地址和运行地址相同的区域内。
注意:所谓链接就是资源定位,目标文件和可执行文件的区别就在于是否有定址,即初始入口点。
Ø 如何指定初始入口点
在链接之前可以使用链接选项-entry address来指定映像文件的初始入口点。地址0x0处为ROM的嵌入式应用系统,可以使用-entry 0x0指定初始入口点
当用户没有指定链接选项-entry address时,链接器根据下面的原则确定映像文件的初始入口点:
l 如果输入目标文件中只有一个普通入口点,则该普通入口点被链接器当成映像文件的初始入口点。
l 如果输入目标文件中普通入口点多于一个,或没有。则链接器生成的映像文件中不含初始入口点,并产生以下警告信息:L6305W:Image dose not have an entry point.(Not specified or not set duo to multiple choices)。
Ø 普通入口点
普通入口点在汇编程序中用ENTRY伪操作定义。它通常用于标识该段代码是通过异常中断处理程序进入的。一个映像文件中可以定义多个普通入口点。
5 分散加载文件(scatter file)
xxx.scf,链接时使用。它可以把用户的应用程序分割成多个RO运行域和RW运行域,并给他们指定不同的地址。在嵌入式系统中,Flash、16位RAM、32位RAM都可能存在于系统,所以不同功能的代码定位于特定的位置上会大大提高系统的运行效率。
LOAD_ROM(加载域名称) 0x0000(加载域起始地址) 0x8000(加载域最大字节数)
{
EXEC_ROM(第一运行域名称) 0x0000(第一运行域起始地址) 0x8000(第一运行域最大字节数)
{
*(+RO (代码与只读数据) )
}
RAM(第二运行域名称) 0x10000(第二运行域起始地址) 0x6000(第二运行域最大字节数)
{
*(+RW(读写变量), +ZI(未初始化变量与初始化为0的变量))
}
}
Ø BNF符号与语法
Ø 加载域(Load Region)描述
名 称:为Linker确定不同加载区域
基 地 址:相对或绝对地址
属 性:可选
最大字节数:可选
运行域列:确定执行时各运行域的类型与位置
load_region_description ::=
load_region_name (base_address |("+" offset)) [attribute_list] [max_size]
"{"
execution_region_description+
"}"
load_region_name |
加载域名称 |
最大有效字符数31。(并不像运行域段名用于 Load$$region_name,而是仅仅用于表示加载区域)。 |
base_address |
加载域基地址 |
按字对齐 |
offset |
偏移量 |
相对前一个加载区域的偏移量(4的整数倍) |
attribute_list |
属性列表 |
PI, RELOC, OVERLAY, ABSOLUTE |
max_size |
最大空间(字节单位) |
若加载区域的实际空间大于该指定值,则ARMLink报错 |
Ø 执行域(Execution Region)描述
名 称:为Linker确定不同运行域
基 地 址:相对或绝对地址
属 性:确定运行域的属性
最大字节数:可选
输 入 段:确定放在该运行域的模块
execution_region_description ::=
exec_region_name (base_address | "+" offset) [attribute_list] [max_size]
"{"
input_section_description+
"}"
exec_region_name |
执行域名称 |
最大有效字符数31 |
base_address |
执行域基地址 |
按字对齐 |
offset |
偏移量 |
相对于前一个运行域结束地址的偏移量,4的整数倍;如果之前没有运行域(本运行域为该加载区域的第一个运行域),则该偏移量是相对于该加载域的基址偏移量。 |
attribute_list |
属性列表 |
PI, OVERLAY, ABSOLUTE, FIXED, UNINIT |
max_size |
最大空间(字节单位) |
若加载区域的实际空间大于该指定值,则ARMLink报错 |
input_section_description |
输入段内容 |
属性 |
说明 |
PI |
位置无关 |
OVERLAY |
覆盖 |
ABSOLUTE |
绝对地址(默认值) |
FIXED |
固定地址。表示装载地址与执行地址都是该固定地址。 |
UNINIT |
未初始化数据。 |
RELOC |
可重定位。无法明确指定执行域的属性,而只能通过继承前一个运行或父区域获得。 |
注意:
对于PI,OVERLAY,ABSOLUTE,FIXED,我们只能选择一个,缺省属性为ABSOLUTE。
一个运行域要么直接继承其前面的执行域的属性或者具有属性为ABSOLUTE。 具有PI,OVERLAY,RELOC属性的执行域允许其地址空间重叠,对于ABSOLUTE,FIXED 属性运行域地址空间重叠ARMlink会报错。
Ø 输入段(Input section)描述
模块选择形式:匹配方式可为:目标文件名,库成员名,库文件名,通配符。
输入段属性 :RO(TEXT),RW(DATA),ZI(BSS),属性前必需加“+”。
输入段形式 :匹配方式可为:通配符”*”,”?”,输入段名称。
input_section_description ::=
module_select_pattern ["("
("+" input_section_attr | input_section_pattern)
([","] "+" input_section_attr | "," input_section_pattern))*
")"]
module_select_pattern |
模块选择形式。匹配方式可为:目标文件名,库成员名,库文件名,通配符”*”,”?” (”*”匹配任意多个字符,”?”匹配任意一个字符,名称不区分字母大小写)。 |
input_section_attr |
输入段属性 (RO(TEXT),RW(DATA),ZI(BSS),FIRST,LAST)属性前必需加”+”。 |
input_section_pattern |
输入段选择形式。匹配方式可为:通配符”*”,”?”,输入段名。 |
例1:*libtx.a (+RO)
libtx.a为threadX库文件。
例2:tx_ill.o (INIT)
tx_ill.o为threadX中断向量目标文件。
例3:os_main_init.o (INIT ,+FIRST)
FIRST表示放于本运行域的开始处。
例4:*libtx.a (+RO)
RO 表示*libtx.a的只读部分。
例5:os_main_init.o (INIT ,+FIRST)
INIT 为os_main_init.o的一个段
例6:os_stackheap.o (heap)
heap 为os_stackheap.o的一个段。
例7:os_stackheap.o (stack)
stack为os_stackheap.o的一个段。
附录:
1、ARM编译中的RO、RW和ZI DATA段
ARM程序(指在ARM系统中正在执行的程序,而非保存在ROM中的bin文件)的组成。一个ARM程序包含3部分:RO段,RW段和ZI段
Code |
代码占用空间 |
Flash中被占用空间是: Code+RO-Data+RW-Data |
RO-data |
Read-Only 只读常量 | |
RW-data |
Read-Write 可读写变量 | |
ZI-data |
Zero-Initialize 没初始化的可写可写变量 |
RAM使用空间是:RW-Data+ZI-Data |
2、ARM映像文件
所谓ARM映像文件就是指烧录到ROM中的bin文件,也成为Image文件。以下用Image文件来称呼它。
Image文件包含了RO和RW数据。之所以Image文件不包含ZI数据,是因为ZI数据都是0,没必要包含。只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
3、ARM的执行过程
从以上两点可以知道,烧录到ROM中的Image文件与实际运行时的ARM程序之间并不是完全一样的。因此就有必要了解ARM程序是如何从ROM中的Image到达实际运行状态的。
实际上,RO中的指令至少应该有这样的功能:
Ø 将RW从ROM中搬到RAM中。因为RW是变量,变量不能存在ROM中。
Ø 将ZI所在的RAM区域全部清零 。因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理变量不能存在ROM中。
在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。
注意:如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在ZI区域。即:ARM C程序中,所有的未初始化变量都会被自动初始化为0。
4、总结
① C中的指令以及常量被编译后是RO类型数据。
② C中的未被初始化或初始化为0的变量编译后是ZI类型数据。
③ C中的已被初始化成非0值的变量编译后市RW类型数据。
ROM主要指:NAND Flash,Nor Flash
RAM主要指:PSRAM,SDRAM,SRAM,DDRAM
简单的说就是在烧写完的时候是:FLASH中:Code+RO Data+RW Data,运行的时候:RAM: RW Data + ZI Data,当然还要有堆栈的空间。