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 为已分配的长度;

 

openresty lua 共享内存 freertos 共享内存_链表

 

 

 

核心代码

 

 

openresty lua 共享内存 freertos 共享内存_内存分配_02

 

 

 

 

heap2.c

说明

 heap2引入链式管理内存,支持内存释放,算法比较简单,仅链表的插入操作、遍历、删除操作,是最基本的链式管理算法,没考虑内存碎片问题,不会合并相邻的空闲节点;又名首次适应算法;

数据结构:每个内存块前面8个字节存链表节点字段,如下,返回给客户使用的pvReturn 为链表节点后的地址;

 

 

openresty lua 共享内存 freertos 共享内存_链表_03

 

 

 

主要实现

使用链表存储空闲的内存块,链表按照xBlockSize的从小到大生成,

每次申请wantSized尺寸时,遍历链表,对首次找到xBlockSize>=wantSized的内存块,返回给链表节点后的地址即可;如果blockSize - wantedSize >heapMINIMUM_BLOCK_SIZE 该阈值时,会切割成2不部分,后面没有使用的那部分,会按照剩余blockSize,重新插入到链表中; 如果没有满足阈值,不做处理;

 

 

openresty lua 共享内存 freertos 共享内存_openresty lua 共享内存_04

 

 

 

 

 

接口

  • static void prvHeapInit( void );
  • prvInsertBlockIntoFreeList( pxBlockToInsert );
  • void *pvPortMalloc( size_t xWantedSize )
  • void vPortFree( void *pv ) ;

根据内存块结构可知,根据pv可获取该链表节点的数据,(uint8*)pv-sizeof(BlockLink_t)即可,实际减去,重新根据该内存块大小 ,插入按空闲块长度排序的空闲链表中;

 

openresty lua 共享内存 freertos 共享内存_链表_05

 

 

 

 

 图1内存初始化,链表头,尾,空闲内存链表构建

 

 

openresty lua 共享内存 freertos 共享内存_内存碎片_06

 

 

 

 图2 链表中插入空闲内存节点

 

 

pvPortMalloc:

 

openresty lua 共享内存 freertos 共享内存_内存碎片_07

 

 

 

vPortFree:

 

openresty lua 共享内存 freertos 共享内存_内存分配_08

 

 

 

heap3.c

主要实现

  • 简单的封装了标准c的malloc与free接口,并加入了 线程保护;no more;

 

 

openresty lua 共享内存 freertos 共享内存_openresty lua 共享内存_09

 

 

 

heap4.c

说明

比较heap2的算法,支持内存碎片的合并;STM32 原子哥产品使用该算法;

与heap2 比较像,free这部分几乎完全一致;插入链表api做了最多2次(前、后)的连续内存块的合并;

 

主要实现

 prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )

插入空闲节点,插入空闲节点有两种场景,一是释放内存时候,二是分配内存,切割大内存块中剩余内存时候;

插入空闲节点会考虑前后合并相邻空闲内存块的问题;空闲内存块插入位置按照内存块地址来;

 

openresty lua 共享内存 freertos 共享内存_内存分配_10

 

 

 

void *pvPortMalloc( size_t xWantedSize )

与heap2实现基本一样,唯一区别在于,空闲链表块不再是按照长度排序,是按照地址排序,首次找到大于wantedSize,即进行切割block,

不会在后续查找,即使后面可能有长度最合适的wantSized;

 

heap2与heap4对比

heap2空闲块插入示意图,根据空闲块大小插入链表节点,链表指向不是同向的,而heap4根据链表空闲块地址则是同向的,此处主要是为了和heap4形成对比;

 

openresty lua 共享内存 freertos 共享内存_内存分配_11

 

 

 

 

核心代码:

 

 

openresty lua 共享内存 freertos 共享内存_内存分配_12

 

 

 

heap5.c

说明

malloc与free算法与heap4完全一致,支持多个内存堆,比如sram,sdram等多个不同地址段的内存一起使用;

例如:

 

openresty lua 共享内存 freertos 共享内存_openresty lua 共享内存_13

 

 

 

 

仅仅在初始化的时候,将多个不同地址段的空间,看成多个空闲内存块,加入空闲链表中;