基于FreeRTOS的STM32F103系统—Heap_4内存管理机制介绍

1
heap_4内存管理机制详解
首先介绍一下用到的重要的结构体-标记内存块,在每个存放数据的内存块前都会有一个这样的标记结构体。
typedef struct a_block_link{ struct a_block_link *pxnextfreeblock; /*< < the next free block in the list. */ size_t xblocksize; /*< xblocksize = 0; //末尾的内存块大小初始化为0 pxend- >pxnextfreeblock = null; //下一个指向null在申请内存的最开始,把整个内存堆都看成一个整体,作为一个大内存块,这个内存块之前也需要有一个标记结构体,也就是pxfirstfreeblock结构体,这里对这个结构体进行初始化,它的首地址就是字节对齐后的地址,大小是尾地址uxaddess-内存块字节对齐后的首地址,下一个内存块指向pxend。
/*开始的时候将内存堆整个可用空间看成一个空闲内存块*/ pxfirstfreeblock = ( void * ) pucalignedheap; //空闲内存块之前的标记结构体地址 pxfirstfreeblock- >xblocksize = uxaddress - ( size_t ) pxfirstfreeblock; //标记结构体记录内存块大小为末地址-初地址 pxfirstfreeblock- >pxnextfreeblock = pxend; //下一个空闲内存块为末尾内存块指针最后这里就是更新一下全局变量,并标记一下块被占用。
/*只有一个内存块,而且这个内存块拥有内存堆的整个可用空间*/ xminimumeverfreebytesremaining = pxfirstfreeblock- >xblocksize; //记录最小的空闲内存块大小 xfreebytesremaining = pxfirstfreeblock- >xblocksize; //记录历史最小的空闲内存块大小 /* work out the position of the top bit in a size_t variable. */ xblockallocatedbit = ( ( size_t ) 1 ) < pxnextfreeblock pxnextfreeblock pxnextfreeblock ) { /* nothing to do here, just iterate to the right position. */ }这里是判断要插入的这块内存和前一块内存是否相邻,如果相邻就合并成一块,判断是否相邻的条件是puc + pxiterator->xblocksize ) == ( uint8_t * ) pxblocktoinsert,插入点地址+这块内存的大小==要插入块首地址;即上一块末地址==要插入块起始地址
//插入内存块,如果要插入的内存块可以和前一个内存块合并的话就 //合并两个内存块 puc = ( uint8_t * ) pxiterator; //找到的合适的插入点的地址 if( ( puc + pxiterator- >xblocksize ) == ( uint8_t * ) pxblocktoinsert ) //插入点地址+这块内存的大小==要插入块首地址;即上一块末地址==要插入块起始地址 { pxiterator- >xblocksize += pxblocktoinsert- >xblocksize; //大小合并 pxblocktoinsert = pxiterator; //合并后内存首地址不变 } else { mtcoverage_test_marker(); }再借用一下原子的图:
这一部分代码是检查要插入的内存块是否和后一块内存相邻,如果相邻就合并起来,判断条件是puc + pxblocktoinsert->xblocksize == ( uint8_t * ) ( pxiterator->pxnextfreeblock ),要插入块首地址+这块内存的大小==下一块首地址;即要插入块末地址==下一块起始地址
//检查是否可以和后面的内存块合并,可以的话就合并 puc = ( uint8_t * ) pxblocktoinsert; //要插入的内存块的首地址 if( ( puc + pxblocktoinsert- >xblocksize ) == ( uint8_t * ) pxiterator- >pxnextfreeblock ) 要插入块首地址+这块内存的大小==下一块首地址;即要插入块末地址==下一块起始地址 { if( pxiterator- >pxnextfreeblock != pxend ) //下一块不是表尾 { /* form one big block from the two blocks. */ //将两个内存块组合成一个大的内存块时 pxblocktoinsert- >xblocksize += pxiterator- >pxnextfreeblock- >xblocksize; 内存块大小合并 pxblocktoinsert- >pxnextfreeblock = pxiterator- >pxnextfreeblock- >pxnextfreeblock;//合并起来之后下下快变成了下一块 } else { pxblocktoinsert- >pxnextfreeblock = pxend; //要插入的变成表尾 } } else { pxblocktoinsert- >pxnextfreeblock = pxiterator- >pxnextfreeblock; }最后借用一下原子的图:
如果和前后都不相邻,则使用最简单的插入方法:
//在内存块插入的过程中没有进行过一次内存合并,使用最简单的插入方法 if( pxiterator != pxblocktoinsert ) { pxiterator- >pxnextfreeblock = pxblocktoinsert; } else { mtcoverage_test_marker(); }4
内存申请函数
先初始化一下内存堆:
//第一次调用,初始化内存堆 if( pxend == null ) { prvheapinit(); } else { mtcoverage_test_marker(); }判断一下想要插入数据的内存块是否被使用,就是和xblockallocatebit变量做一次与运算,如果结果不是1,则说明没被使用;在确保要插入的大小大于0之后,需要附加上标记结构体的大小(8字节)后,再进行字节对齐。
//需要申请的内存块大小的最高位不能为 1,因为最高位用来表示内存块有没有被使用 if( ( xwantedsize & xblockallocatedbit ) == 0 ) { /* the wanted size is increased so it can contain a blocklink_t structure in addition to the requested amount of bytes. */ if( xwantedsize > 0 ) { xwantedsize += xheapstructsize; //要申请的大小加上标记结构体的大小 /* ensure that blocks are always aligned to the required number of bytes. */ if( ( xwantedsize & portbyte_alignment_mask ) != 0x00 ) { /* byte alignment required. */ /*要插入的内存块字节对齐*/ xwantedsize += ( portbyte_alignment - ( xwantedsize & portbyte_alignment_mask ) ); configassert( ( xwantedsize & portbyte_alignment_mask ) == 0 ); } else { mtcoverage_test_marker(); } } else { mtcoverage_test_marker(); }当我们想要插入的内存块小于剩余内存大小时,就开始查找满足要求的内存块。
if( ( xwantedsize > 0 ) && ( xwantedsize xblocksize pxnextfreeblock != null ) ) { pxpreviousblock = pxblock; pxblock = pxblock- >pxnextfreeblock; }如果找到的是pxend表示没有内存可以分配,否则就将内存首地址保存在 pvreturn 中,函数返回的时候返回此值,然后将这块内存从空闲内存表中删除
//如果找到的内存块是 pxend 的话就表示没有内存可以分配 if( pxblock != pxend ) { /* return the memory space pointed to - jumping over the blocklink_t structure at its start. */ //找到内存块以后就将内存首地址保存在 pvreturn 中,函数返回的时候返回此值 //找到的内存块让出一个标志结构体的大小 pvreturn = ( void * ) ( ( ( uint8_t * ) pxpreviousblock- >pxnextfreeblock ) + xheapstructsize ); /* this block is being returned for use so must be taken out of the list of free blocks. */ //将申请到的内存块从空闲内存链表中移除 pxpreviousblock- >pxnextfreeblock = pxblock- >pxnextfreeblock; //把满足要求的pxblock块的下一块拼接到上一块申请的内存大小小于空闲的一大块内存的大小,则将其分割,剩下的留着,相当于给空闲内存块的首地址做一个地址偏移:新的空闲内存块=满足要求的内存块首地址+需要的内存块首地址,然后更新新的空闲内存块的大小,并将其插入到空闲内存表。
//如果申请到的内存块大于所需的内存,就将其分成两块 if( ( pxblock- >xblocksize - xwantedsize ) > heapminimum_block_size ) { /* this block is to be split into two. create a new block following the number of bytes requested. the void cast is used to prevent byte alignment warnings from the compiler. */ pxnewblocklink = ( void * ) ( ( ( uint8_t * ) pxblock ) + xwantedsize ); //新的空闲内存块=满足要求的内存块首地址+需要的内存块首地址 configassert( ( ( ( size_t ) pxnewblocklink ) & portbyte_alignment_mask ) == 0 ); /* calculate the sizes of two blocks split from the single block. */ pxnewblocklink- >xblocksize = pxblock- >xblocksize - xwantedsize; //更新新的空闲内存块的大小 pxblock- >xblocksize = xwantedsize; //满足要求的内存块的大小 /* insert the new block into the list of free blocks. */ prvinsertblockintofreelist( pxnewblocklink ); //插入新的空闲内存块 } else { mtcoverage_test_marker(); }最后就是更新一下全局变量
xfreebytesremaining -= pxblock- >xblocksize; //更新最小内存块大小 if( xfreebytesremaining xblocksize |= xblockallocatedbit; //将pxblock最高位置1 pxblock- >pxnextfreeblock = null; //满足要求的内存块下一块指向null后面还可以配置内存申请失败时的钩子函数,需要把configuse_malloc_failed_hook宏打开
#if( configuse_malloc_failed_hook == 1 ) { if( pvreturn == null ) { extern void vapplicationmallocfailedhook( void ); vapplicationmallocfailedhook(); } else { mtcoverage_test_marker(); } } #endif5
内存释放函数
先定义一些用到的局部变量:
uint8_t *puc = ( uint8_t * ) pv; //传入要释放内存的地址blocklink_t *pxlink; //包含了标志结构体后的首地址传入的数据地址没包含标志结构体,需要先做减法,进行地址移位,然后将包含了标志结构体的首地址保存在pxlink中
puc -= xheapstructsize; //释放的部分包括上标志结构体大小/* this casting is to keep the compiler from issuing warnings. */pxlink = ( void * ) puc; //防止编译器报错如果要释放的内存真的被使用,就开始释放操作,先把首位变0,表示变成空闲,然后更新空闲内存大小,将这块内存插入回空闲内存表中,要注意:释放和申请内存,并不是把这块内存从一个链表中拿出来了,只是做了一些标记,让程序知道这部分被占用,有数据,在释放内存之前我们将数据删除,然后把标志位改为空闲状态就行,这就是释放的本质。
if( ( pxlink- >xblocksize & xblockallocatedbit ) != 0 ) //判断是否真被使用 { if( pxlink- >pxnextfreeblock == null ) { /* the block is being returned to the heap - it is no longer allocated. */ pxlink- >xblocksize &= ~xblockallocatedbit; //首位变0,表示未被使用 vtasksuspendall(); { /* add this block to the list of free blocks. */ /*将内存块插到空闲内存链表中*/ xfreebytesremaining += pxlink- >xblocksize; //更新最小内存块大小 tracefree( pv, pxlink- >xblocksize ); prvinsertblockintofreelist( ( ( blocklink_t * ) pxlink ) ); //将被释放的内存块插入空闲内存链表中 } ( void ) xtaskresumeall(); } else { mtcoverage_test_marker(); } } else { mtcoverage_test_marker(); }6
总结
其他的函数主要就是直接返回我们之前更新的全局变量,终于把基础知识都铺垫完了,下面结合具体项目程序谈谈怎么优化了。

越来越慢了,苹果笔记本清理内存怎么清理
四通道多路复用器有助于确保完成多路复用器上电
基于TMS320F2812的高压电机保护装置
基于UDP的简单文件传输协议TFTP设计
户外组网摆脱布线困扰,工业5G网关实现无人值守、远程实时监控
基于FreeRTOS的STM32F103系统—Heap_4内存管理机制介绍
智慧路灯照明系统的应用为智慧城市的发展建设添砖加瓦
安装vocs在线监测系统价格是多少
基于FPGA技术实现数字式解调器的设计
摩托罗拉全新旗舰要来了!Moto官宣:今晚有大事发生 有望今晚与公众见面
3GPP宣布5G Rel-17标准延期半年
简要介绍测量Wi-Fi应用的能耗的方法
高功率密度快充及PD适配器电源结构解析
RF至位解决方案可为材料分析应用提供精密的相位和幅度数据
背投电视的电视制式
亚马逊宣布将从Rivian购买10万辆纯电配送车
RNN存在的问题及其改进方法,并介绍更多复杂的RNN变体
Raychem重型车辆用蓄电池电缆和电力电缆优点
IBM B16光纤交换机ZOON划分原则和含义
多镜头将是未来智能手机的主要战场