freertos的内存管理其实挺简单

本来打算只用一篇文章来介绍所有rtos的内存管理,但是发现内存管理写起来内容还挺多,就慢慢来吧。


freertos内存管理

freertos支持5种内存管理算法,分布在5个文件中,分别命名为heap_1.c,heap_2.c,heap_3.c,heap_4.c,heap_5.c。\u0001


Heap_1.c

先说一下这种内存分配的算法要实现要求:

按需分配,不需要释放分配的空间。

总共包含这么几个函数:

<code>void *pvPortMalloc( size_t xWantedSize );

void vPortFree( void *pv );

void vPortInitialiseBlocks( void );

size_t xPortGetFreeHeapSize( void );/<code>

它首先定义一个大的数组,命名为:static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];


1.怎么实现pvPortMalloc?

首先需要返回申请的内存的起始地址,然后是申请的内存也需要按照8字节对齐。

所以,定义一个变量,static uint8_t *pucAlignedHeap = NULL;既然需要8字节对齐,所以可以使用:

pucAlignedHeap = ( uint8_t * ) ( ( ( uint32_t ) &ucHeap[ 8 ] ) & ( ~( ( uint32_t ) 7 ) ) );

参数:xWantedSize 表示的是想要申请的内存大小。

再定义一个变量:

static size_t xNextFreeByte = ( size_t ) 0;它表示的是现在已经使用的空间。

所以,这个函数主要的实现方法就是:

pvReturn = pucAlignedHeap + xNextFreeByte;

其中,pvReturn就是返回的分配的地址。

最后,就只需要将 xNextFreeByte重新加载一个新值即可,如下:

xNextFreeByte += xWantedSize;

这就是函数:void *pvPortMalloc( size_t xWantedSize );


2.有malloc,怎么释放?

这个文件实现的内存分配是不需要释放空间的,它在整个生命周期中不再被其他地方再利用。所以这个函数其实就什么都不需要做。这就是函数:void vPortFree( void *pv );


3.内存块怎么初始化?

这个文件的内存管理算法比较简单,所以只需要初始化变量xNextFreeByte = ( size_t ) 0;

这就是函数:void vPortInitialiseBlocks( void );实现的内容。

4.怎么知道还剩下多少可用空间?

上面定义了一个变量是是使用了多少空间,所以剩下多少空间只需:

( configADJUSTED_HEAP_SIZE - xNextFreeByte );

这就是函数:size_t xPortGetFreeHeapSize( void );实现的内容。


Heap_2.c

这个文件的内存管理实现的要求如:

使用链表实现内存管理,并且需要能统计内存剩余量。

先说一下思路:先定义一个大的数组,然后定义一个起始链表,一个结束链表,这是两个全局的结构体,不占用数组项。起始项结构体的大小字段定义为0,它的下一个链定义为数组的初始地址(因为初始情况下数组就是没有使用过的),而结束链表结构体的大小字段定义为数组的大小,下一个链定义为空。

后续需要分配空间的时候,则将分配的空间从空闲链中摘去,把下一个空闲的空间链接到链表中,分配完成后,需要将数据空间,而不是从结构体空间返回给用户接口。

释放的时候,指定的是数据空间地址,所以根据它找到链表的地址,然后将它添加到整个空闲的链表中,另外,需要将这个空间大小加到内存剩余空间计数值上。

从这个思路出发,可以看到这种分配有一些缺点:

不能合并内存,那么会导致内存分配次数过多,最后找不到大的内存了。例如,一个300bytes的空间,3次分配100bytes。再释放3次,这样空闲空间数为300bytes,但是其实每一个空闲的是100bytes,所以要再分配200bytes是不可能的。因此,这种算法还是比较适合每次分配释放空间都一样的大小。

1.定义链表结构

由于使用的是链表管理整个系统内存,所以先定义一个结构体,只需要包含下一个存储块的位置和当前存储块的大小,定义如:

typedef struct A_BLOCK_LINK

{

struct A_BLOCK_LINK *pxNextFreeBlock; /*<< The next free block in the list. */

size_t xBlockSize; /*<< The size of the free block. */

} BlockLink_t;

2.内存分配

定义函数:void *pvPortMalloc( size_t xWantedSize )

对于用户来说,初始化工作也需要由系统做好,所以这个函数里面要做到第一次操作这个函数和后续操作这个函数的区别。

这其实也是freertos做的好的一个方面,用户不用过多关注系统层面的内容。

定义一个初始与结束的链表变量:

static BlockLink_t xStart, xEnd;

它们赋值如下:

xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;

xStart.xBlockSize = ( size_t ) 0;

/* xEnd is used to mark the end of the list of free blocks. */

xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;

xEnd.pxNextFreeBlock = NULL;

然后再将管理的内存表链接进来:

pxFirstFreeBlock = ( void * ) pucAlignedHeap;

pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;

pxFirstFreeBlock->pxNextFreeBlock = &xEnd;


如下图所示:


freertos的内存管理其实挺简单

如果分配了一个空间,则如下图所示:


freertos的内存管理其实挺简单

所以,用图说起来更简单了。

另外说一下,在多次申请内存与释放内存后,查找空闲内存算法是找到一个空闲内存只要大于需要申请的内存,就使用这个空闲内存区域。


3.内存释放

定义函数:void vPortFree( void *pv )

释放则是将占用的空间重新链接到空闲表中。


Heap_3.c

这个算法是调用系统提供的malloc和free,只是为了支持系统特性而加了一层封装,所以没什么可说的。


Heap_4.c

在heap_2.c中已经说明了存在内存越分配越小,最后找不到大的空间(但是其实还是有大的空间的)。

这节就增加了内存合并算法,例如300bytes分3次分配了100bytes,然后释放3次,如果按照heap_2.c则还会有链表链着这3个空间,因此再分配200bytes则分配不了。而如果在第二次释放空间的时候,将第一次释放的空间合并,然后第三次释放空间的时候,再执行一次合并,则可以将这300bytes的内容顺利回收,那再申请200bytes空间就不会有问题了。


首先说明一下:

freertos的这部分的内存管理代码写的不一定算很好,因为考虑一个问题,在调用分配函数分配一块内存的时候,这个时候有没有可能需要合并内存块呢?这个时候我认为是没有可能的(有不同意见的还请告知)。所以合并算法只可能在调用释放函数的时候。而freertos将所有功能都放在static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )中,这就是不合理的地方。也有可能freertos有自己的考虑。另外一个则是块使用标志符xBlockAllocatedBit,有它与没有它程序也能很好的工作。

大致列出heap_4.c能执行到的分配释放流程如下:


freertos的内存管理其实挺简单


freertos的内存管理其实挺简单

再来具体看看合并算法怎么实现的,因为它才是本节的重点。

如:

在”free开始前”,有准备释放的块,则执行合并算法的时候,先遍历空闲链表,它需要按照地址大小遍历。由于释放的块地址在前面,所以遍历直接退出。那它执行合并算法的分支:

xStart->pxNextFreeBlock=准备释放的块

准备释放的块->pxNextFreeBlock=下一个地址值大空闲的块

如果有两个空闲的块中间夹着一个使用中的块,则这个使用中的块如果要释放:

先定义三个块为A(地址小的空闲块)、B(准备释放的块)、C(地址大的空闲块)。

仍然遍历空闲表,找到了A这个位置,然后A的地址+块大小,刚好为B的地址,那么这两个块合并。

接着AB合成的一个块发现地址值与块大小和C的地址也重合,那么就再做一次合并。


还有一种可能是释放的是xEnd之前的一个块,则如下所示:


freertos的内存管理其实挺简单

这只需要做好空闲块的指针指向即可。

所以上面就是函数static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )的内容了。


就不再一一分析每句代码,这比代码形象多了。

另外,为了获取操作内存的信息,再定义一个函数:void vPortGetHeapStats( HeapStats_t *pxHeapStats );

以上,就能说明完全部的heap_4.c的代码了。


Heap_5.c


这个文件实现的内存管理在算法上没有增强,只是在功能上增加了离散区域内存块管理。例如,芯片上sram有内存空间与sdram上有内存空间,它能实现统一管理。

所以其他的没什么好说的,只是考虑一个问题,功能的增加导致对外接口不能只有申请和释放函数,而还需要添加初始化内存管理块函数:void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );


另外,画了一张图,表明初始化要做的事情:

freertos的内存管理其实挺简单

这张图说明的意思是,有两个结构体,第一个结构体表明的使用的sram,第二个结构体表明的是使用的sdram,而vPortDefineHeapRegions则能将它们链接在一起形成一个整体来进行管理。


分享到:


相關文章: