FreeRTOS-内存管理源码分析

FreeRTOS 总共提供了5种内存分配方法:

heap_1.c
heap_2.c
heap_3.c
heap_4.c
heap_5.c

这五种分配方式各有各的优势,用户可根据应用情况按需使用,在分析源码之前先了解一下内存管理的一些相关知识,其中内存碎片一直是内存管理致力于解决的一项问题,内存碎片是指频繁地请求和释放不同大小的内存,结果就是当再次要求分配连续的内存时导致申请失败,原因是由于之前内存块被释放后,存在空闲内存块大小不一,空闲的小内存块穿插于各个大内存块中间,当需要申请的内存总比这些空闲小内存块大的时候,这些小内存就永远无法得到使用,但它又一直占着空间,这些无法合并的小内存块就叫内存碎片,内存碎片最终会导致内存明明是够的,却无法分配成功的现象。入下图所示:
FreeRTOS-内存管理源码分析
FreeRTOS 对内存碎片也有做出相应的处理,后面代码会详细分析源码是如何尽量避免碎片产生的。这里先了解一下5中内存分配的优缺点:

heap_1:
适合不需要动态管理的应用,一旦创建好任务、信号量、队列就不再删除的应用,不会产生内存碎片,对于一般的应用这已经足够使用了

heap_2:
使用动态管理内存,用在可能会重复的删除任务、队列、信号量的应用中,如果每次分配的大小不一 样会导致内存碎片产生,如果针对一项操作每次分配的内存都一样大,那heap2就很合适了

heap_3:
对标准C中的函数 malloc() 和 free() 的简单封装(做线程保护),STM32的话在启动文件中修改Heap_Size来修改内存的大小,这种方式具有不确定性,可能会增加代码量

heap_4:
使用动态管理内存,用在可能会重复的删除任务、队列、信号量的应用中,不会像heap_2那样产生严重的内存碎片,但同样有可能产生内存碎片的问题,只有前后地址连续时才能合并内存块,一定程度上可以降低内存碎片的产生,一般有动态分配需求建议使用这种方式

heap_5:
和heap4的实现方式大致相同,在heap4的基础上允许内存跨越多个不连续的内存段,比如说有外部SRAM或SDRAM的时候,heap4只能选择外部或内部中的一个作为内存管理对象,但heap5则允许两个一起作为内存堆来使用。

在操作内存之前,用户需要自定义内存管理的对象,即内存堆,在 FreeRTOSConfig.c 中可以定义内存堆的大小:

#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 17 * 1024 ) )

系统实际上就是去定义一个大的数组作为内存堆:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

在了解各个分配方式的优缺点后,下面针对每个内存分配方式进行源码分析:


heap_1.c
内存分配源码分析:

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;

	#if( portBYTE_ALIGNMENT != 1 )
	{
		/* 字节数8字节对齐 */
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
			/* 补充字节数对齐 */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	/* 任务调度器休眠 */
	vTaskSuspendAll();
	{
		if( pucAlignedHeap == NULL )
		{
			/* 获取内存堆首地址,并确保地址8字节对齐 */
			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;
}

内存释放源码分析:

void vPortFree( void *pv )
{
	/* 使用 heap1 一旦申请内存成功是不允许释放的,所以这里什么也不做 */
	( void ) pv;
	configASSERT( pv == NULL );
}

heap_2.c
内存分配源码分析:

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;

			/* 字节数8字节对齐 */
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
			{
				/* 未对齐,这里补充字节数对齐 */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}

		/* 检查申请的内存大小是否合理,合理则进行内存分配 */
		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
		{
			/* 获取堆首 */
			pxPreviousBlock = &xStart;
			/* 获取首个内存块地址 */
			pxBlock = xStart.pxNextFreeBlock;
			/* 遍历找到满足内存大小的内存块 */
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
			{
				/* 此内存块不满足,内存块地址递增继续遍历 */
				pxPreviousBlock = pxBlock;
				/* 获取下一个内存块的地址 */
				pxBlock = pxBlock->pxNextFreeBlock;
			}
			
			/* 找到了满足需求的内存块,并且内存块不能是链表尾
			   (这块申请到的内存可能大小比用户需要的还要大,比如用户只要100字节,而这块内存是1K,
			   则剩余900字节就会浪费,下面会先从空闲链表移除(1K),再把剩余的放入空闲链表(900字节)) */
			if( pxBlock != &xEnd )
			{
				/* 返回申请到的内存块首地址(包括内存块结构体大小) */
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
				
				/* 内存已经被申请了,所以需要将这个内存块从空闲内存块的链表中移除,
				   这里主要避免前面 while 下次遍历的时候遍历到此次已被使用的内存 */
				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;
}

源码显示,在分配之前会初始化内存堆(prvHeapInit),初始化分析如下:

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

	/* 获取内存堆首地址,并确保地址8字节对齐 */
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

	/* 初始化内存块结构体的起始项,指向空闲内存块链表首 */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* 初始化内存块结构体的结束项,指向空闲内存块链表尾 */
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;

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

源码会将释放或者未使用的内存块插入到空闲内存块链表中,以满足后面内存分配需求,内存块插入源码分析如下:

#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\
																					\
	/* 遍历链表,查找插入点 */																\
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		/* There is nothing to do here - just iterate to the correct position. */	\
	}																				\
																					\
	/* 将内存块插入到插入点 */																\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}

内存释放源码分析:

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
内存分配源码分析:

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

	/* 休眠任务调度器(为malloc提供保护) */
	vTaskSuspendAll();
	{
		/* 使用系统自带的分配函数申请内存 */
		pvReturn = malloc( xWantedSize );
		traceMALLOC( pvReturn, xWantedSize );
	}
	/* 恢复任务调度器 */
	( void ) xTaskResumeAll();

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

	return pvReturn;
}

内存释放源码分析:

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

	/* 休眠任务调度器(为malloc提供保护) */
	vTaskSuspendAll();
	{
		/* 使用系统自带的分配函数申请内存 */
		pvReturn = malloc( xWantedSize );
		traceMALLOC( pvReturn, xWantedSize );
	}
	/* 恢复任务调度器 */
	( void ) xTaskResumeAll();

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

	return pvReturn;
}

heap_4.c
内存分配源码分析:

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	/* 任务调度器休眠 */
	vTaskSuspendAll();
	{
		if( pxEnd == NULL )
		{
			/* 初始化内存堆 */
			prvHeapInit();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 需要申请的内存大小最高位不能为1,因为最高位用来表示内存有没有被使用
		   (意思就是 xWantedSize 加上结构体大小最大只能为0x7fffffff) */
		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 ) )
			{
				/* 从内存链表头开始遍历找到满足内存大小的内存块 */
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					/* 此内存块不满足,内存块地址递增继续遍历 */
					pxPreviousBlock = pxBlock;
					/* 获取下一个内存块的地址 */
					pxBlock = pxBlock->pxNextFreeBlock;
				}

				/* 找到了满足需求的内存块,并且内存块不能是链表尾
			   	   (这块申请到的内存可能大小比用户需要的还要大,比如用户只要100字节,而这块内存是1K,
			   	   则剩余900字节就会浪费,下面会先从空闲链表移除(1K),再把剩余的放入空闲链表(900)) */
				if( pxBlock != pxEnd )
				{
					/* 返回申请到的内存块首地址(包括内存块结构体大小) */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* 内存已经被申请了,所以需要将这个内存块从空闲内存块的链表中移除,
				   	   这里主要避免前面while下次遍历的时候遍历到此次已被使用的内存 */
					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();
					}

					/* 标记这个内存块已被使用,即最高位置1 */
					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;
}

和 heap2 一样,heap4 在分配之前会初始化内存堆(prvHeapInit),初始化分析如下:

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.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 用于记录最小的空闲内存块大小 */
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	
	/* xFreeBytesRemaining 用于表示内存堆剩余大小 */
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

	/* 0x01 << 31 = 0x80000000,用最高位来表示内存是否被使用,1为被使用了,0为未使用 */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

heap4 释放将释放或未使用的内存块插入到空闲内存块链表中的代码也有差别,主要差别就是 heap4 在这里做了合并内存的工作,一定程序上能减少碎片的产生,内存块插入源码分析如下:

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

	/* 遍历链表,查找插入点,内存块按照地址从低到高连接在一起 */
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
	{
		/* Nothing to do here, just iterate to the right position. */
	}

	//插入内存块,如果要插入的内存块可以和前一个内存块合并的话
	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;
		}
	}
	/* 地址不是连续的,执行和heap2同样的操作 */
	else
	{	
		/* 不能合并,将这个两个内存块链接起来,仅此而已,没法真正解决碎片 */
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
	}

	/* 如果不能进行合并就执行这个分支 */
	if( pxIterator != pxBlockToInsert )
	{
		/* 将两个内存块链接起来,和heap2方式一样 */
		pxIterator->pxNextFreeBlock = pxBlockToInsert;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

内存释放源码分析:

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

	if( pv != NULL )
	{
		/* 获取需要释放的内存空间 */
		puc -= xHeapStructSize;

		/* 防止编译器报错 */
		pxLink = ( void * ) puc;

		configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		configASSERT( pxLink->pxNextFreeBlock == NULL );

		/* 内存块之前正在被使用的话最最高位为1,则执行这个分支去正常的进行释放 */
		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/* 清除正在被使用的标志,即最高位清零 */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;

				/* 任务调度器休眠 */
				vTaskSuspendAll();
				{
					/* 剩余内存空间大小递增 */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					/* 将内存块添加到空闲内存块的列表中 */
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				/* 任务调度器恢复 */
				( void ) xTaskResumeAll();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}

heap_5.c
heap5 的内存分配和释放基本上和 heap4 一致,差别主要在于 heap5 在进行分配之前会先解决内部和外部内存空间的连接工作,在使用分配函数之间需要先调用 vPortDefineHeapRegions 这个接口函数来初始化和将两个内存堆建立联系,它的接口定义如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

其中 pxHeapRegions 这个形参需要客户去定义一下内外内存堆的地址和大小,它的定义如下:

typedef struct HeapRegion
{
	uint8_t *pucStartAddress;
	size_t xSizeInBytes;
} HeapRegion_t;

用户在使用 heap5 之前需要自行先定义,如下:

 HeapRegion_t xHeapRegions[] =
 {
 	{ ( uint8_t * ) 0x80000000UL, 0x10000 }, << Defines a block of 0x10000 bytes starting at address 0x80000000
 	{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, << Defines a block of 0xa0000 bytes starting at address of 0x90000000
 	{ NULL, 0 }                
 };

分别对应的是内存堆的地址和大小,这个接口函数具体的源码分析如下:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
BlockLink_t *pxFirstFreeBlockInRegion = NULL, *pxPreviousFreeBlock;
size_t xAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0;
size_t xAddress;
const HeapRegion_t *pxHeapRegion;

	configASSERT( pxEnd == NULL );

	/* 获取第一个内存段的地址 */
	pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );

	while( pxHeapRegion->xSizeInBytes > 0 )
	{
		/* 获取内存段大小 */
		xTotalRegionSize = pxHeapRegion->xSizeInBytes;
		
		/* 获取内存段地址 */
		xAddress = ( size_t ) pxHeapRegion->pucStartAddress;
		/* 地址对齐 */
		if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
		{
			xAddress += ( portBYTE_ALIGNMENT - 1 );
			xAddress &= ~portBYTE_ALIGNMENT_MASK;

			xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
		}

		/* 获取对齐后的地址 */
		xAlignedHeap = xAddress;

		if( xDefinedRegions == 0 )
		{
			/* 如果是第一个内存段则初始化起始内存块结构体,指向空闲内存块链表首 */
			xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
			xStart.xBlockSize = ( size_t ) 0;
		}
		else
		{
			configASSERT( pxEnd != NULL );

			configASSERT( xAddress > ( size_t ) pxEnd );
		}

		pxPreviousFreeBlock = pxEnd;

		/* 获取内存堆末尾的地址,并初始化pxEnd为末尾地址 */
		xAddress = xAlignedHeap + xTotalRegionSize;
		xAddress -= xHeapStructSize;
		xAddress &= ~portBYTE_ALIGNMENT_MASK;
		pxEnd = ( BlockLink_t * ) xAddress;
		pxEnd->xBlockSize = 0;
		pxEnd->pxNextFreeBlock = NULL;

		/* 一开始的时候将内存堆整个可用空间看成一个空闲内存块 */
		pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;
		pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;
		pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;

		if( pxPreviousFreeBlock != NULL )
		{
			/* 获取到第二个内存段,将前后两个内存段连接起来 */
			pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
		}

		/* 内存总大小递增 */
		xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;

		/* 准备获取下一个内存段,如果有的话 */
		xDefinedRegions++;
		pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
	}

	/* xMinimumEverFreeBytesRemaining 用于记录最小的空闲内存块大小 */
	xMinimumEverFreeBytesRemaining = xTotalHeapSize;
	
	/* xFreeBytesRemaining 用于表示内存堆剩余大小 */
	xFreeBytesRemaining = xTotalHeapSize;

	configASSERT( xTotalHeapSize );

	/* 0x01 << 31 = 0x80000000,用最高位来表示内存是否被使用,1为被使用了,0为未使用 */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}