赞
踩
内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。
不管什么样的操作系统、什么样的计算机架构,进程使用的内存都可以按照功能大致分成以下4个部分。
(1)代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指令并执行。
(2)数据区:用于存储全局变量等。
(3)堆区:进程可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。动态分配内存和回收内存是堆区的特点。
(4)栈区:用于动态地存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数中继续执行。
在Windows平台下,高级语言写出的程序经过编译链接,最终会变成所谓的PE文件。当PE文件被装载运行后,就成了所谓的进程。
STM32内存分为3块区域:全局/静态变量区、栈区、堆区。
内存首先存放/开辟全局变量区域,然后开辟栈区最后开辟堆区。
stm32F407存储器映像图:
STM32的内存(存储器)的地址空间大小为4G(0x0000 0000 ~ 0xFFFF FFFF),被分为8个block(block0~block7),每个block为512Mbyte
(出自STM32F407ZGT6数据手册P61)
堆栈是由栈(Stack)和堆(Heap)组成的,汇编中应用的 PUSH 和 POP 就是对 栈(Stack)的操作,其按照后进先出(LIFO-Last In First Out)的原理运作。
堆栈是一个特定的存储区或者寄存器。一般在内存总开辟一块区域作为堆栈,叫做软件堆栈;用寄存器构成的堆栈,叫硬件堆栈。大多数情况下,我们使用的都是软件堆栈。
普通单片机启动时,不需要用bootloader将数据 从ROM搬移到RAM。 但是STM32单片机需要。
计算机系统的程序在存储状态时位于硬盘,执行的时候甚至会把上述的 RO 区域 (代码、只读数据) 加载到内存,加快运行速度,还有虚拟内存管理单元 (MMU) 辅助加载数据,使得可以运行比物理内存还大的应用程序。
32启动文件中的堆栈就是软件堆栈。
在mcu中,heap和stack的使用者是不同的。
stack(栈):由系统自动分配释放,存放的函数的参数值,局部变量的值。这个空间用户操作不了的。
heap(堆):由用户分配及释放,调用malloc 和free时操作的空间就是堆空间。
栈是从高到低分配,堆是从低到高分配。
栈区的首地址是编译器自动分配的,栈首地址=全局区域大小+栈大小(Stack_Size)。
程序中如果没有动态申请内存则堆区无用,可以不用管 Heap_Size
main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10); 堆
p2 = (char *)malloc(20); 堆
}
在keil编译后的控制台信息中:
从编译信息可以看出
我们的代码占用 FLASH 大小为:1892 字节(1556+336)
所用的 SRAM 大小为:1864 个字节(32+1832)
最终,烧写时flash被占有的空间为:
falsh = Code + RO-Data + RW-Data
如果这些内容比 RT1052 芯片的 FLASH 空间大,程序就无法被正常保存了。
程序运行时ram被占有的空间为
ram = RW-Data + ZI-Data
STM32 的 RO 区域不需要加载到 SRAM,内核直接从 FLASH 读取指令运行。
是否需要掉电保存,是把 RW-data 与 ZI-data 区别开来的原因
启动文件中的stack_size是可以设定的。stm32的堆、栈设置可以在底层驱动文件startup_stm32f10x_hd.s(这个文件要看你所用MCU的型号,这个是大容量的)里设置。
flash占用空间可以在最终生成的bin文件查看,刚好是13116字节大小。
ram占用空间在生成的.map文件中。
map文件中显示ram占用的size为0x19c0,转换为10进制为6592,而生成RW-Data + ZI-Data=180+6412=6592.
通过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个字节(它根据堆栈大小,由编译器自动生成)
对于如下的空间占用
调试时会出现
MSP就是主堆栈指针,一般是我们复位之后指向的位置,复位执向的其实是栈顶。
MSP指向地址0x20000668是0x20000000偏移0x668而得来。具体哪些地方占用了RAM,可以参看map文件中:
栈顶长这样:
MSP(R13,主堆栈指针)和PC(R15,程序计数器)初始化流程图:
栈是一块连续的内存空间,由上往下增长,指针指向最下边的地址,即使用栈时地址是会越来越小的,如先声明的局部变量比后声明的地址要高;
压栈(PUSH): SP 先自减 4,再存入新的数值。
先从 SP 指针处读取上一次被压入的值,再把 SP 指针自增 4。
虽然 POP 后被压入的数值还保存在栈中,但它已经无效了,因为为下次的 PUSH 将覆盖它的值!
当处理器处于线程模式时,控制寄存器决定使用的堆栈和软件执行的特权级别,并指示 FPU 状态是否为活动。
在 Cortex‐M4 的 handler 模式中, CONTROL[1](CONTROL寄存器的bit[1])总是 0
当 CONTROL[1]=0 时,只使用 MSP,此时用户程序和异常 handler 共享同一个堆栈。这也是复位后的缺省使用方式。
当 CONTROL[1]=1 时,线程模式将不再使用 PSP,而改用 MSP( handler 模式永远使用 MSP)。
注意,在这种情况下,进入异常时的自动压栈使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换回 PSP,并且从进程堆栈上弹出数据。
在特权级下,可以直接对 MSP 和 PSP 执行读/写操作,而不会混淆你所引用的R13。
栈是固定且连续的一个大小,如果使用局部变量等超出了栈的大小则会造成内存溢出,而编译器通常是发现不了的,只有当程序运行到那个函数时才会发生的。这就会引入很难查找的bug。
当指针 p 调用 malloc 申请内存的时候,申请到的堆空间是不连续的,由于RAM中还存在局部变量,代码段和栈等等,所以动态分配的内存是取暂时空闲的内存而不是预先划出一块区域,这就是动态分配内存的好处。具体执行如下:
堆的增长方向是向上,所以malloc申请的地址也是越来越大的,前提是连续申请且在最后一次申请后再释放内存(free)。
代码:
char * Onebyte;//声明一个char指针
Onebyte = (char*)malloc(sizeof(Onebyte));//向堆中申请一个字节内存
//下面两句效果一样,若在函数里
char Onearr[10];//向栈中申请10个字节
char *Onearr = (char*)malloc(10);//向堆中申请10个字节
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。
代码
//对于上面malloc的使用
free(Onebyte);
free(Onearr);
由于使用malloc申请内存时,不单只申请了所需的大小空间,还要额外暂用管理这部分空间的内存,而释放时又只释放申请的内存,所以使用堆会引入内存碎片。
分块式内存管理由内存池和内存管理表两部分组成。
内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用。当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。
比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。
内寸分配方向如图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。