keil中如何查看代码大小
qq_511386807 2019-12-31 11:14:36 1352 收藏 2
分类专栏: 软件技巧 C语言学习笔记 STM32学习笔记 文章标签: keil mdk 生成 代码大小
版权
在用keil编译完代码后,会生成编译信息
(1) Code(inc.Data) : 包含两部分,即代码和数据
- code,即程序代码部分
- inline data. For example, literal pools(文字常量池), and short strings(短字符串)等. 这个一般被忽略,请大家注意!!!
- 代码段,存放程序的代码部分。
(2) RO Data: read-only data,只读的数据
Shows how many bytes are occupied by read-only data. This is in addition to the inline data included in the Code (inc. data) column. 除inline data 之外的所有只读数据。
只读数据段,存放程序中定义的常量。
(3) RW Data: read write data,可读写的数据
Shows how many bytes are occupied by read-write data.
读写数据段,存放初始化为非0值的全局变量。
(4) ZI Data: zero initialized data,零初始化的可读写变量
Shows how many bytes are occupied by zero-initialized data.
0数据段,存放未初始化的全局变量及初始化为0的变量。
存储Size:
RO size: Code + RO_data,表示程序占用Flash空间的大小。
RW size: RW_data + ZI_data,表示运行时占用RAM的大小。
ROM (minimum)size = Code + RO_data + RW_data (即烧/下载程序到FLASH/ROM时,所占用的最小空间)
Total ROM Size (Code + RO Data + RW Data)这样所写的程序占用的ROM的字节总数,也就是说程序所下载到ROM flash 中的大小。为什么Rom中还要存RW,因为掉电后RAM中所有数据都丢失了,每次上电RAM中的数据是被重新赋值的,每次这些固定的值就是存储在Rom中的,为什么不包含ZI段呢,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。RAM相当于内存,Flash相当于硬盘。
RAM size: RW Data + ZI Data (即程序运行的时,RAM使用的空间)
通过上述信息可以看出当前代码编译后的总大小为:16088+372+204=16664 字节
16664/1024=16.2734375 K
查看编译后的二进制文件大小
说明理论的计算和实际是相符的。
单片机在上电后默认是从Flash启动,启动之后会将RW段中的RW-data(初始化的全局变量)搬运到RAM中,但不会搬运RO段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址和大小分配出ZI段,并将这块RAM区域清0.
其中动态内存堆为未使用的RAM空间,应用程序申请和释放的内存块都来自于该空间。
#include “main.h”
const staic int ADDR = 0x000000FF; //存放在RO段
int num; //存放在ZI段
int s = 100; //存放在RW段
void main (void)
{
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
还有一种方法,可以直接在keil生成的工程文件*.map中查看。
具体操作方法为,在keil中全部编译一次工程。
然后在双击工程文件名,这时候会打开一个map文件
拖动右侧滚动条,一直拖动到底部。
在这个文件最底部也会显示内存得占用情况,将滚动条向上滚动一点,还可以看到内存使用的详细情况。
通过编译器生成的报告,可以更清晰的看到每个文件占用内存情况。
如果感觉在工程中打开这个文件比较麻烦,也可以直接在工程文件夹中找到这个文件打开,找到后缀名为map的文件,以文本格式打开。
拖动滚动条到最底部,就可以看到代码占用内存的详细情况。
https://blog.csdn.net/qq_20222919/article/details/103779582
STM32的RAM的分配与占用
原创
2019/04/14 11:39
阅读数 255
本文被收录于专区
5点讲透云原生安全!Linux Foundation开源软件大学开课啦!>>>
1.介绍
本文主要针对如何合理的使用STM32的RAM角度入手,对STM32的RAM进行分配与计算。目的是降低RAM的使用率,将RAM的使用情况都弄清楚,从而合理的规划及分配内存。
本文涉及到一些堆栈方面的思考,在MDK中查看MAP文件及堆栈使用情况的文件进行分析,得出当前程序RAM的分配情况,同时对可以缩减的地方进行分析。
2.内存的基本构成
可编程内存基本上可以分为以下几个部分:静态存储区bss段、堆区和栈区。他们的功能不同,使用方法也就不同。
静态存储区:
静态存储区也就是BSS段,英文是Block Started by Symbol的简称,通常是指存放在未初始化的全局变量的一块内存区域,在程序载入时由内核清零。静态存储区在程序编译好的时候就已经分配好,这块内存在程序的整个运行期间都存在,主要存放静态数据全局数据和常量。
栈区:
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内容容量有限。
堆区:
也称动态内存分配。一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。程序在运行时候调用malloc或者new申请任意大小的内存,程序员自己负责在适当的时候free或者delete释放内存。动态内存的生存期可以由程序决定。良好的使用方法是:如果某动态内存不再使用,需要将其释放掉,否则,很容易出现内存泄漏现象。
其内存的分配规律:
如果系统调用了malloc,从0X20000000开始依次为:静态存储区+堆区+栈区
如果系统未调用了malloc,从0X20000000开始依次为:静态存储区+栈区
2.1 STM32的堆栈机制
要搞清楚stm32的堆栈机制,需要理清楚stm32的存储结构。
在stm32中,flash,SRAM寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。可访问的存储器分为8个主要块,每个块为512MB。
C语言上分为栈、堆、bss、data、code段。重点分析一下STM32以及在MDK里面段的划分。
MDK下Code,RO-data,RW-data,ZI-data这几个段:
Code是存储程序代码的。
RO-data是存储const常量和指令。
RW-data是存储初始化值不为0的全局变量。
ZI-data是存储未初始化的全局变量或初始化值为0的全局变量。
Flash=Code + RO Data + RW Data;
RAM= RW-data+ZI-data;
这个是MDK编译之后能够得到的每个段的大小,也就能得到占用相应的FLASH和RAM的大小,但是还有两个数据段也会占用RAM,但是是在程序运行的时候,才会占用,那就是堆和栈。在stm32的启动文件.s文件里面,就有堆栈的设置,其实这个堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配的。
在stm32的启动文件中,statrtup_stm32l151xba.s文件中,有一句这样的函数
Stack_Size EQU 0x400
表示栈的大小为0x400也就是1024字节。这样CPU在处理任务的时候,函数局部变量最多可以占用的空间大小为1024字节。这里的栈大小包括函数的嵌套,递归等等,都是从这个栈里面分配出来的。
所以如果一个函数的局部变量过多,或者嵌套层数越深,那么程序非常容易出现崩溃的情况。所以一定不要在函数里放过多的局部变量。
堆的增长方向时向上的,而栈的增长方向时向下的,并且没有固定的界限,一旦堆栈冲突,函数就会崩溃。总体上也就是说,在使用堆栈的过程中,一定要确保堆栈的大小及使用情况。
2.2 OS系统内存分配策略
对于OS中对堆栈的管理,主要分为两种情况:
(1)用庞大的全局变量数组来圈住一块内存,然后将这个内存拿来进行内存管理和分配。这种情况下,堆栈占用的内存就是上面说的:如果没有初始化数组,或者数组的初始化值为0,堆栈就是占用的RAM的ZI-data部分;如果数组初始化值不为0,堆栈就占用的RAM的RW-data部分。这种方式的好处是容易从逻辑上知道数据的来由和去向。目前在rtthread的内存管理上就是采用这种方式。
在board.c中,可以看到如下的代码
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 1024
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
用这种方式也有一定的弊端,就是在操作系统创建任务或者申请内存时,会多占用一些内存资源。
(2)就是把编译器没有用掉的RAM部分拿来做内存分配,也就是除掉RW-data+ZI-data+编译器堆+编译器栈后剩下的RAM内存中的一部分或者全部进行内存管理和分配。这样的情况下就只需要知道内存剩下部分的首地址和内存的尾地址,然后要用多少内存,就用首地址开始挖,做一个链表,把内存获取和释放相关信息链接起来,就能及时的对内存进行管理了。
3.STM32内存使用情况分析
如果要分析出STM32的内存使用情况,可借助KEIL中编译出来的map文件进行查阅
3.1 总体情况一览
首先可以在map的末尾看总的内存使用情况
RO-data是 Read Only 只读常量的大小,如const型;
RW-data是(Read Write) 初始化了的可读写变量的大小;
ZI-data是(Zero Initialize) 没有初始化的可读写变量的大小。ZI-data不会被算做代码里因为不会被初始化;
其中RW Data + ZI Data表示总共需要占用的RAM的大小。而Code + RO Data + RW Data表示ROM需要的大小,根据这两个值,可以根据程序合理的选择相应的MCU。
3.2 各函数所需要的内存
以下是函数分配时的内存分配情况,可根据下面的map文件定位到具体的函数的内存使用情况,其中比较重要的是ZI Data,因为这些内存都是分配在RAM空间中的。
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
.
.
.
----------------------------------------------------------------------
31376 2302 650 400 6848 668739 Object Totals
0 0 32 0 0 0 (incl. Generated)
54 0 3 10 0 0 (incl. Padding)
从上面的表格,可以分析出(ZI Data+RW Data)正好是7248,也就是RAM的空间大小。
对于以上的数据,可以从占用RAM最大的开始计时
board.c
该文件是RT-THREAD操作系统里面的,划分了一个4KB的静态数组作为操作系统分配的内存区域。
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 3072
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
该文件里定义了一个全局的数组作为操作系统分配的内存区域,这块区域作为系统线程的栈空间使用,也可以用来动态的申请内存区域。在这块RAM中,合理的估算每个线程的栈大小可以有效的对该大小进行规划。
startup_stm32l151xba.S
启动函数中,会分配堆栈,这个堆栈是供C语言使用的,在进行程序跳转或者中断到来时,都会进行入栈及出栈的操作。目前分配给系统的堆栈空间时1KB。
Stack_Size EQU 0x400
usart.c
该函数是HAL库函数中定义的,其中用到了几个全局的结构体,该结构体的用处主要是供其他函数调用UART的操作句柄。这里消耗的RAM资源为600K。
idle.c
idle线程与其他的线程不一样的地方就是该线程的栈不是存在于rt_heap里面,而是单独为其申请了一块静态数组作为线程使用的堆栈。该函数消耗的RAM资源为384KB。
仅仅这四个文件就占用了6KB左右的资源。下面来分析一下具体的内存使用情况。
3.3 操作系统RAM的使用情况
在操作系统中,使用RAM的情况可以通过对每个线程栈的最大深度来进行计算。
在MDK中,可以查看Static Call Graph for image文件来查看栈的使用情况
可以看出,main函数的线程栈最大,为224bytes。
那么如何计算线程的栈的最大值?
就拿main线程来分析
main函数的最深的栈的最后一个函数为_rt_timer_remove
然后看一下rt_timer_stop函数
然后是rt_thread_suspend
以此类推,可以得到main函数线程最大需要消耗的栈空间大小为224bytes。
前面分析出对于操作系统使用的内存,都是在rt_heap上,而这个内存目前是4KB。
所以对于rt_malloc内存都是在以上的资源上申请的。
目前操作系统的线程中,使用的线程如下
线程名称 | 堆栈大小 | 说明 |
---|---|---|
main | 224 | main线程 |
tsk_xxx1_entry | 184 | a线程 |
tsk_xxx2_entry | 160 | b线程 |
tsk_xxx3_entry | 168 | c线程 |
tsk_xxx4_entry | 160 | d线程 |
tsk_xxx5_entry | 176 | e线程 |
tsk_xxx6_entry | 160 | f线程 |
也就任务最少也需要分配的栈空间大小
对于申请在rt_heap上的不只有线程的栈空间,还有线程控制块,每一个线程控制块为128字节,每申请一块内存都需要在在头部加上12字节的头部信息。所以在创建这些线程时,一共消耗rt_heap的资源为
也就是线程上消耗的rt_heap的大小为2632bytes。
还有邮箱与时间消耗的rt_heap空间
事件统计:
事件名称 | 占用大小 | 说明 |
---|---|---|
a_re | 32 | 回复事件 |
b_te | 32 | 发送事件 |
tcle | 32 | 其他消息 |
所以需要的大小为
信号量统计
信号量名称 | 占用大小 | 说明 |
---|---|---|
a_rsem | 32 | a消息 |
b_rmsg | 32 | b消息信号量 |
c_sem | 32 | c信号量 |
d_rmsg | 32 | d接收信号量 |
总结来看,消耗的rt_heap上的内存空间为
3.优化内存使用策略
如果要优化RAM的使用,可以有以下几个办法:
OS的栈内存优化
缩小OS的堆,目前来看给OS内存空间4K还算比较的合理。但是也可以缩减到3K左右,这个就根据需求来定。这4K的资源可以做如下的分配:
线程名称 | 堆栈大小 | TCB | 内存头部 | 说明 |
---|---|---|---|---|
main | 256(min:224) | 128 | 24 | main线程 |
tsk_xxx1_entry | 256(min:184) | 128 | 24 | a线程 |
tsk_xxx2_entry | 256(min:160) | 128 | 24 | b线程 |
tsk_xxx3_entry | 256(min:168) | 128 | 24 | c线程 |
tsk_xxx4_entry | 256(min:160) | 128 | 24 | d线程 |
tsk_xxx5_entry | 256(min:176) | 128 | 24 | e线程 |
tsk_xxx6_entry | 256(min:160) | 128 | 24 | f线程 |
如果按照每个堆栈都256字节来计算,那么总共需要的RAM空间为2856字节。加上信号量与邮箱需要的资源一共是3164字节。现在分配的是4096字节。
以上是一种方式,也可以不用rt_thread的内存管理,这样就烧了内存头部信息的占用。
系统栈上的优化
对于系统栈上目前已知的消耗是2752。可以优化的地方是系统栈的空间,现在用的是1024字节。应该可以缩减到512字节,但是目前没有缩减。
然后是idle线程的,现在是分配了256字节的静态数组,这里用不了那么多,可以缩减到128字节即可。
另外具体的细节部分还可以调整,目前程序的至少可以缩小1K字节。
4.总结
STM32降低RAM的使用,本质上就是降低堆栈的使用。在这个过程中,只要搞清楚内存的分配方式以及OS的堆栈使用情况即可进行内存的合理规划。
对于降低RAM的过程,可以从以下方面入手,如果用局部变量,要考虑到栈的分配问题,栈空间的计算以函数最深的入栈开始,一层一层的计算累加,得到最大的栈的大小,由此,可以计算得到栈的大小。OS上的内存有着自己的一套内存管理方式,所以可以划分出一块静态区域给OS作为堆使用。OS申请的内存都是在这块区域内进行,计算各个线程的栈空间大小,从而得出最合理的大小。