FreeRTOS笔记(六):五种内存管理详解

不同的嵌入式系统对于内存分配和时间要求不同。FreeRTSO将内存分配作为移植层的一部分,这样FreeRTOS使用者就可以设用自己的合适的内存分配方法。

当内核需要分配内存时可以调用pvPortMalloc(),释放内存时使用pvPortFree()。

FreeRTOS提供了5种内存分配方法,以不同文件的形式存在,分别是heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c。这5个文件在FreeRTOS源码终,路径为FreRTOS->Source->porttable->MemMang。

一 、heap_1.c

heap_1实现起来就是当需要RAM的时候就从一个大数组中分一小块出来,大叔组的容量为configTOTAL_HEAP_SIZE。使用xPortGetFreeHeapSize()可以获取内存堆栈的剩余内存大小。

1.1 heap_1特性

1.适合一旦创建好任务、信号量和队列就再也不删除的应用;

2.具有可确定性,而且不会产生碎片;

3.代码实现和内存分配简单,适合不要动态内存分配的应用。

1.2内存申请函数详解
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;

	//字节对齐
	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
			//需要字节对齐
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();
	{
		if( pucAlignedHeap == NULL )
		{
			//确保内存开始的地址是字节对齐的
			pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		}

		//检查是否由足够的内存分配
		if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
			( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)/* Check for overflow. */
		{
			pvReturn = pucAlignedHeap + xNextFreeByte;
			xNextFreeByte += xWantedSize;
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}
1.3内存释放函数
void vPortFree( void *pv )
{
	( void ) pv;
	configASSERT( pv == NULL );
}

二、heap_2.c

heap_2不会把释放的内存块合并成一个大块,这样有一个缺点,随着你不断的申请内存,内存就会被分为很多大小不一的碎片。

2.1 heap_2特性

1.可以重复删除任务、队列、信号等,但会产生碎片;

2.适合分配的内存大小都一样的场景。

2.2 heap_2内存块详解

和heap_1一样,heap_2整个内存堆都是ucHeap[],大小为configTOTAL_HEAP_SIZE。

为了实现内存块释放,heap_2引入了内存块的概念,没分去一段内存就是一个内存块,剩下的空闲内存也是一个内存块。为了管理内存块又引入了一个链表结构,如下:

typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	//指向下一个空闲内存块
	size_t xBlockSize;						//当前空闲内存块大小
} BlockLink_t;

内存块结构体如下图:
FreeRTOS笔记(六):五种内存管理详解_第1张图片

图中,虽然只申请了16个字节,但是还需要另外8字节来保存BlockLink_t结构体变量

2.3 heap_2内存块初始化

内存初始化函数为prvHeapInit,如下:

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;

	//确保内存开始地址是对齐的
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

	//xStart指向空闲内存的链表首
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	//xEnd指向空闲内存的链表尾部
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;

	//刚开始只有一个空闲内存块,空闲大小就是可用内存大小
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

FreeRTOS笔记(六):五种内存管理详解_第2张图片

2.4 heap_2内存块插入函数

heap_2允许内存释放,释放的内存肯定是添加到空闲内存链表中的,如下:

#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\										
		//遍历链表,找到插入点															\
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		//不做任何事																	\
	}																				\
		//插入点中														 			\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}

内存块释放,插入空闲链表中:

FreeRTOS笔记(六):五种内存管理详解_第3张图片

2.5 heap_2内存块申请
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;

	vTaskSuspendAll();
	{
		//第一次申请内存的话需要初始化内存
		if( xHeapHasBeenInitialised == pdFALSE )
		{
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}
		//内存对齐,实际申请的内存大小还要加上结构体
		if( xWantedSize > 0 )
		{
			xWantedSize += heapSTRUCT_SIZE;
			//xWantedSize做字节对齐
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
			{
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}

		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
		{
			//从xStart开始寻找满足所需要的内存块
			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
			{
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;
			}
			if( pxBlock != &xEnd )
			{
				//返回申请到的内存受地址
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
				pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
					pxBlock->xBlockSize = xWantedSize;

					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
				}

				xFreeBytesRemaining -= pxBlock->xBlockSize;
			}
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}
2.6 heap_2内存块释放
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
	if( pv != NULL )
	{
		puc -= heapSTRUCT_SIZE;		
		pxLink = ( void * ) puc;

		vTaskSuspendAll();
		{
			//将内存块添加到空闲内存块链表中
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}

三、heap_3内存分配方法

这个分配方法对标准C中的函数malloc()和free()的简单封装,FreeRTOS对这两个函数做了线程保护。

3.1 heap_3内存分配详解
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

	vTaskSuspendAll();   //-------------------------------(1)
	{
		pvReturn = malloc( xWantedSize );   //------------(2)
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();            //--------------(3)

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}

(1)挂起任务调度器,为malloc()提供线程保护;

(2)调用malloc()来申请内存;

(3)恢复任务调度。

3.2 heap_3内存释放详解
void vPortFree( void *pv )
{
	if( pv )
	{
		vTaskSuspendAll();   //-----------(1)
		{
			free( pv );		//------------(2)
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();	//----(3)
	}
}

(1)挂起任务调度器,为free()提供线程保护;

(2)调用freec()来释放内存;

(3)恢复任务调度。

3.3 heap_3特性如下

1.需要编译器提供一个内存堆,编译器要提供malloc()和free()函数;

2.具有不确定性;

3.可能会增加代码量。

四、heap_4内存分配

heap_4提供了一个最优的匹配算法,heap_4会将内存碎片合并成一个大的可用内存块,它提供了内存合并算法。heap_4也使用链表来管理空闲内存块,定义了xStart和pxEnd来表示链表头和尾。

4.1 heap_4特性如下

1.可以用在重复创建删除任务、队列、信号量和互斥量等应用中;

2.不会像heap_2那样产生严重的内存碎片;

3.具有不确定性,但比malloc()和free()函数效率高。

4.2 heap_4内存初始化函数
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	//起始地址做字节对齐处理
	uxAddress = ( size_t ) ucHeap;
	if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
	{
		uxAddress += ( portBYTE_ALIGNMENT - 1 );
		uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
		xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
	}

	pucAlignedHeap = ( uint8_t * ) uxAddress;

	//xStart为空闲链表头
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	//pxEnd为空闲内存块列表尾,并且将其放到内存的尾部
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	//开始时将内存堆整个可用空间看成一个空闲内存块
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	//只有一个内存块,而且整个内存块拥有内存堆整个可用空间
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

FreeRTOS笔记(六):五种内存管理详解_第4张图片

4.3 heap_4内存插入函数

释放时将某个内存块插入到空闲链表中。

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;

	//寻找插入点
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
	{

	}
	//检查内存块,如果可以和之前的内存合并的话,就合并
	puc = ( uint8_t * ) pxIterator;
	if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
	{
		pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
		pxBlockToInsert = pxIterator;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	//检查内存块,如果可以和之后的内存合并的话,就合并
	puc = ( uint8_t * ) pxBlockToInsert;
	if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
	{
		if( pxIterator->pxNextFreeBlock != pxEnd )
		{
			
			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();
	}
}

在下图中,右边椭圆圈出来的就是要插入的内存块,其起始地址为0x20009040,该地址刚好是空闲内存块Block2的末尾地址一样,所有这两个内存块就可以合并。合并以后Block2的大小xBlockSize要更新为最新的内存块大小,为64+80=144。

FreeRTOS笔记(六):五种内存管理详解_第5张图片

4.4 heap_4内存分配函数
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	vTaskSuspendAll();
	{
		//第一次调用初始化堆
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		//需要申请的内存块大小的最高位不能为1,最高位用来表示有没有被使用
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			if( xWantedSize > 0 )
			{
				xWantedSize += xHeapStructSize;
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					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 <= xFreeBytesRemaining ) )
			{
				//从xStart开始,查找满足大小的内存块
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}
				//没有找到可以分配的内存块
				if( pxBlock != pxEnd )
				{
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
					//将申请的内存块从空闲列表中删除
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					//申请到的内存块大于所需大小,将其分成两块
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
						configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					xFreeBytesRemaining -= pxBlock->xBlockSize;

					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
					//内存申请成功,标记次内存
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif

	configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
	return pvReturn;
}
4.5 heap_4内存释放函数

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

if( pv != NULL )
{
	puc -= xHeapStructSize;                           //-------------------(1)
	//防止编译器报错
	pxLink = ( void * ) puc;
	configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
	configASSERT( pxLink->pxNextFreeBlock == NULL );

	if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )//---------------(2)
	{
		if( pxLink->pxNextFreeBlock == NULL )
		{
			pxLink->xBlockSize &= ~xBlockAllocatedBit;  //-----------------(3)
			vTaskSuspendAll();
			{
				xFreeBytesRemaining += pxLink->xBlockSize;
				traceFREE( pv, pxLink->xBlockSize );
				prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );//(4)
			}
			( void ) xTaskResumeAll();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

}

(1)获取内存块的BlockLink_t类型结构体;

(2)判断释放的内存是否被使用;

(3)重新标记此内存块没有被使用;

(4)将内存块插入空闲链表中。

五、heap_5内存分配方法

heap_5使用了和heap_4相同的合并算法,但是heap_5允许内存跨越多个连续的内存段。heap_4只能在内部RAM和外部SRAM或SDRAM之间二选一,使用heap_5的话就不会存在这个问题,两个都可以使用。

六、总结

heap_1:最简单只能分配,不能释放。

heap_2:提供了释放,用户可以直接调用pvPortMalloc、vPortFree函数。

heap_3:对标准C库的malloc、free进行封装,提供了线程保护。

heap_4:相较于heap_2提供了内存合并,降低了内存碎片。

heap_5:支持不连续的内存块。

你可能感兴趣的:(freeRTOS,操作系统,嵌入式,freertos,内存管理)