Gestión de memoria relacionada con STM32 (arquitectura de memoria, gestión de memoria, análisis de archivos de mapas)

Este artículo ha participado en el evento "Ceremonia de creación de recién llegados" para comenzar juntos el camino de la creación de oro.

把以前看过的做过的笔记,结合自己的参考分析,利用假期好好整理一遍,希望对大家有所帮助
复制代码

prefacio

Usando un chip STM32, hay dos indicadores intuitivos para la memoria: tamaño de RAM y tamaño de FLASH, como la serie STM32F103 (lo mismo es cierto para otras series):

inserte la descripción de la imagen aquí

Entonces, ¿qué significa ser dos tamaños? Cómo entender estas dos memorias, tenemos que partir de qué es Flash y qué es RAM.

1. Conceptos básicos de FLASH y RAM

Primero mira una imagen:

inserte la descripción de la imagen aquí

1.1 ¿Qué es FLASH?

De la figura anterior, podemos saber que FLASH pertenece a la memoria no volátil:

Para expandir, FLASH, también conocida como memoria flash, no solo tiene el rendimiento de borrable y programable electrónicamente (EEPROM), sino que también puede leer datos rápidamente sin pérdida de energía y pérdida de datos.Este tipo de memoria se usa en U disk y MP3. En los chips incrustados anteriores, el dispositivo de almacenamiento siempre ha usado ROM (EPROM), con el avance de la tecnología, ahora incrustado es básicamente FLASH, se usa para almacenar el cargador de arranque y el sistema operativo o el código del programa o se usa directamente como un disco duro (disco U) .

Luego, hay dos tipos principales de Flash, NOR Flash y NADN Flash. (Para la diferencia entre los dos, las siguientes palabras son de referencia, porque estas introducciones se basan en tecnologías de años anteriores)

La lectura de NOR Flash es la misma que la lectura de nuestra SDRAM común. Los usuarios pueden ejecutar directamente el código cargado en NOR FLASH, lo que puede reducir la capacidad de SRAM y ahorrar costos.

NAND Flash no adopta la tecnología de lectura aleatoria de la memoria. Su lectura se lleva a cabo en forma de lectura de un bloque a la vez, generalmente 512 bytes a la vez. El uso de Flash con esta tecnología es relativamente económico. Los usuarios no pueden ejecutar directamente el código en NAND Flash, por lo que muchas placas de desarrollo que usan NAND Flash usan un pequeño NOR Flash para ejecutar el código de inicio además de usar NAND Flash.

El FLASH dentro del microcontrolador STM32 es NOR FLASH. Flash tiene una capacidad relativamente grande y los datos no se pierden cuando se pierde la energía. Se utiliza principalmente para almacenar códigos y algunos datos de usuario que no se pierden cuando se pierde la energía.

1.2 ¿Qué es la RAM?

RAM es memoria volátil:

RAM随机存储器(Random Access Memory)表示既可以从中读取数据,也可以写入数据。当机器电源关闭时,存于其中的数据就会丢失。比如电脑的内存条。

RAM有两大类,一种称为静态RAM(Static RAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。

DRAM分为很多种,常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,这里介绍其中的一种DDR RAM。

DDR RAM(Date-Rate RAM)也称作DDR SDRAM,这种改进型的RAM和SDRAM是基本一样的,不同之处在于它可以在一个时钟读写两次数据,这样就使得数据传输速度加倍了。这是目前电脑中用得最多的内存,而且它有着成本优势,事实上击败了Intel的另外一种内存标准-Rambus DRAM。在很多高端的显卡上,也配备了高速DDR RAM来提高带宽,这可以大幅度提高3D加速卡的像素渲染能力。

为什么需要RAM,因为相对FlASH而言,RAM的速度快很多,所有数据在FLASH里面读取太慢了,为了加快速度,就把一些需要和CPU交换的数据读到RAM里来执行(注意这里不是全部数据,只是一部分需要的数据,这个在后面介绍STM32的内存管理中会提到)。

STM32单片机内部的 RAM 为 SRAM。 RAM相对容量小,速度快,掉电数据丢失,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。

二、STM32的内存架构

2.1 Cortex-M3的存储器映射分析

在《ARM Cotrex-M3权威指南》中有关 M3的存储器映射表:

inserte la descripción de la imagen aquí

存储器映射 是用 地址来表示 对象,因为Cortex-M3是32位的单片机,因此其PC指针可以指向2^32=4G的地址空间,也就是图中的 0x00000000到0xFFFFFFFF的区间,也就是将程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内,数据字节以小端格式存放在存储器中。

2.2 STM32 的存储器映射分析

STM32存储器映射表(选用的是STM32F103VE的,不同的型号Flash 和 SRAM 的地址空间不同,起始地址都是一样的):

inserte la descripción de la imagen aquí

那么我们所需要分析的STM32 内存,就是图中 0X0800 0000开始的 Flash 部分 和 0x2000 0000 开始的SRAM部分,这里还要介绍一个和Flash模块相关的部分:

inserte la descripción de la imagen aquí

2.3 STM32的 Flash 组织

inserte la descripción de la imagen aquí

STM32的Flash,严格说,应该是Flash模块。该Flash模块包括:Flash主存储区(Main memory)、Flash信息区(Informationblock),以及Flash存储接口寄存器区(Flash memory interface)。

主存储器,该部分用来存放代码和数据常数(如加const类型的数据)。对于大容量产品,其被划分为256页,每页2K,小容量和中容量产品则每页只有1K字节。主存储起的起始地址为0X08000000,B0、B1都接GND的时候,就从0X08000000开始运行代码。

信息块,该部分分为2个部分,其中启动程序代码,是用来存储ST自带的启动程序,用于下载,当B0接3.3V,B1接GND时,运行的就这部分代码,用户选择字节,则一般用于配置保护等功能。

闪存储器块,该部分用于控制闪存储器读取等,是整个闪存储器的控制机构。

对于主存储器和信息块的写入有内嵌的闪存编程管理;编程与擦除的高压由内部产生。

在执行闪存写操作时,任何对闪存的读操作都会锁定总线,在写完成后才能正确进行,在进行读取或擦除操作时,不能进行代码或者数据的读取操作。

三、STM32 的内存管理

STM32 的内存管理起始就是对 0X0800 0000 开始的 Flash 部分 和 0x2000 0000 开始的 SRAM 部分使用管理

3.1 C/C++ 程序编译后的存储数据段

在了解如何使用内存管理之前,先得理解一下 6 个储存数据段 和 3种存储属性区 的概念:

inserte la descripción de la imagen aquí

.data

数据段,储存已初始化且不为0的全局变量和静态变量(全局静态变量和局部静态变量)。

static声明的变量放在data段。

数据段属于静态内存分配,所以放在RAM里,准确来说,是在程序运行的时候需要在RAM中运行。

.BSS

Block Started by Symbol。储存未初始化的,或初始化为0的全局变量和静态变量。

BSS段属于静态内存分配,所以放在RAM里。

.text(CodeSegment/Text Segment)

代码段,储存程序代码。也就是存放CPU执行的机器指令(machineinstructions)。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

放在Flash里。

.constdata

储存只读常量。const修饰的常量,不管是在局部还是全局

放在Flash 里。

所以为了节省 RAM,把常量的字符串,数据等 用const声明

heap(堆)

堆是用于存放进程运行中被动态分配的内存段。他的大小并不固定,可动态扩张或者缩减,由程序员使用malloc()和free()函数进行分配和释放。当调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

放在RAM里

其可用大小定义在启动文件startup_stm32fxx.s中。

stack(栈)

栈又称堆栈,是用户存放程序临时创建的局部变量,由系统自动分配和释放。可存放局部变量、函数的参数和返回值(但不包括static声明的变量,static意味着 放在 data 数据段中)。 除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。

???由于栈的先进先出(FIFO)特点???上面这句话正确吗?Cortex-M3/M4的堆栈是向下生长,第一个入栈的元素应该是最后一个才能出来??

所以栈特别方便用来保存/恢复调用现场。

从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

放在RAM里

其大小定义在启动文件startup_stm32fxx.s中。

3.2 STM32 程序编译后的内存占用情况

3.2.1 MDK 编译

MDK编译后的结果:

inserte la descripción de la imagen aquí

Code:

程序代码部分。

.text 段

放在ROM里面,就是Flash,需要占用flash空间

RO-data

(Read Only)只读数据

程序定义的常量,只读数据,字符串常量(const修饰的)

.constdata 段

放在flash里面,需要占用flash空间

RW-data

(Read Write)可读可写数据

已经初始化的全局变量和静态变量(就是static修饰的变量);

.data 段

需要在 RAM里面运行,但是起初需要保存在 Flash里面,程序运行后复制到 RAM里面运行,需要占用Flash空间

ZI-data

(Zero Initialize)未初始化的全局变量和静态变量,以及初始化为0的变量;

.BSS段

ZI的数据全部是0,没必要开始就包含,只要程序运行之前将ZI数据所在的区域(RAM里面)一律清 0,不占用Flash,运行时候占用RAM.

heap 和 stack 其实也属于 ZI,只不过他不是程序编译就能确定大小的,必须在运行中才会有大小,而是是变化的

因为RAM掉电丢失,所以 RW-data 数据也得下载到ROM(flash) 中,在运行的时候复制到 RAM中运行,如下图所示(图中的地址也是错的,应该是从0x0800 0000 开始):

inserte la descripción de la imagen aquí

由上我们得知:

程序占用 Flash = Code + RO data + RW data

程序运行时候占用 RAM = RW data + ZI data

Code + RO data + RW data 的大小也是生成的 bin 文件的大小

3.2.1 GCC 编译

GCC编译结果:

inserte la descripción de la imagen aquí

GCC编译, 图中红色的部分是占用 Flash 的大小:Flash = text + data

蓝色部分是运行时候占用 RAM大小:RAM = data + bss

3.3 STM32 程序的内存分配

我们前面说到的 stack(栈) 和 heap(堆),程序编译完成以后并不能知道运行时候实际占用RAM 大小。

但是我们可以知道的是 stack(栈) 和 heap(堆)的起始地址,和能够使用的最大空间,我们先看能够使用的空间大小。

3.3.1 MDK 环境

MDK是在 startup_stm32fxxx.s 中定义的:

inserte la descripción de la imagen aquí

在 startup_stm32fxxx.s 中我们可以看到关于 Stack_Size 和 Heap_Size的定义,图中的定义就是规定本程序中 栈 的大小为 1K, 堆的大小为 0.5K。

startup_stm32fxxx.s文件是系统的启动文件,startup_stm32fxxx.s主要完成三个工作:栈和堆的初始化、定位中断向量表、调用Reset Handler。

(关于STM32的启动文件,我们有单独的一篇文章,请查看另一篇博文 STM32的启动过程(startup_xxxx.s文件解析)

我们在生成的.map文件可以看到 HEAP 和 STACK的起始地址(不懂的可以先看博文下面一节的内容——四、MDK生成的.map文件简析),我们要注意的是:

堆使用时候从起始地址开始,往上加

栈使用时候从结束地址,就是__initial_sp(栈顶指针的地址)开始,往下减

他们的空间大小定义好了,如果入栈元素过大,使得元素减到了堆的地址范围,就是栈溢出,这会导致改变堆中相应地址元素的值。 同样的,当申请的动态内存过大,使得堆的变量加到了栈的地址范围,就是堆溢出。

在实际项目中,如果程序复杂,中断嵌套太多,栈需要多设置一点空间 如果你使用裸机程序,从来不使用标准库的malloc,heap可以没有,因为永远不会用到,还一直占用着一片RAM区。

所以在我们知道了以上的知识后,我们可以按照自己的工程需求,定义Stack_Size 和 Heap_Size。

3.3.2 GCC 环境

如果是在GCC编译器下面,关于 Stack_Size 和 Heap_Size的定义如下图:

inserte la descripción de la imagen aquí

四、MDK生成的.map文件简析

为了更深层次的理解上述内容,我们还有必要分析一下MDK生成的 .map 文件, 那么既然要分析,除了我们关注的flash 和 ram部分的内容,其他的地方也稍微做一下笔记:

4.1 Section Cross References

主要是不同文件中函数的调用关系:

inserte la descripción de la imagen aquí

我们查看一下 clock.c 文件下的 rt_tick_increase函数:

inserte la descripción de la imagen aquí

4.2 Removing Unused input sections from the image

被删除的冗余函数:

inserte la descripción de la imagen aquí

删除函数功能在MDK的配置中可以设置,勾选以后删除得多,不勾选删除得少,如下图:

inserte la descripción de la imagen aquí

在 Removing Unused input sections from the image 的最后会列出删除的冗余函数的大小,如果在MDK上改变上图所示的配置,下图中的删除总代码可以看到变化:

inserte la descripción de la imagen aquí

4.3 Image Symbol Table

4.3.1 Local Symbols

局部标号,

用Static声明的全局变量地址和大小,

C文件中函数的地址和用static声明的函数代码大小,

汇编文件中的标号地址(作用域限本文件):

inserte la descripción de la imagen aquí

我们找个占用内存地址的地方看一下:

inserte la descripción de la imagen aquí

我们在 startup_stm32f103xg.s ,可以看到RESET函数:

inserte la descripción de la imagen aquí

我们继续往下看:

inserte la descripción de la imagen aquí

上图中的 i.RCC_Delay 下面跟了一个 RCC_Delay,说明这个函数是用static修饰的,我们找到 stm32f1xx_hal_rcc.c下的RCC_Delay 如图:

inserte la descripción de la imagen aquí

我们接着看到 SRAM区:

inserte la descripción de la imagen aquí

我们继续看到后面的.bss段,包括了 HEAP 和 STACK区域:

inserte la descripción de la imagen aquí 通过上图中我们可以看到 HEAP 的起始地址为 0x20002338,

ram从 0x2000 0000开始存放的依次为 .data、.bss、HEAP、STACK。

HEAP在 startup_stm32fxxx.s 中定义过大小为 0x200,

所以结束地址为0x20002538, HEAP 是和 STACK连接在一起的,所以STACK的起始地址为 0x20002538,大小 0X400,结束地址为 0x20002938。

最后我们可以看到 __initial_sp 指向的是 0x20002938,入栈从高地址开始入栈,地址越来越小。

4.3.2 Global Symbols

全局标号,

全局变量的地址和大小,

C文件中函数的地址及其代码大小,

汇编文件中的标号地址(作用域全工程):

inserte la descripción de la imagen aquí

我们找到Flash地址开始的部分:

inserte la descripción de la imagen aquí

在 startup_stm32fxxx.s 能看到对应的部分:

inserte la descripción de la imagen aquí

看到最后的 RAM区,注意下图中标出的两行的功能:

inserte la descripción de la imagen aquí

4.4 Memory Map of the image

映像文件可以分为加载域(Load Region)和运行域(Execution Region):加载域反映了ARM可执行映像文件各个段存放在存储器中时的位置关系。

inserte la descripción de la imagen aquí

其中还能看出来 Flash中存放的都是 code,和 RO_Data:

inserte la descripción de la imagen aquí

我们看看SRAM部分:

inserte la descripción de la imagen aquí

需要注意一下,我们前面代码he 数据部分都是4字节对齐,PAD一般都是补充2个字节,到了栈部分,需要8字节对齐:

inserte la descripción de la imagen aquí

4.5 Image component sizes

存储组成大小

这部分的内容就比较直观

inserte la descripción de la imagen aquí

最后就是我们熟悉的部分:

inserte la descripción de la imagen aquí

五、一些相关的补充内容

5.1 STM32 的启动方式

BOOT0 BOOT1 启动模式
0 X User Flash memory(从闪存存储器启动)
1 0 System memory(从系统存储器启动)
1 1 Embedded SRAM(从内嵌SRAM启动)

第一种启动方式是最常用的用户FLASH启动,正常工作就在这种模式下,STM32的FLASH可以擦出10万次,所以不用担心芯片哪天会被擦爆!

第二种启动方式是系统存储器启动方式,即我们常说的串口下载方式(ISP),不建议使用这种,速度比较慢。STM32 中自带的BootLoader就是在这种启动方式中,如果出现程序硬件错误的话可以切换BOOT0/1到该模式下重新烧写Flash即可恢复正常。

第三种启动方式是STM32内嵌的SRAM启动。该模式用于调试。 用jlink在线仿真,则是下载到SRAM中。

以上三种启动方式我们都很熟悉,但是他的究竟是如何实现的呢?

我们先来看看《Cortex-M3权威指南》关于CM3复位后的动作:

inserte la descripción de la imagen aquí

当选择相应的启动方式时,对应的存储器空间被映射到启动空间(0x00000000)

从闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000) ,也就是0x08000000被映射到0x00000000。

从内嵌SRAM启动 :SRAM起始地址 0x2000 0000 被映射到0x00000000。

从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),也就是0x1FFF F000被映射到0x00000000。

(为什么是0x1FFF F000 可以查阅上文中的 2.2小节 STM32 的存储器映射分析,STM32互联型产品这个地址不一样,此地址由ST官方写入了一段BootLoader代码,可以通过官方BootLoader升级MCU固件,无法修改)。

5.2 STM32启动地址和 Bootloader 说明

通过上面的内容,我们现在知道STM32 从Flash程序启动以后会从 0X08000000 开始运行,那么他这个地址是否可以修改,答案是当然的!但是单独的改他的启动地址,没有任何意义,一般都是需要使用到 Bootloader 才会使得应用程序的地址发生变化。

在 0x1FFF F000 这个地址上官方写入了一段 BootLoader 用户使用,我们也可以自己写一段 BootLoader 程序方便自己使用,因为是自己写的,他还是用户程序,只是我们自己把程序分成了 BootLoader部分和 应用程序部分,大概的意思如下图所示: inserte la descripción de la imagen aquí

为什么要使用用户 BootLoader

在有些项目中,可能因为某些原因需要经常更换 程序,如果每次都是重新烧录,特别的麻烦,那么我们就可以自己设计一个 BootLoader,通过 SD卡进行升级:

上电后先运行 BootLoader,BootLoader主要工作是检测是否有SD卡,SD卡中是否有需要的BIn文件, 如果检测到就将其复制到 应用程序区域 使得程序得以更新,更新结束以后跳转到应用程序执行; 如果没检测到相应的SD卡,就说明程序不需要更新,也跳转到应用程序执行;

以上主要是说明使用 BootLoader 的思路与适用场合,至于具体的实现其实网上有很多教程,如果有机会我也会补充或者单独写一篇文章总结一下

(说明:下面是零碎的笔记,还没整理完,仅供参考 stm32 FLASH的起始地址是0x08000000,当然也可以自定义起始地址,不过记得在main函数中定义变量后加一句SCB->VTOR=FLASH_BASE | OFFSET;OFFSET是想要偏移的量,可宏定义或直接0xXX。 当然也可以调用库函数 NVIC_SetVectorTable()进行偏移,效果一样。IAP升级这样用的多)

5.3 单片机和 x86cpu运行程序的不同

x86的pc机cpu在运行的时候程序是存储在RAM中的,而单片机等嵌入式系统则是存于flash中

x86cpu和单片机读取程序的具体途径:

pc机在运行程序的时候将程序从外存(硬盘)中,调入到RAM中运行,cpu从RAM中读取程序和数据

而单片机的程序则是固化在flash中,cpu运行时直接从flash中读取程序,从RAM中读取数据

原因分析 :

x86构架的cpu是基于冯.诺依曼体系的,即数据和程序存储在一起,而且pc机的RAM资源相当丰富,从几十M到几百M甚至是几个G,客观上能够承受大量的程序数据。

单片机的构架大多是哈弗体系的,即程序和数据分开存储,而且单片的片内RAM资源是相当有限的,内部的RAM过大会带来成本的大幅度提高。

冯.诺依曼体系与哈佛体系的区别:

二者的区别就是程序空间和数据空间是否是一体的。 早期的微处理器大多采用冯诺依曼结构,典型代表是Intel公司的X86微处理器。取指令和取操作数都在同一总线上,通过分时复用的方式进行的。缺点是在高速运行时,不能达到同时取指令和取操作数,从而形成了传输过程的瓶颈。

哈佛总线技术应用是以DSP和ARM为代表的。采用哈佛总线体系结构的芯片内部程序空间和数据空间是分开的,这就允许同时取指令和取操作数,从而大大提高了运算能力。

5.4 ARM 和 x86cpu 指令集RISC和CISC说明

此部分是学习了韦东山老师的视频后来记录的,原视频可以在韦东山老师官网里面找到:

ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点: ① 对内存只有读、写指令 ② 对于数据的运算是在CPU内部实现 ③ 使用RISC指令的CPU复杂度小一点,易于设计

inserte la descripción de la imagen aquí 在ARM架构中,对于乘法运算a = a * b, 在RISC中要使用4条汇编指令: ① 读内存a ② 读内存b ③ 计算a*b ④ 把结果写入内存

x86属于复杂指令集计算机(CISC:Complex Instruction Set Computing),

它所用的指令比较复杂,比如某些复杂的指令,它是通过“微程序”来实现的。

inserte la descripción de la imagen aquí

比如执行乘法指令时,实际上会去执行一个“微程序”, 一样是去执行这4步操作: ① 读内存a ② 读内存b ③ 计算a*b ④ 把结果写入内存

RISC和CISC的区别:

  • CISC的指令能力强,单多数指令使用率低却增加了CPU的复杂度,指令是可变长格式;
  • RISC的指令大部分为单周期指令,指令长度固定,操作寄存器,对于内存只有Load/Store操作
  • CISC支持多种寻址方式;RISC支持的寻址方式
  • CISC通过微程序控制技术实现;
  • RISC增加了通用寄存器,硬布线逻辑控制为主,采用流水线
  • CISC的研制周期长
  • RISC优化编译,有效支持高级语言

六、 GCC下生成的.map文件

Conocemos el archivo .map bajo MDK, y el archivo .map bajo GCC básicamente se puede entender.Aquí, agregue la parte sobre la RAM del archivo .map bajo GCC:

6.1 Sección RAM

La parte RAM comienza en 0x2000 0000 y la parte .data se almacena primero:

inserte la descripción de la imagen aquí

A continuación está la sección .bss:

inserte la descripción de la imagen aquí

Si se usa FreeRTOS, habrá datos sobre la sección FreeRTOS después de la sección .bbs:

inserte la descripción de la imagen aquí

Finalmente vino a amontonar y apilar:

inserte la descripción de la imagen aquí

おすすめ

転載: juejin.im/post/7139348620427411492