更新历史
20220318
- 首次编辑,简述 keil 工程生成的代码大小,以及存放的空间位置;
文章目录
- 引言
- 生成数据区分
-
- 各数据含义
-
- 为什么 RW 数据有两份?
- 如何缩减空间
-
- 闪存空间
- RAM 空间
-
- FreeRTOS 堆空间简述
- 参考资料
引言
这篇文章介绍了 keil 工程生成代码的各中 data 的介绍,以及在地址空间中的存放差异。
依旧是【固件防复制系列】的衍射知识总结,在真正讨论程序加载以及堆栈的分配及使用问题前,先以 keil 为实践 IDE 对象来简单描述下代码生成后,各种数据的放置空间,以及若存在空间不足情况下,代码的简单体积缩减。
生成数据区分
各数据含义
图1 keil 编译信息截图
这里我们可以看到:
这里的数值,单位均为字节 Byte。
先分别来说下,各部分的含义:
-
Code: 代码大小
这个很容易理解,也是我们程序最主要的部分;90512 字节
-
RO-data: Read Only data, 只读性质的数据
如字符串,const 数据;4184 字节
-
RW-data: Read Write data, 可读可写性质的数据
已初始化的全局变量;820 字节
-
ZI-data: Zero Initialized, 初始值为 0 的数据
未初始化的全局变量;44140 字节
关于其放置位置,也是根据 MCU 存储介质的性质以及本身的操作类型来决定的,我们知道 MCU 的闪存或 Flash 空间其属于 ROM 存储器,适合存放只读或初始化值非 0 的数据,而 RAM 则适合存放反复修改的数据类型。
(RW-data 会先分配到闪存,但是由于其 RW 的性质,会在 RAM 中进行拷贝。)
则会有下面的分配:
Flash_occupied = Code + RO-data + RW-data = 95516 Bytes = 93 MB
RAM_occupied = RW-data + ZI-data = 44960 Bytes = 43 MB
为什么 RW 数据有两份?
RW 我们知道是初始化且非 0 的全局变量,也就是说相当于我们看书,看到了某个进度,需要下次再看,这里就需要记录一下进度。
由于 RAM 是掉电即失,则不会也不适合将这个进度的记录放到 RAM 中(程序的放置或者映射位置是编译器工具链进行设定的,而数据本身的读写性质是工程师进行编码确定的,映射放置时只看本身的读写性质),而又由于程序运行中,会对其进行读写(产品电源周期内的运行态),所以需要一块掉电不易失的存储空间来存储,这就会将其放置到了闪存及 Flash 空间。
相应的, ZI-data 放在了 RAM 区,因为其是未初始化或者初始化值为 0 的变量,则不用占用闪存空间(相当于,知道是新书,也不用记录这次看到哪了,拿起来看就完了),运行中进行默认初始化为 0 .
注:关于加载的知识,详细请见《程序员的自我修养》,具体版本请见参考资料章节。
如何缩减空间
在嵌入式开发领域中,嵌入式资源是宝贵的,不免会遇到代码体积过大或需要进行优化的情况,单单从代码体积来看,可以如何缩小占用空间。
想要有的放矢的缩减尺寸,需要了解哪些数据属于 RO, RW 和 ZI。
闪存空间
- 优化代码语法的使用;如替换 while(1) 为 for( ; ; ),这样汇编语句会减少,且不占用寄存器,没有判断和跳转,空间和时间上都具有优势:
while (1);
// while(1) 编译后
mov eax,1
test eax,eax
je foo+23h
jmp foo+18h
for(;;);
// 编译后
jmp foo+23h
- 结构体成员之间的排列问题;
- 不需要的字符串或 const 常量的删减;(RO-data区)
- 。。。
RAM 空间
这里的 RAM 一定程度上可以理解为运行时用的空间,涉及到动态建立、修改和访问的数据及数据结构都会放置于这里,比如一些任务控制块、堆栈等。
简化这部分的使用大小:
- 避免栈空间的大量使用,会导致内存溢出;
- 简化函数调用;
- 避免一些不用但是依旧声明的数组等;
- 。。。
如我在代码中注释掉了僵尸代码,一个在源文件中申明的全局数组(外部源文件没有引用):
我们可以看到,这个数据为 64 长的 32 字节元素长度的数组,计算下来就需要 256 字节长度的 RAM 空间,这里注释掉后,再次编译:
图2 keil 编译信息截图(删除冗余代码后)
这里我们对比图1,发现 ZI-data 的大小减少了 44140-43884=256 Byte,也就是我们刚才注释掉的数组大小。
注:这里只是演示了一个很简单的策略,当生成代码大小与预期不一致,有些许出入也是很正常的。
FreeRTOS 堆空间简述
这里简单提一下,如果包含了操作系统的话,比我们产品中使用的是 FreeRTOS,其是在 RAM 区申请了一个较大空间的数组,也就是连续存储空间以作为其本身资源的消耗及管理,这部分是计算在 ZI-data 空间内的,具体大小查看宏定义,比如 v10.0.1 版本的 FreeRTOS 系统堆空间为 15M:
图3 FreeRTOS 堆空间大小宏定义
图4 FreeRTOS 不同内存管理策略下的空间申请
结合图 3、4 我们可知,不同的内存管理策略,都是在 15MB 堆空间大小的前提下,至于不同的内存管理策略具体有什么不同,请参考 FreeRTOS 官网手册: Memory Management; 根据自身开发需求来选定策略。
参考资料
- stackoverflow - ROM and RAM in ARM;
- Arm Cortex-M4 Processor Technical Reference Manual;
- FreeRTOS Developer Docs;
- 《程序员的自我修养 - 链接、转载与库》,俞甲子 石凡 潘爱民 著,中国工信出版社;