关于stm32的堆、栈、内存管理以及外扩ram的使用总结

发布者:素心静听最新更新时间:2020-01-12 来源: eefocus关键字:stm32  内存管理  外扩ram 手机看文章 扫描二维码
随时随地手机看文章

刚接手STM32时,编写一个空工程,BUILD后,


Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632 


程序已用了1600多的RAM,要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,


分析map,你会发现是堆和栈占用的,在startup_stm32f10x_md.s文件中,它的前面几行就有以上定义,这下该明白了吧。


Stack_Size   EQU   0x00000400


Heap_Size   EQU   0x00000200


对于一个C语言程序而言,内存空间主要由五个部分组成:代码段(.text)、数据段(.data)、静态区(.BSS)、堆和栈组成。


BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量 (这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间 而只在运行的时候占用内存空间 ,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。


数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中 。


代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中 。 

  

栈区:由系统自动分配,栈区的分配运算内置于处理器的指令集,当函数执行结束时由系统自动释放。存放局部变量。栈的缺点是:容量有限,当相应的区间被释放时,局部变量不可再使用。查询栈容量的命令:ulimits -s。栈是一块连续的区域,向高地址扩展,栈顶和容量是事先约定好的。


堆区:在程序的执行过程中才能分配,由程序员决定,编译器在编译时无法为他们分配空间,只有在程序运行时分配,所以被称为动态分配。堆是不连续的区域,向高地址扩展。由于系统用链表来描述空闲的地址空间,链表的遍历是由地地址向高地址的,故堆区是不连续的动态的存储空间。


重点分析一下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之后的地址开始分配的。


我们知道了SRAM空间用来存放了什么东西了:1、各个文件中声明和定义的全局变量、静态数据和常量;2、HEAP区;3、STACK区。


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


(1)栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。


(2)堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。


(3)全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统自动释放。


(4)文字常量区:常量字符串就是存放在这里的。


(5)程序代码区:存放函数体的二进制代码。


例如:


int a=0;   //全局初始化区


char *p1;   //全局未初始化区


main()


{


int b;   //栈


char s[]="abc";   //栈


char *p3= "1234567";   //在文字常量区Flash


static int c =0 ;   //静态初始化区


p1= (char *)malloc(10);   //堆区


strcpy(p1,"123456");   //"123456"放在常量区


}


所以堆和栈的区别:


stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放。


stack的空间有限,heap是很大的自由存储区。


程序在编译期和函数分配内存都是在栈上进行,且程序运行中函数调用时参数的传递也是在栈上进行。


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------


1.堆和栈大小


定义大小在startup_stm32f2xx.s


Stack_Size  EQU  0x00000400


Heap_Size  EQU  0x00000200


2.堆和栈位置


通过MAP文件可知


HEAP  0x200106f8  Section  512  startup_stm32f2xx.o(HEAP) 

STACK  0x200108f8  Section  1024  startup_stm32f2xx.o(STACK)


__heap_base  0x200106f8  Data  0  startup_stm32f2xx.o(HEAP) 

__heap_limit  0x200108f8  Data  0  startup_stm32f2xx.o(HEAP) 

__initial_sp  0x20010cf8  Data  0  startup_stm32f2xx.o(STACK)


显然 Cortex-m3资料可知:__initial_sp是堆栈指针,它就是FLASH的0x8000000地址前面4个字节(它根据堆栈大小,由编译器自动生成)


显然堆和栈是相邻的。


3.堆和栈空间分配


栈:向低地址扩展


堆:向高地址扩展


显然如果依次定义变量


先定义的栈变量的内存地址比后定义的栈变量的内存地址要大


先定义的堆变量的内存地址比后定义的堆变量的内存地址要小


4.堆和栈变量


栈:临时变量,退出该作用域就会自动释放


堆:malloc变量,通过free函数释放


另外:堆栈溢出,编译不会提示,需要注意


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


如果使用了HEAP,则必须设置HEAP大小。 如果是STACK,可以设置为0,不影响程序运行。 IAR STM8定义STACK,是预先在RAM尾端分配一个字节的区域作为堆栈预留区域。 

当程序静态变量,全局变量,或者堆与预留堆栈区域有冲突,编译器连接的时候就会报错。 你可以吧STACK设置为0,并不影响运行。(会影响调试,调试会报堆栈溢出警告)。 其实没必要这么做。 


一般程序,(在允许范围内)设置多少STACK,并不影响程序真实使用的RAM大小, (可以试验,把STACK设置多少,编译出来的HEX文件都是一样), 程序还是按照它原本的状态使用RAM,把STACK设置为0,并不是真实地减少RAM使用。仅仅是欺骗一下编译器,让程序表面上看起来少用了RAM。 而设置一定size的STACK,也并不是真的就多使用了RAM,只是让编译器帮你 检查一下,是否能够保证有size大小的RAM没有被占用,可以用来作为堆栈。 以上仅针对IAR STM8.


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


从以上网摘来看单片机的堆和栈是分配在RAM里的,有可能是内部也有可能是外部,可以读写;


栈:存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,


      编译器里定义的Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出;


     Stack_Size不影响Hex,更不影响Hex怎么运行的,只是在Debug调试时会提示错。栈溢出也有是超过了国界进行


     活动,只要老外没有意见,你可以接着玩,有老外不让你玩,你就的得死,或是大家都死(互相撕杀),有的人写


    单片机代码在函数里定义一个大数组 int buf[8192],栈要是小于8192是会死的很惨。



堆:存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在RAM里的,是


     存在Hex里,下载到Flash里,上电由代码(编译器生成的汇编代码)搬过去的。有的人很“霸道”,上电就霸占已一块很


    大的RAM(Heap_Size),作为己有(malloc_init),别人用只能通过他们管家借(malloc),用完还得换(free)。所以  


    一旦有“霸道”的人出现是编译器里必须定义Heap_Size,否则和他管家借也没有用。


总之:堆和栈有存在RAM里,他两各分多少看函数需求,但是他两的总值不能超过单片机硬件的实际RAM尺寸,否则只能


     到海里玩(淹死了)或是自己打造船接着玩(外扩RAM)。


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现 2 个函数: malloc 和 free; malloc 函数用于内存申请, free 函数用于内存释放。


嵌入式系统的堆栈,不管是用什么方法来得到内存,感觉他的方式都和编程中的堆差不多。目前我知道两种获得内存情况:


(1)用庞大的全局变量数组来圈住一块内存,然后将这个内存拿来进行内存管理和分配。这种情况下,堆栈占用的内存就是上面说的:如果没有初始化数组,或者数组的初始化值为0,堆栈就是占用的RAM的ZI-data部分;如果数组初始化值不为0,堆栈就占用的RAM的RW-data部分。这种方式的好处是容易从逻辑上知道数据的来由和去向。


(2)就是把编译器没有用掉的RAM部分拿来做内存分配,也就是除掉RW-data+ZI-data+编译器堆+编译器栈后剩下的RAM内存中的一部分或者全部进行内存管理和分配。这样的情况下就只需要知道内存剩下部分的首地址和内存的尾地址,然后要用多少内存,就用首地址开始挖,做一个链表,把内存获取和释放相关信息链接起来,就能及时的对内存进行管理了。内存管理的算法多种多样,不详说,这样的情况下:OS的内存分配和自身局部变量或者全局变量不冲突,之前我就在这上面纠结了很久,以为函数里面的变量也是从系统的动态内存中得来的。这种方式感觉更加能够明白自己地址的开始和结束。


关键字:stm32  内存管理  外扩ram 引用地址:关于stm32的堆、栈、内存管理以及外扩ram的使用总结

上一篇:STM32栈和堆使用不同RAM的实现方法
下一篇:STM32的Code/RO/RW/ZI区、Flash/Ram的占用情况、堆栈大小的设置

推荐阅读最新更新时间:2024-11-02 09:31

STM32GPIO——快速IO的使用
STM32的每个GPIO端口都有两个特别的寄存器,GPIOx_BSRR和GPIOx_BRR寄存器,通过这两个寄存器可以直接对对应的GPIOx端口置'1'或置'0'。 GPIOx_BSRR的高16位中每一位对应端口x的每个位,对高16位中的某位置'1'则端口x的对应位被清'0';寄存器中的位置'0',则对它对应的位不起作用。 GPIOx_BSRR的低16位中每一位也对应端口x的每个位,对低16位中的某位置'1'则它对应的端口位被置'1';寄存器中的位置'0',则对它对应的端口不起作用。 简单地说
[单片机]
STM32微控制器中采用DMA实现方波的产生和捕获
  1 STM32微 控制器 介绍   STM32系列微控制器是ST公司基于Cortex-M3内核的高集成度的微控制器。它在性能、价格、功耗和实时性方面树立了一个新的标杆,集成了Cortex-M3内核,以及双ADC、多用途的通用 时钟 TIMx、RTC、I2C、SPI、UART、 CAN 、DMA、 USB 等丰富的 外设 。其功耗在全速72MHz所有模块都打开时也仅仅为36 mA,在低功耗模式下其功耗仅为2μA。   2 DMA和TIMx简介   STM32系列微控制器均含有DMA和通用时钟TIMx模块。其低端型号中仅包含DMA1,支持7个通道;高端型号还包括DMA2,支持5个通道。它的每个通道可任意指定工作模式,如
[电源管理]
<font color='red'>STM32</font>微控制器中采用DMA实现方波的产生和捕获
基于STM32的红外接收
4.23.1概述 人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为0.62~0.76μm;紫光的波长范围为0.38~0.46μm。比紫光光波长更短的光叫紫外线,比红光波长更长的光叫红外线最广义地来说,传感器是一种能把物理量或化学量转变成便于利用的电信号的器件,红外传感器就是其中的一种。随着现代科学技术的发展,红外线传感器的应用已经非常广泛。 4.23.1.1 红外 接收头 工作原理 红外 接收头 一般是接收、放大、解调一体头,一般红外信号经接收头解调后,数据 “0”和“1”的区别通常体现在高低 电平 的时间长短或信号周期上,单片机解码时,通常将接收头输出脚连接到单片机的 外部中断
[单片机]
基于<font color='red'>STM32</font>的红外接收
STM32 IAP 设计实例 (一)
项目需要,需要开发一款手持设备,对产品进行软件升级。现在的产品都是使用STM32,所以可以很方便的应用STM32的 IAP功能对软件进行在线升级。 总体需求就是,主机Master通过CAN接口,发送数据给从机Slave。从机在接收到应用程序APP后,把接收的数据覆盖掉原来的应用程序区。从而实现Master对Slave的在线升级。 这里先介绍IAP,对IAP的整体实现有个了解,方便后续开发。参考了原子STM32开发板,对IAP功能的介绍。 要实现IAP,需要有两个项目代码。第一个称之为Bootloader程序,第二个称之为APP程序。Bootloader负责引导APP程序启动,以及需要在线更新APP时,接收主机发送的APP
[单片机]
<font color='red'>STM32</font> IAP 设计实例 (一)
MDK5 打开程序 提示错误 device not found device
双击把这些都按上就好了
[单片机]
MDK5 打开程序 提示错误 device not found device
基于ARM微处理器TCP/IP协议LwlP实现
   0 引 言   随着嵌入式系统与网络的日益结合,越来越多的嵌入式设备需要实现Internet网络化,支持嵌入式设备接入网络,已成为嵌入式领域重要的研究方向。而目前嵌入式系统中大量应用低速处理器,受内存和速度限制,实现完整的TCP/IP协议较为困难,LwIP作为较为成熟的嵌入式TCP/IP协议栈受到了广泛的应用。    1 硬件平台   本通信系统的硬件平台由以下几个部分组成:S3C2410主CPU芯片控制嵌入式外围设备的存储、通信、保护、调试、显示等操作;DSP和FPGA负责信号数据的采集和处理;CS8900A负责网络数据的收发;其他部分还包括串口RS232的通信、LCD的数据显示、数据存储FLASH和SDRAM
[嵌入式]
WS2812灯珠(二)-- STM32 SPI+DMA方式驱动
通过硬件SPI的可以很巧妙的模拟出WS2812的通信时序,用spi的8位数据模拟ws281x的一位数据。 要将系统时钟设置为56M,SPI分频数设置为8,则SPI的通信频率为7M,1s/7M≈143ns 即传输一位数据的时间约为143纳秒(ns) 3*143 = 429ns 5*143 = 715ns 符合WS281X芯片的通信时序。 11111000 high level (十六进制:0XF8)表示WS281X的1码 11100000 low level (十六进制:0XE0)表示WS281X的0码 程序头文件部分: 通过宏的方式定义了灯珠个数和WS281X的0码和1码。 #ifndef __WS2812_
[单片机]
STM32掌机教程5,程序框架,随机,加命与升级
随机生成地鼠   随机数是游戏里边非常重要的组成部分,贪吃蛇随机刷新下一个食物,俄罗斯方块随机生成下一个方块,大富翁扔骰子,都是随机的。甚至微信群红包,金额也是随机的。正是因为这些事件不可预测,游戏才充满趣味性。我们地鼠的生成,当然也要随机。   然而,计算机产生的随机数,都是“伪随机”。伪,指的是说它是随机的,但是却都是有规律可循的。对于C语言,可以直接调用一个随机数生产函数srand()。但是这个函数需要种子。随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。也就是说,伪随机数也是某种对应映射的产物,而这个自变量就是种子。   如果你每次调用srand()时都提
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved