
在FreeRTOS中,内存管理文件夹下存在着五个源文件,分别是heap_1~5.c,对应着五种不同的内存管理策略,准确来说,应该是堆内存的管理策略。
下面逐一讲解这些源码以及背后的思想。
这个文件的代码仅有150多行,实现的功能也十分有限,主要就是实现了pvPortMalloc()函数,用于申请内存。这个策略是不允许释放内存的,所以不存在内存碎片的问题。这个策略适用于:
//堆的定义:
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];可以看到,所谓的堆其是就是一段连续的内存空间,就是一个巨大的数组,在F103中的FreeRTOSConfig.h中,将这个数组大小设置为了17KB。所以内存的分配和释放其实就是在这个数组上进行。
void * pvPortMalloc( size_t xWantedSize )
{
void * pvReturn = NULL;
static uint8_t * pucAlignedHeap = NULL;
/* Ensure that blocks are always aligned. */
#if ( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
/* Byte alignment required. Check for overflow. */
if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
xWantedSize = 0;
}
}
}
#endif /* if ( portBYTE_ALIGNMENT != 1 ) */
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL )
{
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
/* Check there is enough room left for the allocation and. */
if( ( xWantedSize > 0 ) && /* valid size */
( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
{
/* Return the next free byte then increment the index past this
* block. */
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}这个函数的参数是想要申请的空间大小,返回值是申请空间的地址。
函数的刚开始会进行一次字节对齐的判断,Cortex M3内核采取的是8字节对齐,这样可以加快cpu访问效率。如果xWantedSize=5,会在这个判断语句中补到8,以确保内存对齐。
接下来需要关闭调度器,防止分配内存时,被任务调度打断。
vTaskSuspendAll();接着,在第一个判断语句中,确保堆内存的起始地址也是对齐的,如果不是对齐的,pucAlignedHeap会指向调整之后的堆内存。
if( pucAlignedHeap == NULL )
{
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}下面进行申请空间,先判断xWantedSize是否合法以及申请后,是否会出现堆溢出等条件,如果全部符合申请条件,则将申请的地址赋值给pvReturn并返回。
/* Check there is enough room left for the allocation and. */
if( ( xWantedSize > 0 ) && /* valid size */
( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
{
/* Return the next free byte then increment the index past this
* block. */
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}重新开启调度器。
( void ) xTaskResumeAll();后面这里还有一个条件判断语句,是FreeRTOS中开放给用户的钩子函数,用于在内存分配失败后处理。
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
vApplicationMallocFailedHook();
}
}
#endifheap_1.c不支持释放内存,所以
void vPortFree( void * pv )这个函数没有实现
void vPortFree( void * pv )
{
/* Memory cannot be freed using this scheme. See heap_2.c, heap_3.c and
* heap_4.c for alternative implementations, and the memory management pages of
* https://www.FreeRTOS.org for more information. */
( void ) pv;
/* Force an assert as it is invalid to call this function. */
configASSERT( pv == NULL );
}还有两个函数用于当前堆指针初始化,和获取。
void vPortInitialiseBlocks( void )
{
/* Only required when static memory is not cleared. */
xNextFreeByte = ( size_t ) 0;
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
return( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}
heap_2.c相当于在heap_1.c的基础上演进而来。2使用的同样是一个巨大的数组:
PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];只不过为了实现能够动态释放内存,heap_2.c引入了新的算法和结构。

heap_2.c引入了一个块结构,用于表示分配的内存,其本质是一个链表,记录下一个块地址,并记录当前剩余可用空间:
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;
我们来看,初始化函数做了什么?
static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */
{
BlockLink_t * pxFirstFreeBlock;
uint8_t * pucAlignedHeap;
/* Ensure the heap starts on a correctly aligned boundary. */
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* xStart is used to hold a pointer to the first item in the list of free
* blocks. The void cast is used to prevent compiler warnings. */
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;
/* To start with there is a single free block that is sized to take up the
* entire heap space. */
pxFirstFreeBlock = ( BlockLink_t * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}首先还是在刚开始,将当前可用的堆地址对齐,pucAlignedHeap指向当前对齐后的地址,也就是可用于分配的地址。
然后定义了三个指针,分别是头尾指针xStatr、xEnd和已分配头节点指针。初始函数将这两个指针分别指向了堆的开头和结尾,
然后将pxFirstFreeBlock指向当前可分配的地址:

下面先讲解内存释放函数:
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* The memory being freed will have an BlockLink_t structure immediately
* before it. */
puc -= heapSTRUCT_SIZE;
/* This unexpected casting is to keep some compilers from issuing
* byte alignment warnings. */
pxLink = ( void * ) puc;
configASSERT( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
* allocated. */
heapFREE_BLOCK( pxLink );
#if ( configHEAP_CLEAR_MEMORY_ON_FREE == 1 )
{
( void ) memset( puc + heapSTRUCT_SIZE, 0, pxLink->xBlockSize - heapSTRUCT_SIZE );
}
#endif
vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
}
( void ) xTaskResumeAll();
}
}
}
}vPortFree 函数的主要功能是将之前分配的内存块返回到堆中,以便后续的内存分配可以使用。它的主要步骤包括:
NULL。BlockLink_t 类型的指针。也就是说,我们将要释放的地址传递给这个API,函数就会根据我们提供的地址,定位到指定块,然后将其连接到空闲链表上,并修改标志位,表示这块内存可用。
核心函数:
vTaskSuspendAll();
{
prvInsertBlockIntoFreeList((BlockLink_t *)pxLink);
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE(pv, pxLink->xBlockSize);
}
(void)xTaskResumeAll();完成将释放的块挂载空闲链表上。
heap_2.c的内存分配函数原理与1差不多,也要考虑内存对齐问题,其接收到我们提供的参数(要分配内存的大小)后,遍历空闲链表,如果匹配到需要大小的块,就占用这个地址,并从空闲链表中移除。
具体可见这篇博客,我认为写的非常细致:
https://zhuanlan.zhihu.com/p/687409961
下篇会继续分析heap_4.c也就是我们移植一直使用的这个内存管理模块。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。