有些场合中(例如工控产品),当系统复位后(非上电复位),可能要求保持复位前RAM中的部分数据,用来快速恢复现场等等。而通常情况下,系统任何形式的复位,都会导致RAM中的未赋初值变量因为被系统重新初始化而清零,因此有必要知道如何使未赋初值变量在系统复位时不被清零。(int i; //i就是一个未赋初值变量)
这里我们需要了解ARM映像文件和分散加载文件:
工程中的源文件经过编译器编译生成目标文件(*.o),这些目标文件和相应的C/C++代码运行时用到的库,经过链接器处理后生成可执行文件,所谓ARM映像文件就是指烧录到ROM/FLASH中的可执行文件,也称为Image文件(*.bin或者*.hex)。
整理归纳一些相关概念,这几点为本人的个人理解,仅供参考:
- region:域,指的是映像文件的内容所在的内存区域,分为加载域(load region)和运行域(executable region),LR和ER都允许有一个或者若干个。
- input section:输入段,指目标文件中的指令和数据,相同属性的输入段,经过链接而变成输出段。
- output section:输出段,具有三种属性:Read Only,Read Write,Zero Init(输出段就是实际运行的指令和数据)。
- 输入段和输出段是相对于链接过程来说的,链接过程的输入大都是由源代码编绎生成的目标文件*.o,目标文件中包含的段,相对链接过程来说就是输入段;而链接过程的输出一般是可执行文件,可执行文件中包含的段就称为输出段。
- (备注:有其他资料的说法为,映像文件由若干个域组成,每个域包含1~3个输出段,输出段由输入段组成)
其中,指令和常量为RO,初始化为非零值的变量为RW,未赋初值变量和初始化为零的变量为ZI。通常,RO段的加载域和运行域相同;RW段和ZI段的加载域在ROM/FLASH中,运行域在RAM中。
注意Image文件实际上只包含RO和RW段,而不包含ZI段,因为没有必要。当嵌入式系统复位时,初始化寄存器之后,CPU会先将RW段从加载域复制到运行域中,然后清零一片区域,作为ZI段的运行域。
以STM32F4为例,编译链接完成后,MDK会给出Program Size: Code=... RO-data=... RW-data=... ZI-data=...
其中,Code是指令所占字节数,RO-data是常量所占字节数,它们都属于RO段,存储在FLASH中;而RW-data和ZI-data存储在RAM中。 通过工程目录下的map文件可以查看到以下信息:
Total RO Size (Code + RO Data) ...( ...kB)
Total RW Size (RW Data + ZI Data) ... ( ...kB)
Total ROM Size (Code + RO Data + RW Data) ... ( ...kB)
STM32F4默认从flash启动,然后复制RW段到SRAM中,并清出一片ZI运行域;而RO段不会被复制,即CPU执行的代码是在flash中读取。如下图所示:
这里就说到scatter file(*.sct),即分散加载文件,是该文件在链接器生成映像文件的过程中,指定所有域的地址,大小和包含的段。*.sct一般不需要修改,链接器会按照默认方式工作;当类似于本文开头所述的情况出现时,就要按需修改。
先讲解一个简单的语法例子,以下是“STM32F407ZGT6”的MDK工程中sct文件的默认内容。
LR_IROM1 0x08000000 0x00100000 { ; 第一个加载域,起始地址为0x08000000,大小为0x00100000
ER_IROM1 0x08000000 0x00100000 { ; 第一个运行域,与加载域相同
*.o (RESET, +First) ;将RESET段最先加载到本域,即RESET的起始地址为0,RESET存储的是向量表
*(InRoot$$Sections) ;不详
.ANY (+RO) ;此运行域为所有(any)RO数据的运行域
}
RW_IRAM1 0x20000000 0x00020000 { ; 第二个运行域
.ANY (+RW +ZI) ;此运行域为所有(any)RW和ZI数据的运行域
}
}
对比数据手册可知,加载域就是芯片内置的1M flash,而第二个运行域就是196KB的SRAM去掉CCM的可用128KB。
回到正题,解决这个问题的关键思路,就是将所需的未赋初值变量,通过__attribute__关键字,指定到某输入段中;然后在分散加载文件中,使用UNINIT修饰一个运行域,使这个运行域的数据不会被初始化,然后将该段指定到这个运行域。
.c:uint32 X __attribute__((section("area0") , zero_init)); //疑问:zero_init是area0的属性?
.sct:RW_IRAM1 ...... UNINIT ......{ .ANY (area0) ; }
本文的介绍到此结束,详细的分散加载文件语法规则参见:
https://wenku.baidu.com/view/55076ace541810a6f524ccbff121dd36a32dc47c.html
备注(无关主题):
调试过程是将程序直接加载到芯片的RAM中。要用到*.axf,该文件由编译器产生,不仅包含hex的内容,还包含调试信息,加在可执行代码之前。调试时,这些调试信息不会下载到RAM中,仅有可执行代码会被下载,因此,即便RAM的空间小于axf文件,程序依然有可能在RAM中调试。
调试信息有以下作用:
1.可将源代码包括注释夹在反汇编代码中,这样我们可随时切换到源代码中进行调试。
2.我们还可以对程序中的函数调用情况进行跟踪(通过Watch & Call StackWindow查看)。
3.对变量进行跟踪(利用Watch & Call Stack Window)。
调试信息虽然有用,但程序功能实现后,在目标文件和库中减少调试信息却是非常有益的。减少调试信息可减少目标文件和库大小、加快链接速度、减小最终镜象代码。以下几种方法可用来减少每个源文件产生的调试信息:
1、避免在头文件中条件性使用#define,链接器不能移除共用的调试部分,除非这些部分是完全一样的。
2、更改C/C++源文件,使#included包含的所有头文件有相同顺序。
3、尽量使用数量较多的小头文件而不是较大的单一头文件,这有利于链接器获取更多的通用块。
4、程序中最好只包含必须用到的头文件。避免重复包含头文件,可使用编译器选项--remarks来产生警告信息;
5、使用编译命令行选项--no_debug_macros,从调试表中丢弃预处理宏定义。
出处:https://blog.csdn.net/yy1069442142/article/details/65445330?utm_source=blogxgwz0