FreeRTOS内存管理分析
FreeRTOS提供了5个heap.c供用户选择,本文介绍各个实现特点,原理。
注:由markdown转为富文本,空行可能较多。
heap_1.c分析
问题:解决动态内存分配,5个内存管理算法分配的都是内存堆ucHeap[],大小为configTOTAL_HEAP_SIZE;
主要关注为内存分配算法;
不使用glibc的malloc:原因是,单次malloc时间不确定,效率低,代码空间占用大;
共性:内存分配会对齐处理
heap1特性
- 适用资源不释放的情况(任务,信号量,队列等),只申请,不释放,因此不会有内存碎片,大多数RTOS应用就是如此;
- 内存分配时间确定
- 线程安全,加了锁;
接口
与malloc等使用一致;
void * pvPortMalloc( size_t xWantedSize );
实现
- WantedSize长度需要对齐,内存分配的开始地址需要对齐为:pucAlignedHeap
第 1 次分配: pvRet = pucAlignedHeap,分配长度为对齐的wantedSize1; xNextFreeByte == wantedSize1
第2次分配: pvRet = pucAlignedHeap + wantedSize1,分配长度为对齐的wantedSize2; xNextFreeByte == wantedSize1 + wantedSize2
xNextFreeByte 为已分配的长度;
核心代码
heap2.c
说明
heap2引入链式管理内存,支持内存释放,算法比较简单,仅链表的插入操作、遍历、删除操作,是最基本的链式管理算法,没考虑内存碎片问题,不会合并相邻的空闲节点;又名首次适应算法;
数据结构:每个内存块前面8个字节存链表节点字段,如下,返回给客户使用的pvReturn 为链表节点后的地址;
主要实现
使用链表存储空闲的内存块,链表按照xBlockSize的从小到大生成,
每次申请wantSized尺寸时,遍历链表,对首次找到xBlockSize>=wantSized的内存块,返回给链表节点后的地址即可;如果blockSize - wantedSize >heapMINIMUM_BLOCK_SIZE 该阈值时,会切割成2不部分,后面没有使用的那部分,会按照剩余blockSize,重新插入到链表中; 如果没有满足阈值,不做处理;
接口
- static void prvHeapInit( void );
- prvInsertBlockIntoFreeList( pxBlockToInsert );
- void *pvPortMalloc( size_t xWantedSize )
- void vPortFree( void *pv ) ;
根据内存块结构可知,根据pv可获取该链表节点的数据,(uint8*)pv-sizeof(BlockLink_t)即可,实际减去,重新根据该内存块大小 ,插入按空闲块长度排序的空闲链表中;
图1内存初始化,链表头,尾,空闲内存链表构建
图2 链表中插入空闲内存节点
pvPortMalloc:
vPortFree:
heap3.c
主要实现
- 简单的封装了标准c的malloc与free接口,并加入了 线程保护;no more;
heap4.c
说明
比较heap2的算法,支持内存碎片的合并;STM32 原子哥产品使用该算法;
与heap2 比较像,free这部分几乎完全一致;插入链表api做了最多2次(前、后)的连续内存块的合并;
主要实现
prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
插入空闲节点,插入空闲节点有两种场景,一是释放内存时候,二是分配内存,切割大内存块中剩余内存时候;
插入空闲节点会考虑前后合并相邻空闲内存块的问题;空闲内存块插入位置按照内存块地址来;
void *pvPortMalloc( size_t xWantedSize )
与heap2实现基本一样,唯一区别在于,空闲链表块不再是按照长度排序,是按照地址排序,首次找到大于wantedSize,即进行切割block,
不会在后续查找,即使后面可能有长度最合适的wantSized;
heap2与heap4对比
heap2空闲块插入示意图,根据空闲块大小插入链表节点,链表指向不是同向的,而heap4根据链表空闲块地址则是同向的,此处主要是为了和heap4形成对比;
核心代码:
heap5.c
说明
malloc与free算法与heap4完全一致,支持多个内存堆,比如sram,sdram等多个不同地址段的内存一起使用;
例如:
仅仅在初始化的时候,将多个不同地址段的空间,看成多个空闲内存块,加入空闲链表中;