keil-STM32中如何查看代码大小,和KEIL51稍微不同

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的分配与占用

原创

bigmagic

嵌入式IoT

2019/04/14 11:39

阅读数 255

本文被收录于专区

硬件 & IoT

进入专区参与更多专题讨论 

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内存都是在以上的资源上申请的。

目前操作系统的线程中,使用的线程如下

线程名称堆栈大小说明
main224main线程
tsk_xxx1_entry184a线程
tsk_xxx2_entry160b线程
tsk_xxx3_entry168c线程
tsk_xxx4_entry160d线程
tsk_xxx5_entry176e线程
tsk_xxx6_entry160f线程

也就任务最少也需要分配的栈空间大小


对于申请在rt_heap上的不只有线程的栈空间,还有线程控制块,每一个线程控制块为128字节,每申请一块内存都需要在在头部加上12字节的头部信息。所以在创建这些线程时,一共消耗rt_heap的资源为 


也就是线程上消耗的rt_heap的大小为2632bytes。

还有邮箱与时间消耗的rt_heap空间

事件统计:

事件名称占用大小说明
a_re32回复事件
b_te32发送事件
tcle32其他消息

所以需要的大小为


信号量统计

信号量名称占用大小说明
a_rsem32a消息
b_rmsg32b消息信号量
c_sem32c信号量
d_rmsg32d接收信号量

总结来看,消耗的rt_heap上的内存空间为

3.优化内存使用策略

如果要优化RAM的使用,可以有以下几个办法:

OS的栈内存优化

缩小OS的堆,目前来看给OS内存空间4K还算比较的合理。但是也可以缩减到3K左右,这个就根据需求来定。这4K的资源可以做如下的分配:

线程名称堆栈大小TCB内存头部说明
main256(min:224)12824main线程
tsk_xxx1_entry256(min:184)12824a线程
tsk_xxx2_entry256(min:160)12824b线程
tsk_xxx3_entry256(min:168)12824c线程
tsk_xxx4_entry256(min:160)12824d线程
tsk_xxx5_entry256(min:176)12824e线程
tsk_xxx6_entry256(min:160)12824f线程

如果按照每个堆栈都256字节来计算,那么总共需要的RAM空间为2856字节。加上信号量与邮箱需要的资源一共是3164字节。现在分配的是4096字节。

以上是一种方式,也可以不用rt_thread的内存管理,这样就烧了内存头部信息的占用。

系统栈上的优化

对于系统栈上目前已知的消耗是2752。可以优化的地方是系统栈的空间,现在用的是1024字节。应该可以缩减到512字节,但是目前没有缩减。

然后是idle线程的,现在是分配了256字节的静态数组,这里用不了那么多,可以缩减到128字节即可。

另外具体的细节部分还可以调整,目前程序的至少可以缩小1K字节。

4.总结

STM32降低RAM的使用,本质上就是降低堆栈的使用。在这个过程中,只要搞清楚内存的分配方式以及OS的堆栈使用情况即可进行内存的合理规划。

对于降低RAM的过程,可以从以下方面入手,如果用局部变量,要考虑到栈的分配问题,栈空间的计算以函数最深的入栈开始,一层一层的计算累加,得到最大的栈的大小,由此,可以计算得到栈的大小。OS上的内存有着自己的一套内存管理方式,所以可以划分出一块静态区域给OS作为堆使用。OS申请的内存都是在这块区域内进行,计算各个线程的栈空间大小,从而得出最合理的大小。

https://my.oschina.net/u/4239621/blog/4351786

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值