源:Keil MDK下如何设置非零初始化变量

一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备。而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始化变量数据清零。如何设置非初始化数据变量不被零初始化,这是本篇文章所要探讨的。

在给出方法之前,先来了解一下代码和数据的存放规则、属性,以及复位后为何默认非初始化变量所在RAM都被初始化为零了呢。

什么是初始化数据变量,什么又是非初始化数据变量?(因为我的文字描述不一定准确,所以喜欢举一些例子来辅助理解文字。)

定义一个变量:int nTimerCount=20;变量nTimerCount就是初始化变量,也就是已经有初值;

如果定义变量:int nTimerCount;变量nTimerCount就是一个非赋值的变量,Keil MDK默认将它放到属性为ZI的输入节。

那么,什么是“ZI”,什么又是“输入节”呢?这要了解一下ARM映像文件(image)的组成了,这部分内容略显无聊,但我认为这是非常有必要掌握的。

ARM映像文件的组成:

  • 一个映像文件由一个或多个域(region,也有译为“区”)组成
  • 每个域包含一个或多个输出段(section,也有译为“节”)
  • 每个输出段包含一个或多个输入段
  • 各个输入段包含了目标文件中的代码和数据

输入段中包含了四类内容:代码、已经初始化的数据、未经过初始化的存储区域、内容初始化为零的存储区域。每个输入段有相应的属性:只读的(RO)、可读写的(RW)以及初始化成零的(ZI)。

一个输出段中包含了一些列具有相同的RO、RW和ZI属性的输入段。输出段属性与其中包含的输入段属性相同。

一个域包含一到三个输出段,各个输出段的属性各不相同:RO属性、RW属性和ZI属性

到这里我们就可以知道,一般情况下,代码会被放到RO属性的输入节,已经初始化的变量会被分配到RW属性输入区,而“ZI”属性输入节可以理解为是初始化成零变量的集合。

已经初始化变量的初值,会被放到硬件的哪里呢?(比如定义int nTimerCount=20;那么初始值20被放到哪里呢?),我觉得这是个有趣的问题,比如keil在编译完成后,会给出编译文件大小的信息,如下所示:

Total RO Size (Code + RO Data) 54520 ( 53.24kB) Total RW Size (RW Data + ZI Data) 6088 ( 5.95kB) Total ROM Size (Code + RO Data + RW Data) 54696 ( 53.41kB)

很多人不知道这是怎么计算的,也不知道究竟放入ROM/Flash中的代码有多少。其实,那些已经初始化的变量,是被放入RW属性的输入节中,而这些变量的初值,是被放入ROM/Flash中的。有时候这些初值的量比较大,Keil还会将这些初值压缩后再放入ROM/Flash以节省存储空间。那这些初值是谁在何时将它们恢复到RAM中的?ZI属性输入节中的变量所在RAM又是谁在何时给用零初始化的呢?要了解这些东西,就要看默认设置下,从系统复位,到执行C代码中你编写的main函数,Keil帮你做了些什么。

硬件复位后,第一步是执行复位处理程序,这个程序的入口在启动代码里(默认),摘录一段cortex-m3的复位处理入口代码:

   1: Reset_Handler   PROC        ;PROC等同于FUNCTION,表示一个函数的开始,与ENDP相对?  
   2:  
   3:                 EXPORT  Reset_Handler             [WEAK]  
   4:                 IMPORT  SystemInit  
   5:                 IMPORT  __main  
   6:                 LDR     R0, =SystemInit  
   7:                 BLX     R0  
   8:                 LDR     R0, =__main  
   9:                 BX      R0  
  10:                 ENDP  

初始化堆栈指针、执行完用户定义的底层初始化代码(SystemInit函数)后,接下来的代码调用了__main函数,这里__main函数会调用一些列的C库函数,完成代码和数据的复制、解压缩以及ZI数据的零初始化。数据的解压缩和复制,其中就包括将储存在ROM/Flash中的已初始化变量的初值复制到相应的RAM中去。对于一个变量,它可能有三种属性,用const修饰符修饰的变量最可能放在RO属性区,已经初始化的变量会放在RW属性区,那么剩下的变量就要放到ZI属性区了。默认情况下,ZI数据的零初始化会将所有ZI数据区初始化为零,这是每次复位后程序执行C代码的main函数之前,由编译器“自作主张”完成的。所以我们要在C代码中设置一些变量在复位后不被零初始化,那一定不能任由编译器“胡作非为”,我们要用一些规则,约束一下编译器。

分散加载文件对于连接器来说至关重要,在分散加载文件中,使用UNINIT来修饰一个执行节,可以避免__main对该区节的ZI数据进行零初始化。这是要解决非零初始化变量的关键。因此我们可以定义一个UNINIT修饰的数据节,然后将希望非零初始化的变量放入这个区域中。于是,就有了第一种方法:

1. 修改分散加载文件,增加一个名为MYRAM的执行节,该执行节起始地址为0x1000A000,长度为0x2000字节(8KB),由UNINIT修饰:

   1: LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
   2:   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
   3:    *.o (RESET, +First)
   4:    *(InRoot$$Sections)
   5:    .ANY (+RO)
   6:   }
   7:   RW_IRAM1 0x10000000 0x0000A000  {  ; RW data
   8:    .ANY (+RW +ZI)
   9:   }
  10:   MYRAM 0x1000A000 UNINIT 0x00002000  {
  11:    .ANY (NO_INIT)
  12:   }
  13: }

那么,如果在程序中有一个数组,你不想让它复位后零初始化,就可以这样来定义变量:

    
    unsigned char  plc_eu_backup[PLC_EU_BACKUP_BUF/8] __attribute__((at(0x1000A000)));

变量属性修饰符__attribute__((at(adder)))用来将变量强制定位到adder所在地址处。由于地址0x1000A000开始的8KB区域ZI变量不会被零初始化,所以处在这一区域的数组plc_eu_backup也就不会被零初始化了。

这种方法的缺点是显而易见的:要自己分配变量的地址,如果非零初始化数据比较多,这将是件难以想象的大工程(以后的维护、增加、修改代码等等)。所以要找到一种办法,让编译器去自动分配这一区域的变量。

2. 分散加载文家同方法1,如果还是定义一个数组,可以用下面方法:

    unsigned char  plc_eu_backup[PLC_EU_BACKUP_BUF/8] __attribute__((section("NO_INIT"),zero_init));  

变量属性修饰符__attribute__((section(“name”),zero_init))用于将变量强制定义到name属性数据节中,zero_init表示将未初始化的变量放到ZI数据节中。因为“NO_INIT”这显性命名的自定义节,具有UNINIT属性。

3. 如何将一个模块内的非初始化变量都非零初始化?

假如该模块名字为test.c,修改分散加载文件如下所示:

   1: LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
   2:   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
   3:    *.o (RESET, +First)
   4:    *(InRoot$$Sections)
   5:    .ANY (+RO)
   6:   }
   7:   RW_IRAM1 0x10000000 0x0000A000  {  ; RW data
   8:    .ANY (+RW +ZI)
   9:   }
  10:   RW_IRAM2 0x1000A000 UNINIT 0x00002000  {
  11:    test.o (+ZI)
  12:   }
  13: }

定义时使用如下方法:

       int uTimerCount __attribute__((zero_init));

这里,变量属性修饰符__attribute__((zero_init))用于将未初始化的变量放到ZI数据节中变量,其实keil默认情况下,未初始化的变量就是放在ZI数据区的。

4.将整个程序的非初始化变量都非零初始化 看了上面的,这个已经没有必要说了。

Keil MDK下如何设置非零初始化变量(转)相关推荐

  1. Keil MDK下基于Cortex-M33进行代码仿真

    Keil的uVision5继承了一贯的debug仿真环境,可以方便地进行代码算力统计,代码debug等.但似乎传统的方式不支持cortex-m33等型号mcu,如下图: 当device选择为ARMCM ...

  2. Linux下如何设置和查看环境变量

    Linux的变量种类 按变量的生存周期来划分,Linux变量可分为两类:  1 永久的:需要修改配置文件,变量永久生效.  2 临时的:使用export命令声明即可,变量在关闭shell时失效. 按作 ...

  3. 使用Keil MDK以及标准外设库创建STM32工程

    应部分网友要求,最新加入固件库以及开发环境使用入门视频教程,同时提供例程模板,个人录制,欢迎指正.下载地址:http://dl.dbank.com/c0w0ehqynd 2013.3补充在线视频教程 ...

  4. (2)stm32开发之使用Keil MDK以及标准外设库创建STM32工程

    通过上一节对标准外设库的介绍,想必各位读者对标准外设库已经有了基本的认识,然而由于标准外设库中文件众多,很多初学者在开始很长一段时间内甚至都无法完全自己建立一个工程,很多人只是依赖标准外设库或给定的工 ...

  5. keil stm32标准库放在哪里_使用Keil MDK以及标准外设库创建STM32工程

    应部分网友要求,最新加入固件库以及开发环境使用入门视频教程,同时提供例程模板,个人录制,欢迎指正.下载地址:http://dl.dbank.com/c0w0ehqynd 2013.3补充在线视频教程 ...

  6. 基于IAP和Keil MDK的远程升级设计

    写在前面:三周之前,我突然想写一个远程升级的程序.那个时候我只是大概知道IAP的意思是在应用编程,但怎么编,我还一无所知.我给自己定下一个个阶段目标,从最基础的代码一点点写起,解决一个又一个的问题.三 ...

  7. keil c语言浮点数,Keil MDK浮点数支持不正常的解决方案

    在调用printf调试浮点数的时候,在内存中表示正确,但是打印数据就都为0.00,其他整形数据表示正常. 经查资料得知,浮点数要求堆栈八字节对齐导致,如果系统的默认堆栈不是八字节对齐的一定会出现此问题 ...

  8. stm32开发之使用Keil MDK以及标准外设库创建STM32工程

    通过上一节对标准外设库的介绍,想必各位读者对标准外设库已经有了基本的认识,然而由于标准外设库中文件众多,很多初学者在开始很长一段时间内甚至都无法完全自己建立一个工程,很多人只是依赖标准外设库或给定的工 ...

  9. 在 Keil uVision4 MDK下配置开发STM32F103Z完整教程

    转载的,请原作者勿怪,以下为原链接: http://www.51hei.com/bbs/dpj-30359-1.html(欢迎直接查看原作者) 环境搭建: 1.安装 Keil uVision4 MDK ...

最新文章

  1. 跨平台开发Flutter初体验
  2. Lambda表达式的基础知识
  3. 极狐(GitLab)发布首款“GitNative”DevOps云一体化解决方案
  4. consul命令行查看服务_第三章 consul服务注册与服务查询
  5. oracle在archivelog模式进行rman备份和恢复
  6. 51nod1005大数加法(C语言实现大数)
  7. linux学习入门之Linux系统目录结构
  8. 学生a3制图标题栏尺寸手绘_机械制图基础知识学习,如何画好一张零件图?
  9. Flask+Bootstrap展示MM图片在网页上,从而更好地挑选富婆??
  10. win7怎么修改锁定计算机时间,锁定Windows7系统时间教程
  11. 个人购买云服务器的必要性和最常见几个用途?细节考虑
  12. html手机响应式布局,手机网页设计中的响应式布局
  13. cmd运行java语名_怎么用cmd运行java文件
  14. Android开发之ViewFlipper
  15. 三张表,四张表怎么连接?
  16. 打败你的不是现实,是时间管理|互联网时代的时间管理术
  17. springboot logback配置included及时区设置问题
  18. leetcode 953. 验证外星语词典 做题笔记
  19. DDD(领域驱动设计)+SpringCloud的代码示例
  20. IE 在中国的春运刷票中又败了,Chrome扩展插件crx时代来临

热门文章

  1. android 自定义相册选择,Android通过手机拍照或从本地相册选取图片设置头像
  2. 按照文件名最后数字进行排序
  3. 智能车竞赛秘书处与RT-Thread关于第十七届智能车竞赛第一次会议
  4. 测量150kHz接收电路的主要的一些特性:扫频,线性,工作电压影响
  5. 直播预告 | STC单车拉力组专题培训
  6. 可否使用串联LED(或者光敏LED)来制作光电检测板?
  7. 海军工程大学学生电子课程-DIY Final预验收
  8. 一款蓝牙音频无线传输设备上拆下的光电管
  9. mysql数据库支持的锁粒度包括_1. Mysql的并发(锁策略、锁粒度、事务)
  10. 未解决计算机主机与打印机,电脑无法与打印机连接 计算机网考题目2(12)