未赋初值变量的非零初始化

有些场合中(例如工控产品),当系统复位后(非上电复位),可能要求保持复位前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

猜你喜欢

转载自blog.csdn.net/WangJianlin3/article/details/84343841