FreeRTOS-内存管理源码分析

2023-05-16

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

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

这五种分配方式各有各的优势,用户可根据应用情况按需使用,在分析源码之前先了解一下内存管理的一些相关知识,其中内存碎片一直是内存管理致力于解决的一项问题,内存碎片是指频繁地请求和释放不同大小的内存,结果就是当再次要求分配连续的内存时导致申请失败,原因是由于之前内存块被释放后,存在空闲内存块大小不一,空闲的小内存块穿插于各个大内存块中间,当需要申请的内存总比这些空闲小内存块大的时候,这些小内存就永远无法得到使用,但它又一直占着空间,这些无法合并的小内存块就叫内存碎片,内存碎片最终会导致内存明明是够的,却无法分配成功的现象。入下图所示:
在这里插入图片描述
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 );
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS-内存管理源码分析 的相关文章

  • 四轴飞行器F450+Futaba 14SG+好盈电调油门行程校准

    F450 43 Futaba 14SG油门行程校准 装机前忘了进行油门校准 xff0c 装好后进行校准一开始老出现电机接收不到油门信号的提示音 因为Futaba 14SG也是新入手的控 xff0c 有些模式和操作不熟悉 xff0c 为此花了
  • NTKO控件安装:“不能装载文档控件,请在检查浏览器的选项中检查浏览器的安全设置”问题

    上礼拜手欠把OFFICE文档控件 NTKO给卸载了 xff0c 结果这周通知基金结题网上填写报告 本以为就装个控件 xff0c 没想到各种问题 xff0c 几经尝试终于解决 xff0c 总结如下 xff1a 1 问题 xff1a 不能装载文
  • Arduino - 串口操作函数与示例代码大全

    Arduino 串口操作函数与示例代码大全 本文总结了Arduino常用串口操作函数 xff0c 函数说明部分来源于Arduino 官网串口使用指南 xff0c 示例与实验部分为自编 本文是对Arduino串口操作函数的较全面总结 xff0
  • vs2010 出错:error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏

    LNK1123 转换到 COFF期间失败 文件无效或损坏 的解决方法 一 错误描述 之前写的程序隔段时间使用VS2010再次运行时出现如下错误 xff1a LINK fatal error LNK1123 转换到 COFF 期间失败 文件无
  • OpenCV—基本矩阵操作与示例

    OpenCV的基本矩阵操作与示例 OpenCV中的矩阵操作非常重要 xff0c 本文总结了矩阵的创建 初始化以及基本矩阵操作 xff0c 给出了示例代码 xff0c 主要内容包括 xff1a 创建与初始化 矩阵加减法 矩阵乘法 矩阵转置 矩
  • ubuntu16.04下利用ROS启动LPMS-CURS、CURS2等型号IMU;用imu控制turtlesim--教程

    文章目录 IMU型号及协议第一步 下载安装 LPsensor library第二步 设置ROS和carkin工作空间第三步 下载编译LPMS IMU的ROS驱动第四步 启动IMU xff08 可能也会遇到问题 xff09 遇到的问题1 ub
  • PixHawk飞控和Mission Planner地面站安装调试

    PixHawk飞控和Mission Planner地面站安装调试 PixHawk是著名飞控厂商3DR推出的新一代独立 开源 高效的飞行控制器 xff0c 前身为APM飞控 xff0c 不仅提供了丰富的外设模块和可靠的飞行体验 xff0c 有
  • 飞越650四轴无人机安装全程详解(多图)

    飞越650四轴无人机安装全程详解 xff08 多图 xff09 本文根据自己的安装实际过程 xff0c 总结了开箱后一个比较合理的650四轴无人机安装顺序 xff0c 以及各个步骤的注意事项 xff0c 主要内容包括 xff1a 系统基本配
  • DIY一个基于树莓派和Python的无人机视觉跟踪系统

    DIY 一个基于树莓派和Python的无人机视觉跟踪系统 无人机通过图传将航拍到的图像存储并实时传送回地面站几乎已经是标配 如果想来点高级的 在无人机上直接处理拍摄的图像并实现自动控制要怎么实现呢 xff1f 其实视觉跟踪已经在一些高端的消
  • windows环境下的Anaconda安装与OpenCV机器视觉环境搭建

    windows环境下的Anaconda安装与OpenCV机器视觉环境搭建 本文介绍win7和win10系统下通过Anaconda配置基于python语言的机器视觉编程环境 xff08 博主测试了两个系统下的安装基本相同 xff09 xff0
  • OpenCV—轮廓操作一站式详解:查找/筛选/绘制/形状描述与重心标注(C++版)

    OpenCV 轮廓操作一站式详解 xff1a 查找 筛选 绘制 形状描述与重心标注 C 43 43 版 轮廓 是定义或限定形状或对象的边或线 xff0c 是机器视觉中的常用的概念 xff0c 多用于目标检测 识别 等任务 关于OpenCV轮
  • 正太分布函数和反函数 标量值函数 (借鉴)

    标准正态分布函数 CREATE function dbo normcdf 64 p decimal 28 18 AS begin
  • 离散时间傅里叶变换(一)

    一 非周期信号的表示 xff1a 离散时间博里叶变换 1 1 离散时间傅里叶变换的导出 1 离散时间傅里叶变换对 要清楚推导过程 X ejw 称为离散时间傅里叶变换 xff0c 这一对式子就是离散时间傅里叶变换对 上式称为综合公式 xff0
  • HuskyLens摄像头系列 | 写给小学生看的视觉PID巡线算法

    Hello xff0c 大家好 xff0c 光天化日之下我又来撸狗了 距离上次撸狗已经过去了个把月时间了 xff0c 那么这次又有什么新惊喜呢 xff1f 先来看一下本期的演示视频吧 https www bilibili com video
  • 时空行为检测数据集 JHMDB & UCF101_24 详解

    文章目录 0 前言1 JHMDB1 1 基本情况1 2 数据准备以及标签详解 2 UDF101 242 1 基本情况2 2 数据准备与标签详解 3 数据集可视化代码 0 前言 现在常用的时空行为检测数据集只有AVA JHMDB UCF101
  • Lock与RLock的区别

    目录 往期推荐介绍区别一区别二 往期推荐 Python多线程的使用 Python线程池的使用 Python多线程的安全问题 B站同名 有温度的算法 已经上线 想观看视频讲解的同学 点击此处直达B站 介绍 在上节中为大家说明了线程访问临界资源
  • ROS 小技巧 - OpenCV4 与 CV_Bridge 配合使用

    1 现象 ROS默认的Python版本是3 3 xff0c 但我系统安装的是OpenCV4 5 如果直接在pkg中使用cv bridge和opencv4 5就会有问题 会有一些undefined reference问题 参考资料 xff1a
  • 【做题系统】后端设计

    目录 一 设计思路 1 项目背景 2 技术栈选择 二 系统设计 1 系统结构图 2 项目结构 3 数据建模 4 数据流图 5 主要流程图 三 问题及解决办法 1 实现安全登录 访问 2 数据库中的信息安全问题 3 Mybatis plus如
  • C/C++字符串查找函数

    C C 43 43 string库 xff08 string h xff09 提供了几个字符串查找函数 xff0c 如下 xff1a memchr在指定内存里定位给定字符strchr在指定字符串里定位给定字符strcspn返回在字符串str
  • ssh命令-manpage

    SSH Section User Commands 1 Index Return to Main Contents BSD mandoc NAME ssh OpenSSH SSH 客户端 远程登录程序 总览 SYNOPSIS ssh l l

随机推荐

  • 一小时做出Java实战项目——飞翔的小鸟

    学姐又来啦 xff0c 今日分享一个Java实战项目 飞翔的小鸟 相信大家都玩过这个游戏 xff0c 这个游戏陪伴了我们整整一个童年 xff0c 是我们青春的回忆 飞翔的小鸟 xff0c 游戏中玩家只需通过点击方向键操纵让小鸟避开绿色管道等
  • 搭建本地仓库源

    一 如何搭建仓库源 之前讲了定制ISO的方法 xff1a 使用chroot定制系统 xff0c 但有时候我们想自定义的安装包不在上游的仓库源中 xff0c 在我们本地应该怎么办呢 xff1f 如果我们将deb包拷贝到iso目录再安装有点过于
  • 节点操作案例

    1 下拉菜单 xff08 仿微博 xff09 lt DOCTYPE html gt lt html lang 61 34 en 34 gt lt head gt lt meta charset 61 34 UTF 8 34 gt lt me
  • document获取对象的三种三方法

    Document对象中有几个常用的方法 xff0c 我们在Dom简介中提到过 说到获取JavaScript对象的方法 xff0c 最常用的可能就是getElementById了 xff0c 它是Document中最常用的获取对象的方式之一
  • 程序员,最关键的跨越是什么?做到了月薪可能翻上几番~

    黑马程序员视频库 播妞微信号 xff1a boniu236 传智播客旗下互联网资讯 学习资源免费分享平台 作为一名程序员 xff0c 最关键的跨越是什么 xff1f 从普通程序员进阶为熟练开发者 xff0c 从熟练开发者跃升到技术专家或架构
  • 黑马程序员:3分钟带你读懂C/C++学习路线

    随着互联网及互联网 43 深入蓬勃的发展 xff0c 经过40余年的时间洗礼 xff0c C C 43 43 俨然已成为一门贵族语言 xff0c 出色的性能使之成为高级语言中的性能王者 而在今天 xff0c 它又扮演着什么样重要的角色呢 x
  • 数据归一化

    原文链接 xff1a 从公式出发 xff1a 什么是模型收敛的有效方法 xff1f 大家好 xff0c 我是泰哥 数据归一化在模型收敛中起着至关重要的作用 xff0c 从经典机器学习到深度学习的数据归一化方法是如何一步步演变的呢 xff1f
  • 【Python面试】 说说Python变量、函数、类的命名规则?

    最近公众号新增加了一个栏目 xff0c 就是每天给大家解答一道Python常见的面试题 xff0c 反正每天不贪多 xff0c 一天一题 xff0c 正好合适 xff0c 只希望这个面试栏目 xff0c 给那些正在准备面试的同学 xff0c
  • ​LeetCode刷题实战46:全排列

    算法的重要性 xff0c 我就不多说了吧 xff0c 想去大厂 xff0c 就必须要经过基础知识和业务逻辑面试 43 算法面试 所以 xff0c 为了提高大家的算法能力 xff0c 这个公众号后续每天带大家做一道算法题 xff0c 题目就从
  • Android硬件访问服务-Service

    Android有四大组件 xff1a 一 Activity 二 Service 三 Broadcast Receiver 四 Content Provider Service是Android中一个类 xff0c 它是Android四大组件之
  • android6.0第三方APP获得设备节点的访问权限

    之前使用android4 4的系统进行开发时 system app xff08 系统自带APP xff09 目录下的 app 可以直接访问 dev 目录下的设备节点 xff0c Android 5 0 以后 xff0c 因为采取了 SEAn
  • U-boot取消或修改启动延时bootdelay

    在我们的实际项目中都希望uboot尽量能够快速启动 xff0c 这就涉及到uboot的裁剪工作 xff0c 由于裁剪的工作量和内容比较多 xff0c 这里暂不描述 但是uboot有个启动延时bootdelay xff0c 在我们进入linu
  • uboot启动分析第一阶段(start.S)

    前面分析了启动脚本 Makefile mkconfig xff0c 接下来就是uboot的start S这个启动代码了 xff0c 下面是本章的平台介绍 xff1a 单板 xff1a 迅为4412开发板 Exynos 4412 SDRAM
  • Android使用串口(基于android-serialport-api)

    运行平台 xff1a CPU xff1a 全志V40 Android版本 xff1a 6 0 1 关于安卓设备上使用串口 xff0c 谷歌官方在github上有提供代码实例 xff0c 里面有JNI的代码和串口API的java文件 xff0
  • FreeRTOS-启动任务调度器源码分析

    本章基于FreeRTOS的启动任务调度器源码分析 xff0c 后续将会上传其它我对FreeRTOS的源码分析过程及理解 xff0c 首先来认识一下任务调度器 任务调度器 xff1a 任务调度器主要用于实现任务的切换 xff0c 任务并不是我
  • FreeRTOS-任务创建源码分析

    任务创建是FreeRTOS系统启动的第一个步骤 xff0c 前面在启动调度器的时候先创建了空闲任务 xff0c 然后再由调度器跳到任务里面去执行 任务创建函数里面做了很多的工作 xff0c 先会为任务堆栈和任务控制块分配内存并初始化它们 x
  • FreeRTOS-任务通知源码分析

    任务通知可用来代替信号量 消息队列 事件标志位 xff0c 而且使用任务通知的形式效率会更高 xff0c 它不需要像信号量那样创建队列和操作队列 xff0c 任务通知的存储变量来自任务控制块中 xff0c 当宏 configUSE TASK
  • Anaconda出现Collecting package metadata (current_repodata.json): failed错误

    安装包时出现这个错误 xff0c 个人怀疑是梯子使用出错 网上很多教程是换源等均没有解决问题 最后解决方法 xff1a 1 卸载clash for Windows xff08 不确定这一步是不是必须的 xff09 2 直接删除C Users
  • FreeRTOS-空闲任务、低功耗源码分析

    FreeRTOS在启动任务调度时会自动创建一个空闲任务 xff0c 空闲任务主要在系统没有其它任务或任务都处于挂起状态时执行 xff0c 它被系统设置为最低优先级 xff0c 不会去抢占其它高优先级的任务 xff0c 从而既能保证系统总有至
  • FreeRTOS-内存管理源码分析

    FreeRTOS 总共提供了5种内存分配方法 xff1a heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c 这五种分配方式各有各的优势 xff0c 用户可根据应用情况按需使用 xff0c 在分析源码之