1、内存概念
在stm32中,内存分为片内SRAM和Flash、和片外内存。本章主要对片内SRAM内存的管理。
使用STM32F103ZET6
Flash:512K(0x08000000~0x0807FFFF)
SRAM : 64K (0x02000000~0x0200FFFF) (申请的内存在SRAM中)
单片机内部集成了malloc内存申请函数,设计自己的内存管理主要是为了清楚的知道是那一块内存用来做什么,这样在开发过程中,可以非常清楚的知道需要用那一块内存来做什么事情,而不是使用主带的来动态分配。
2. 内存申请原理
在stm32单片机中,申请的内存是在SRAM中。告诉单片机需要一块足够大的内存(这里申请32K)。
红色表示申请内存的起始地址,将32K内存分为一个一个由32字节组成的块。通过创建一个数组来管理这个内存,称这个数组为内存管理表。每一个块对应一个内存管理表,那么内存管理表的大小为:32K/32。
这里将整个32K内存才分为 1024个大小为32字节的块。
(为什么是32字节):这里可以自定义,这个块的大小可以字节定义
图中就有两个东西,一个是内存管理表,一个是32K内存size。
创建两个数组,一个用来存放总的内存(u8 membase[size])、一个用来存内存管理表(u16 memmapbase[1024])
/*
这里的意思是就是,每一个小块(32字节)对于一个内存管理表,比如第一个块对应memmapbase[0]。如有memmapbase[0]!=0 表第一块已经被使用。
这样设计,原本申请32K内存,实际在使用中占用单片机的空间就是:可使用内存(32K)+内存管理表(xx)PS:这意味着 占用内存是32K+内存管理表的内存
*/
在这块区域中,去找是否存在未使用区域,满足申请需求。
先判断传入的申请空间占用多少个32字节,这样可以通过便利内存管理来找到满足要求的块的索引值。
比如:mem_malloc(36) 申请36B的空间,这时候发现,36/32有余,表示需要两块才能完成。这样就去便利memmapbase中连续的两个空内存,拿到其索引值*32也就是申请内存的偏移值
3.内存释放原理
申请完内存,就需要完成对应的释放内存。
如何从内存中(32K)找到之前申请的内存,并将其释放掉
输入一个指针,通过指针判断指针-内存起始地址的大小来确定这个指针是否是指向内存内部的某一区域的指针。
这个偏移值除以块就是其在第几个内存的索引值。拿到内存管理表对应索引值的数就拿到了指针占用了几个块。将其对应的块和内存管理表清空就完成了内存释放。
红色为当前指针的起始地址,黑色为内存的起始地址,(红色-黑色)/32 就表示占用块的索引值。(因为之前申请时候描述了,是4字节对齐,也就是说无论你申请1B还是3B都是让一个块被标记为使用)
通过索引值就可以释放对应内存的数据
4. 代码实现
1.创建内存数组和内存管理表
#define MEM_SIZE 32*1024
#define MEM_BLOCK_SIZE 32
#define MEM_TABLE_SIZE MEM_SIZE/MEM_BLOCK_SIZEstatic __align(4) u8 membase[MEM_SIZE] __attribute__((at(0x20005800)));
u16 memmapbase[MEM_TABLE_SIZE];
2. 初始化mem
清空数组,告诉申请者,内存准备初始化完成,可以申请
memset(membase,0,MEM_SIZE)
memset(memmapbase,0,MEM_TABLE_SIZE)
memrdy = 1;
3.申请内存
遍历整个内存,从中找到未使用的内存块,并且记录其所在的索引值。
for(offset=memtblsize-1;offset >=0; offset--) {
if(!malloc_dev.memmap[offset]) cmemb++;
else cmemb = 0;if(cmemb == nmemb) {
for(i=0;i<nmemb;i++) {
malloc_dev.memmap[offset+i] = nmemb;
}
return (offset * memblksize);
}
}
然后返回内存地址的偏移值,这样就可以通过申请的指针指向内存的首地址,从而向地址写入数据。
4.释放内存
根据申请的指针/MEM_BLOCK_SIZE 就可以知道指针指向的是哪一个块区域。
拿到该区域的内存管理表的数据知道占用了多少个内存块,然后清空对应的块和内存管理表的标志位即可。
MemResult mem_free(u32 offset)
{
int i;
if(!malloc_dev.memrdy) {
malloc_dev.init();
return MEM_ERROR_NOT_INITIALIZED;
}if(offset >= memsize)
return MEM_ERROR_INVALID_POINTER;
else {
int index = offset/memblksize;
int nmemb = malloc_dev.memmap[index];
for(i = 0;i < nmemb;i++) {
malloc_dev.memmap[index+i] = 0;
}
return MEM_SUCCESS;
}
}
5. 效果演示
申请和释放内存
可以看到,从内存中申请了5个内存
内存的地址为:0x20005800~0x2000FFFF
p1起始地址为:2000ffe0 占用32字节
p2起始地址为:2000fbe0 占用1024字节p3起始地址为:2000fb80 占用96字节
p4起始地址为:2000fb60 占用32字节
p5起始地址为:2000fb20 占用64字节
(这里为什么我申请2字节,要占用32字节???因为每一块被定义为32字节,在程序中通过标记每一块是是否不为0表示有无被占用。)
这里是否了p2和p3,申请p6 3字节,起始地址为2000ffc0
从图中,可以看出,p1的起始地址为2000ffe0,那么紧跟的p6地址就应该是2000ffc0
6、问题
从上面来看,申请和释放并没有什么问题,无非就是占用资源不止32K。
但在实际使用中,如果申请的内存是1024释放了然后又重新申请31*32个空间,这里释放的1024就会剩下一块。
这一小块基本处于废弃中。
而且对于申请2字节,却占用了32字节的空间,这样严重浪费了内存资源。加重内存开销。
这样的内存分配对于想要频繁申请和释放内存来说,是不合理的。
(这种方式只适用于知道内存大小,且固定完成一种任务。比如:TCP UDP通信,知道要发送的数据大小,可以采用这种方式来实现,避免系统动态的分配内存)