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的复位处理入口代码:
Reset_Handler PROC ;PROC等同于FUNCTION,表示一个函数的开始,与ENDP相对?
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
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修饰:
LR_IROM1 0x00000000 0x00080000 { ; load region size_region
ER_IROM1 0x00000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x0000A000 { ; RW data
.ANY (+RW +ZI)
}
MYRAM 0x1000A000 UNINIT 0x00002000 {
.ANY (NO_INIT)
}
}
那么,如果在程序中有一个数组,你不想让它复位后零初始化,就可以这样来定义变量:
unsigned ] __attribute__((at(0x1000A000)));
变量属性修饰符__attribute__((at(adder)))用来将变量强制定位到adder所在地址处。由于地址0x1000A000开始的8KB区域ZI变量不会被零初始化,所以处在这一区域的数组plc_eu_backup也就不会被零初始化了。
这种方法的缺点是显而易见的:要自己分配变量的地址,如果非零初始化数据比较多,这将是件难以想象的大工程(以后的维护、增加、修改代码等等)。所以要找到一种办法,让编译器去自动分配这一区域的变量。
2. 分散加载文家同方法1,如果还是定义一个数组,可以用下面方法:
unsigned ] __attribute__((section("NO_INIT"),zero_init));
变量属性修饰符__attribute__((section(“name”),zero_init))用于将变量强制定义到name属性数据节中,zero_init表示将未初始化的变量放到ZI数据节中。因为“NO_INIT”这显性命名的自定义节,具有UNINIT属性。
3. 如何将一个模块内的非初始化变量都非零初始化?
假如该模块名字为test.c,修改分散加载文件如下所示:
LR_IROM1 0x00000000 0x00080000 { ; load region size_region
ER_IROM1 0x00000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x0000A000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x1000A000 UNINIT 0x00002000 {
test.o (+ZI)
}
}
定义时使用如下方法:
int uTimerCount __attribute__((zero_init));
这里,变量属性修饰符__attribute__((zero_init))用于将未初始化的变量放到ZI数据节中变量,其实keil默认情况下,未初始化的变量就是放在ZI数据区的。
4.将整个程序的非初始化变量都非零初始化 看了上面的,这个已经没有必要说了。
Keil MDK下如何设置非零初始化变量的更多相关文章
- Keil MDK下如何设置非零初始化变量(转)
源:Keil MDK下如何设置非零初始化变量 一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备.而keil mdk在默 ...
- Keil MDK下如何设置非零初始化变量(复位后变量值不丢失)
一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备.而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始 ...
- 痞子衡嵌入式:一个奇怪的Keil MDK下变量链接强制对齐报错问题(--legacyalign)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是一个奇怪的Keil MDK下变量链接强制对齐报错问题. 痞子衡最近一直在参与恩智浦SBL项目(就是一个适用LPC和i.MXRT的完整OT ...
- 痞子衡嵌入式:超级下载算法RT-UFL v1.0在Keil MDK下的使用
痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...
- Linux下如何设置和查看环境变量
Linux的变量种类 按变量的生存周期来划分,Linux变量可分为两类: 1 永久的:需要修改配置文件,变量永久生效. 2 临时的:使用export命令声明即可,变量在关闭shell时失效. 按作用范 ...
- 基于IAP和Keil MDK的远程升级设计
写在前面:三个周之前,我突然想写一个远程升级的程序.那个时候我只是大概知道IAP的意思是在应用编程,但怎么编,我还一无所知.我给自己定下一个个阶段目标,从最基础的代码一点点写起,解决一个又一个的问题. ...
- 合宙AIR105使用Keil MDK + DAP-Link 烧录和调试
关于AIR105 AIR105是合宙LuatOS生态下的一款芯片, 1月初上市, 开发板与摄像头一起搭售(赠送). 从配置信息看, 芯片性能相当不错: Cortex-M4F内核, 最高频率204Mhz ...
- 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU硬件那些事(2.4)- 串行NOR Flash下载算法(Keil MDK工具篇)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Keil MDK工具下i.MXRT的串行NOR Flash下载算法设计. 在i.MXRT硬件那些事系列之<在串行NOR Flash ...
- Keil MDK 无法设置断点【worldsing】
要解决一个问题就怕不知道怎么搜索,或是别人没有遇到过: 同样碰到Keil MDK Debug调试无法设置断点问题,首先来问百度,GOOGLE,一下是我搜索到的结果: 1.keil 不能设置断点,每 ...
随机推荐
- PHP字符编码问题-总结
今天在网上看到一个人的对于php开发中字符编码的总结,感觉不错,摘录如下: 一,php编码转换 1.通过iconv()函数实现编码转换 语法:iconv(s ...
- SQL Server 查看正在运行的事务信息的 2 种方法。
方法 1.sys.dm_tran_session_transactions; 方法 2.dbcc opentran ------------------------------------------ ...
- EasyUI 使用心得
最近项目中用到EasyUI,总结了一下 注:EasyUI中所有的控件不能重名,否则会出现意向不到的后果.这是EasyUI框架决定的. ① EasyUI 获取文本框中的值 //日期 $('#beginD ...
- PHP中的错误处理
程序只要在运行,就免不了会出现错误!或早或晚,只是时间问题罢了. 错误很常见,比如Notice,Warning等等.此时一般使用set_error_handler来处理: <?php set_e ...
- struts2配置文件struts.xml的简介
本文在于总结,深入研究有别人写的很好了,也没必要再去写,将在本文后面附上他们的文章地址: 一.struts2的执行过程: 二.struts2的配置文件struts.xml 下面是其三大部分includ ...
- C# 弗洛伊德(Floyd)算法
弗洛伊德(Floyd)算法 主要是用于计算图中所有顶点对之间的最短距离长度的算法,如果是要求某一个特定点到图中所有顶点之间的最短距离可以用; ; ; ; ...
- HDU 4082 Hou Yi's secret(暴力)
直接6重循环就行了吧...判三角形相似直接从小到大枚举两向量夹角是否相等就行了.注意去重点跟三点共线就行了... #include<algorithm> #include<iostr ...
- (十二)boost库之多线程高级特性
(十二)boost库之多线程高级特性 很多时候,线程不仅仅是执行一些耗时操作,可能我们还需要得到线程的返回值,一般的处理方法就是定义一个全局状态变量,不断轮训状态,就如我目前维护的一个项目,全局变量定 ...
- jQuery.each(object, [callback])方法,用于处理json数组
通用例遍方法,可用于例遍对象和数组. 不同于例遍 jQuery 对象的 $().each() 方法,此方法可用于例遍任何对象.回调函数拥有两个参数:第一个为对象的成员或数组的索引,第二个为对应变量或内 ...
- 【LeetCode练习题】Copy List with Random Pointer
Copy List with Random Pointer A linked list is given such that each node contains an additional rand ...