前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >STM32的RAM的分配与占用

STM32的RAM的分配与占用

作者头像
bigmagic
发布2020-03-17 14:42:55
5.3K0
发布2020-03-17 14:42:55
举报
文章被收录于专栏:嵌入式iot嵌入式iot嵌入式iot

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
.
.
.

----------------------------------------------------------------------
       Object Totals
           (incl. Generated)
          (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申请的内存都是在这块区域内进行,计算各个线程的栈空间大小,从而得出最合理的大小。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式IoT 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.介绍
  • 2.内存的基本构成
    • 2.1 STM32的堆栈机制
      • 2.2 OS系统内存分配策略
      • 3.STM32内存使用情况分析
        • 3.1 总体情况一览
          • 3.2 各函数所需要的内存
            • 3.3 操作系统RAM的使用情况
            • 3.优化内存使用策略
            • 4.总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档