freertos之heap浅析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33894122/article/details/84329810

前言

这几天看源码,先看了queue,task东西有点多,还是先以少到多,慢慢来。
在freertos中,官方实现了5种方法来分配对内存,没有直接使用malloc 和 free函数,因为线程不安全,换句话说就是在malloc的时候可能被抢占。下面来介绍五个文件实现的堆内存分配和释放的方法。


heap1.c

heap1.c这是最简单的实现。它不允许在分配内存之后释放内存。尽管这样。heap_1.c适用于大量的嵌入式应用程序。这是因为许多小而深入的应用程序在系统启动时创建所有所需的任务、队列、信号量等,然后在程序的生命周期中使用所有这些对象(直到应用程序再次关闭或重新启动)。任何东西都不会被删除,实现只是根据RAM的请求将单个数组细分为更小的块。数组的总大小(堆的总大小)是由configTOTAL堆大小设置的,这个大小在FreeRTOSConfig.h中定义。
heap_1的实现:

  • 可以使用如果您的应用程序没有删除任务,队列,信号量,互斥锁,等等。(实际上涵盖了大多数应用程序FreeRTOS被使用)。
  • 总是确定性的(总是需要相同的执行时间),并且不会导致内存碎片。
  • 是非常简单的,从静态分配的数组中分配内存,这意味着它通常适用于不允许真正的动态内存分配的应用程序。
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
	/* 保证申请的内存块是以 portBYTE_ALIGNMENT 对齐的,如果本身就是1字节对齐那就不做操作了*/
	#if( portBYTE_ALIGNMENT != 1 ){
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
			/* Byte alignment required. */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();/* 就是给调度器上锁,所有任务不得调度 */
	{
		if( pucAlignedHeap == NULL )/* 第一次malloc分配 */
		{
			/* Ensure the heap starts on a correctly aligned boundary. */
			/* 保证堆区域是在正确的地址上开始的 */
			pucAlignedHeap = ( uint8_t * ) (   ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) 
										& ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		}
		/* 保证有足够容量 和 检查xWantedSize 是否为负数或者 ( xNextFreeByte + xWantedSize )溢出*/
		if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
			( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	){
			/* Return the next free byte then increment the index past this block. */
			pvReturn = pucAlignedHeap + xNextFreeByte;
			xNextFreeByte += xWantedSize;
		}
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();/* 就是给调度器解锁,所有任务开始重新调度 */
//其实还有个内存分配失败的预定义函数,如果内存分配失败可以在这个函数中做相应的操作
	return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFree( void *pv )
{
	/* 这种模式分配的堆内存不可以被释放,可以用heap2.c heap3.c heap4.c等代替 */
	( void ) pv;
	configASSERT( pv == NULL );
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void )
{
	/* 静态内存初始化 xNextFreeByte 就是已经用了的内存 */
	xNextFreeByte = ( size_t ) 0;
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
	/* configADJUSTED_HEAP_SIZE 是用户自定义的系统中总内存    xNextFreeByte 就是已经用了的内存 */
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

heap2.c

该方案使用了最佳拟合算法,并且与heap1.c不同,它允许释放以前分配的块。它不会将相邻的自由块合并成单个大块,但是heap_4.c实现了合并相邻的空闲内存块。
xPortGetFreeHeapSize() API函数返回仍然未分配的堆空间的总量(允许对configTOTAL_HEAP_SIZE设置进行优化),但没有提供关于如何将未分配的内存分割成更小块的信息。
heap2.c实现:

  • 当应用程序重复删除任务、队列、信号量、互斥对象等时,也可以使用此方法,但关于内存碎片的警告如下。
  • 如果分配和释放的内存是随机大小,则不应使用。例如:
  1. 如果应用程序动态创建和删除任务,并且分配给正在创建的任务的堆栈大小总是相同的,那么heap2。c在大多数情况下都可以使用。但是,如果分配给要创建的任务的堆栈大小不总是相同的,那么可用的空闲内存可能会分散成许多小块,最终导致分配失败。heap_4.c在这种情况下是更好的选择。
  2. 如果应用程序动态创建和删除队列,并且队列存储区域在每种情况下都是相同的(队列存储区域是队列项大小乘以队列长度),那么heap_2。c在大多数情况下都可以使用。但是,如果队列存储区域在每种情况下都不相同,那么可用的空闲内存可能会分散成许多小块,最终导致分配失败。heap_4.c在这种情况下是更好的选择。
  3. 应用程序直接调用pvPortMalloc()和vPortFree(),而不是通过其他FreeRTOS API函数间接调用。
  • 如果应用程序队列、任务、信号量、互斥等顺序不可预知,可能会导致内存碎片问题。这对于几乎所有的应用程序来说都不太可能,但应该记住这一点。
  • 不是确定性的——但是比大多数标准的C库malloc实现更高效。
    heap_2.c语言适用于许多需要动态创建对象的小型实时系统。请参阅heap4.c了解将空闲内存块组合成单个更大的块的类似实现。下面分析heap2.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;

/* 插入一个空闲内存块到链表中,这个链表是按照空闲内存块的大小来排序的,小块在开始,大块在后*/
#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\
	/* Iterate through the list until a block is found that has a larger size */	\
	/* than the block we are inserting. 遍历链表,直到找到一个比他大的内存*/				            \
	for( pxIterator = &xStart; \
		pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; \
		pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		/* There is nothing to do here - just iterate to the correct position. */	\
	}																				\
																					\
	/* Update the list to include the block being inserted in the correct */		\
	/* position. */																	\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}
/*-----------------------------------------------------------*/

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;
	vTaskSuspendAll();{
		/*第一次调用pvPortMalloc的话,需要初始化建立空闲内存列表 */
		if( xHeapHasBeenInitialised == pdFALSE ){
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}

		/* 保证需要的内存对齐。 */
		if( xWantedSize > 0 ){
			xWantedSize += heapSTRUCT_SIZE;
			/* Ensure that blocks are always aligned to the required number of bytes. */
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ){
				/* Byte alignment required. */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}/*需要多少字节需要加上 heapSTRUCT_SIZE 算*/

		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) ){
			/* 内存块是按字节大小顺序存储的 - 从开始遍历整个列表,直到最后找出足够的内存块 */
			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			while( ( pxBlock->xBlockSize < xWantedSize ) 
					&& ( pxBlock->pxNextFreeBlock != NULL ) ){
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;
			}
			/* 如果找到了结尾标志 then 说明没有找到合适大小的内存块 */
			if( pxBlock != &xEnd ){
				/* 返回申请的内存空间地址 - 要在开始地址跳过 BlockLink_t 所占的结构*/
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

				/* 这块内存已经被返回使用了所以必须从空闲内存列表中移除 */
				pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

				/* 如果申请到的内存块比需要的多 heapMINIMUM_BLOCK_SIZE 以上,那他可以被分为两块,
				一块被返回了一块被用作下一次分配 */
				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;/* 剩余的内存需要更新 */
			}
		}
	}
	( void ) xTaskResumeAll();
	return pvReturn;
}
/*-----------------------------------------------------------*/

void vPortFree( void *pv ){
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
	if( pv != NULL ){
		/* 被释放的内存有个BlockLink_t结构在他前面 */
		puc -= heapSTRUCT_SIZE;
		vTaskSuspendAll();{
			/* Add this block to the list of free blocks. */
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void ){
	return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void ){
	/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/

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;
}
/*-----------------------------------------------------------*/

heap3.c

heap3.c为标准的C库malloc()和free()函数实现了一个简单的包装器,在大多数情况下,选择的编译器将提供这个包装器。包装器只是在外面停止了调度器 使得 malloc()和free() 函数线程安全。
这个实现:

  • 需要链接器设置堆,编译器库提供malloc()和free()实现。
  • 是不确定的,C库的malloc跟free 可能需要的时间不确定。
  • 可能会大大增加RTOS内核代码的大小。
    注意FreeRTOSConfig中的configTOTAL_HEAP_SIZE设置。使用heap_3时没有影响。
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn;
	vTaskSuspendAll();{
		pvReturn = malloc( xWantedSize );
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();
//其实还有个内存分配失败的预定义函数,如果内存分配失败可以在这个函数中做相应的操作
	return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFree( void *pv ){
	if( pv ){
		vTaskSuspendAll();{
			free( pv );
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();
	}
}

注意这个heap3.c中的pvPortMallocvPortFree 只是在c库的 mallocfree 外面封装了两个系统函数,目的是在执行 malloc 和 free 的时候不会被抢占,就是所谓的线程安全

猜你喜欢

转载自blog.csdn.net/qq_33894122/article/details/84329810