手把手教你FreeRTOS源码解析(一)——内存管理

这篇具有很好参考价值的文章主要介绍了手把手教你FreeRTOS源码解析(一)——内存管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

FreeRTOS中一共有5种内存分配的方法,分别在文件heap_1.c,heap_2.c,
heap_3.c,heap_4.c,heap_5.c种。
虽然标准C库中的 malloc()和 free()也可以实现动态内存管理,但是它有以下缺陷:
1、在小型嵌入式系统种效率不高。
2、线程不安全。
3、具有不确定性,每次执行的时间不同。
4、会导致内存碎片。

FreeRTOS源码解析集合(全网最详细)
手把手教你FreeRTOS源码解析(一)——内存管理
手把手教你FreeRTOS源码详解(二)——任务管理
手把手教你FreeRTOS源码详解(三)——队列
手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量

1、heap_1.c

在heap_1.c种只实现了pvPortMalloc,不允许内存释放,相当于“静态内存”,适用于一旦创建好任务就不会删除的应用,不会导致内存碎片。
动态内存分配需要一个内存堆,FreeRTOS中的内存堆为ucHeap[],
大小为configTOTAL_HEAP_SIZE,比如在heap_1.c中有以下代码:

/*分配内存堆空间(本质是一个大数组),任务所需要的堆空间将从该内存堆划分*/
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];//如果configAPPLICATION_ALLOCATED_HEAP为1,则需用户自行定义内存堆
#else
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

static size_t xNextFreeByte = ( size_t ) 0;

在FreeRTOS中内存堆的实质就是一个大数组,所有任务需要的堆空间将从该内存堆中划分,如果configAPPLICATION_ALLOCATED_HEAP = 1,则需要用户自行定义内存堆,否则将由编译器决定。

1.1 内存申请函数pvPortMalloc详解

heap_1 的内存申请函数pvPortMalloc()源码如下:

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

	/* 确保块与所需字节数对齐      
	portBYTE_ALIGNMENT=8--8字节对齐
	portBYTE_ALIGNMENT_MASK=0x0007*/
	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )//判断xWantedSize是否需要对齐
		{
			/* 加上对齐所需的字节数以后,总共需要的字节数 */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();
	{
		if( pucAlignedHeap == NULL )
		{
			/* Ensure the heap starts on a correctly aligned boundary. */
			pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		}

		/* Check there is enough room left for the allocation. */
		if( ( ( 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 )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}
/*-----------------------------------------------------------*/

大部分硬件访问内存对齐的数据速度会更快,因此RTOS中首先对内存进行对齐判断:

	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )//判断xWantedSize是否需要对齐
		{
			/* 加上对齐所需的字节数以后,总共需要的字节数 */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

Cortex-M3架构采用的8字节对齐,portBYTE_ALIGNMENT=8,对应的对齐掩码portBYTE_ALIGNMENT_MASK=0x0007,首先对xWantedSize进行判断是否需要对齐操作,如果需要对齐则补上对齐所需要的字节数,比如xWantedSize为13,则实际分配时候划分的空间大小应该为16,代码流程如下:
13:0000 0000 0000 1101
0x0007:0000 0000 0000 0111
xWantedSize&portBYTE_ALIGNMENT_MASK=101&0111=0101=5
实际的xWantedSize=xWantedSize+(portBYTE_ALIGNMENT -5)=16

关闭任务调度器,防止分配内存空间的过程被打乱。

vTaskSuspendAll();

第一次分配空间的时候,确保实际开始分配内存的地址是对齐的。

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

手把手教你FreeRTOS源码解析(一)——内存管理

如上图,假如内存堆的起始地址ucHeap为0x200006c4,起始地址不是对齐的,&ucHeap[ portBYTE_ALIGNMENT ]地址为0x200006c12,该地址
&(~portBYTE_ALIGNMENT_MASK)后,即将低3位置0后地址为0x200006c8,这个地址为实际开始分配内存的地址,前4个字节的空间用于对齐弃掉了。
configADJUSTED_HEAP_SIZE为减去对齐弃去字节数后,大约的实际堆空间,由上述分析可知,实际的堆空间大于等于configADJUSTED_HEAP_SIZE。

#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

检查当前空闲堆起始地址加上需要的空间是否小于总的内存堆空间–溢出检查

if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)

返回所申请内存堆的起始地址,索引xNextFreeByte指向新的空闲堆的起始地址

		pvReturn = pucAlignedHeap + xNextFreeByte;
		xNextFreeByte += xWantedSize;

重新开启任务调度器

		( void ) xTaskResumeAll();

内存堆申请失败的钩子函数,可由用户自己来实现

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

1.2 vPortFree、vPortInitialiseBlocks、xPortGetFreeHeapSize函数

由于heap_1.c为静态分配函数,无法释放内存空间,vPortFree函数中确实也无具体操作

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
	http://www.FreeRTOS.org for more information. */
	( void ) pv;
	/* Force an assert as it is invalid to call this function. */
	configASSERT( pv == NULL );
}

vPortInitialiseBlocks函数一般不需用调用,xNextFreeByte初始值已经位0;

xPortGetFreeHeapSize函数为获取空闲的堆空间大小

size_t xPortGetFreeHeapSize( void )
{
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

函数返回值为:总的堆空间大小减去已经使用了的堆空间大小,即剩余的堆空间

2、heap_2.c

heap_2.c允许内存的释放,申请内存时用了最佳匹配算法,但是其释放内存以后不会把相邻的空闲块合成一个大的块,当系统不断地申请和释放大小不同的内存块时,会造成内存碎片化,但是其效率也远高于malloc()和 free()。
heap_2.c的内存堆与heap_1.c相同,均是一个大数组!
heap_2.c为了能释放内存,引入了块的概念,申请的每一个内存空间就是一个内存块,内存块之间由单项链表连接起来。

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

手把手教你FreeRTOS源码解析(一)——内存管理
链表的实际结构也考虑了字节的对齐,这里heapSTRUCT_SIZE的实际大小为8个字节,为了保证字节对齐,则最小的块空间heapMINIMUM_BLOCK_SIZE为16。

static const uint16_t heapSTRUCT_SIZE	= ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
#define heapMINIMUM_BLOCK_SIZE	( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )

记录链表的头和尾

static BlockLink_t xStart, xEnd;

记录剩余的内存堆大小

static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;

2.1 内存堆初始化函数prvHeapInit

与heap_1.c相同,保证实际划分内存的起始地址对齐,此处不再过多赘述。

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;

xxStart指向空闲内存堆链表首

xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;

初始化一个大小为configADJUSTED_HEAP_SIZE的内存块,任务所需的内存将从该内存块划分

pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;

初始化完成后内存堆如下图所示:
手把手教你FreeRTOS源码解析(一)——内存管理

2.2 内存释放函数prvInsertBlockIntoFreeList

获取所释放内存的大小

xBlockSize = pxBlockToInsert->xBlockSize;

所释放的内存块由小到大排列起来,此处用for来遍历内存块,将释放的内存块插入到合适的位置。

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;

手把手教你FreeRTOS源码解析(一)——内存管理

2.3 内存分配函数pvPortMalloc

关闭任务调度器,防止其他任务打断内存分配。

vTaskSuspendAll();

第一次分配内存堆的时候需要调用内存堆初始化函数。

	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 ) );
		}
	}

由于释放的内存块是由小到大排列的,因此申请内存堆的时候从链表首开始遍历,直到找到合适大小的内存堆。

		pxPreviousBlock = &xStart;
		pxBlock = xStart.pxNextFreeBlock;
		while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
		{
			pxPreviousBlock = pxBlock;
			pxBlock = pxBlock->pxNextFreeBlock;
		}

找到合适大小的内存堆以后,pvReturn记录所申请内存堆的起始地址(由于每个内存块会包含一个头部结构体,因此地址需要往后偏移heapSTRUCT_SIZE),最后将申请好的内存堆从内存堆空间中剔除。

			pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

			/* This block is being returned for use so must be taken out of the
			list of free blocks. */
			pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

手把手教你FreeRTOS源码解析(一)——内存管理
如果申请的内存堆足够大,则将其划分成两个。

			/* 如果所申请的块足够大,则将其划分成两个 */
				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					/* pxNewBlockLink指向多余内存块的起始地址 */
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
					
					/*计算多于的内存堆大小*/
					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
					/*重新给申请的内存堆大小赋值*/
					pxBlock->xBlockSize = xWantedSize;

					/* 将多余的内存堆插入到空闲内存堆中*/
					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
				}
				/*计算剩余的内存堆大小*/
				xFreeBytesRemaining -= pxBlock->xBlockSize;

重新开启任务调度器。

xTaskResumeAll();

内存申请失败的钩子函数

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

3、heap_3.c

heap_3.c只对malloc和free进行了简单封装,保证FreeRTOS使用时是安全的。

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

	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 vPortFree( void *pv )
{
	if( pv )
	{
		vTaskSuspendAll();
		{
			free( pv );
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();
	}
}

pvPortMalloc,vPortFree中采用挂起任务调度、释放让任务调度来保证线程安全。

4、heap_4.c

heap_4.c在heap_2.c的基础上,其在释放内存的时候增加了相邻内存块合并算法,减少了碎片的产生。

4.1 内存堆初始化函数prvHeapInit

确保内存堆起始地址对齐

uxAddress = ( size_t ) ucHeap;
	/*字节对齐*/
	if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
	{
		/*确保划分内存堆的起始地址对齐,与heap_1.c heap_2.c相同*/
		uxAddress += ( portBYTE_ALIGNMENT - 1 );
		uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
		/*计算实际的总内存堆大小=总的内存堆大小-字节对齐弃去的大小*/
		xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
	}
	/*pucAlignedHeap为实际的起始地址	*/
	pucAlignedHeap = ( uint8_t * ) uxAddress;

pxEnd指向空闲内存堆链表首,并将其插入到堆空间的尾部

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

	/* 因为只有一个大的空闲内存块,因剩余的最小内存块大小即为pxFirstFreeBlock->xBlockSize */
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	/*记录剩余的空闲内存块大小*/
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

初始化xBlockAllocatedBit,用于标记内存块的类型(是否空闲),xBlockAllocatedBit初始化完成后为0x80000000

xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );

内存堆初始化完成后如下图:
手把手教你FreeRTOS源码解析(一)——内存管理

4.2 内存堆释放函数prvInsertBlockIntoFreeList

遍历空闲内存块链表,将释放的内存块按照地址由低到高排列.

/* Iterate through the list until a block is found that has a higher address
than the block being inserted. */
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
		{
			/*如果下一个内存块为pxEnd,则直接指向*/
			pxBlockToInsert->pxNextFreeBlock = pxEnd;
		}
	}
	else
	{
		/*如果地址不连续就直接指向下一个内存块*/
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
	}

注意:释放内存块的时候要连续判断两次,第一次判断释放的内存块是否能与其前面的内存块合并,第二次判断合并后的内存块是否能与其后面的内存块合并,如果不能则直接用链表连接起来,因此也无法避免产生一些内存碎片。

/* 如果内存块未能与其前面的内存块合并,同样直接用链表连接起来 */
if( pxIterator != pxBlockToInsert )
{
	pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
	mtCOVERAGE_TEST_MARKER();
}

4.3 内存堆申请函数pvPortMalloc

关闭任务调度器,确保线程安全。

vTaskSuspendAll();

如果第一次申请内存堆,则需要初始化内存堆

	if( pxEnd == NULL )
	{
		prvHeapInit();
	}

xBlockAllocatedBit初始值位0x80000000,先判断所申请的内存是否过大。

if( ( xWantedSize & xBlockAllocatedBit ) == 0 )

与heap_2.c相同,实际申请的内存堆大小需要加上头部结构体,并进行字节对齐。

if( xWantedSize > 0 )
			{
				/*增加头部结构体大小*/
				xWantedSize += xHeapStructSize;

				/* 确保字节对齐. */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* Byte alignment required. */
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
					configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
				}

判断剩余的内存是否足够(检查堆溢出)

if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )

从低地址到高地址遍历整个链表,直至找到合适大小的内存堆。

			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			/*从低地址到高地址遍历整个链表*/
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
			{
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;
			}

如果找到了合适大小的内存堆,则记录空闲内存堆的起始地址。

				*如果找到了合适大小的内存堆 */
				if( pxBlock != pxEnd )
				{
					/* pvReturn记录所申请堆空间的起始地址,由于存在头部结构体,因此地址需要往后偏移xHeapStructSize */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* 将申请的内存堆从总的内存堆中剔除 */
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					/* 如果实际申请到的内存堆较大,可以将其划分成两个,多余的内存堆重新添加至总的空闲内存堆中*/
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						/* pxNewBlockLink指向多余的内存堆起始地址 */
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
						configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );

						/* 计算多余的内存堆大小,以及重新给所申请的内存堆赋值 */
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						/* 将多余的内存堆重新添加至空闲内存堆中 */
						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}

更新剩余空闲内存堆大小以及最小空闲内存堆大小。

				/*更新剩余的空闲内存堆大小,即减去此处申请所划去的内存堆*/
					xFreeBytesRemaining -= pxBlock->xBlockSize;
					/*更新执行过程中剩余的最小空闲内存堆大小*/
					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}

申请内存堆成功,标记该内存堆。该内存堆已经从总内存堆中划分出,因此将该内存堆指向空

				pxBlock->xBlockSize |= xBlockAllocatedBit;
				pxBlock->pxNextFreeBlock = NULL;

重新打开任务调度器

( void ) xTaskResumeAll();

4.4 内存堆释放函数vPortFree

函数vPortFree在prvInsertBlockIntoFreeList的基础上,进行了再一次的封装。

每个内存块头部有一个BlockLink_t结构体,因此puc向前偏移xHeapStructSize,指向结构体头部

puc -= xHeapStructSize;

判断需要释放的内存堆是否是有效内存堆

if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )

重新标记内存堆为空闲内存堆

pxLink->xBlockSize &= ~xBlockAllocatedBit;

将内存堆重新插入到空闲内存堆中

/*关闭任务调度器,防止释放内存过程被打断*/
				vTaskSuspendAll();
				{
					/* 更新剩余的空闲内存堆大小 */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					/*将内存堆插入到空闲内存堆中*/
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				/*重新打开任务调度器*/
				( void ) xTaskResumeAll();

5、heap_5.c

heap_5.c在heap_4.c的基础上,实现了跨越多个非相邻的内存区域来申请内存,即可以同时从多种存储介质上申请内存。文章来源地址https://www.toymoban.com/news/detail-471753.html

到了这里,关于手把手教你FreeRTOS源码解析(一)——内存管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【图解数据结构】顺序表实战指南:手把手教你详细实现(超详细解析)

    🌈个人主页: 聆风吟 🔥系列专栏: 图解数据结构、算法模板 🔖少年有梦不应止于心动,更要付诸行动。 线性表(linear list):线性表是一种数据结构,由n个具有相同数据类型的元素构成一个有限序列。 线性表可以用数组、链表、栈等方式实现,常见的线性表有数组、链

    2024年01月22日
    浏览(59)
  • 手把手教你使用Python写贪吃蛇游戏(pygame,附源码)

    贪吃蛇游戏是有史以来最受欢迎的街机游戏之一。在这个游戏中,玩家的主要目标是在不撞墙或不撞墙的情况下抓住最大数量的水果。在学习 Python 或 Pygame 时,可以将创建蛇游戏视为一项挑战。这是每个新手程序员都应该接受的最好的初学者友好项目之一。学习构建视频游戏

    2024年02月16日
    浏览(44)
  • 手把手教你跑一个UVM_demo(含源码)(一键复现)

    笔记内容对应张强所著的《UVM实战》。该书对UVM使用进行了比较详尽的介绍,并在前言中提供了书籍对应源码的下载网址,是一本带有实操性的书籍,对新手比较友好,推荐阅读。 学习完第二章后,我们对UVM已经有了一个基本概念,但如何在自己的电脑上跑一个UVM deme,让知

    2024年02月09日
    浏览(47)
  • 手把手教你在windows下源码编译Open3D

    首先不建议windows下源码编译,需要用C++的可以直接下载官网编译好的版本。熟悉vcpkg的,可以把open3d加到vcpkg使用,参考该博客。 cmake =3.20 python =3.6.0 visual studio =2017 Github下载open3d源码 打开源码,新建build文件夹 填写你的VS版本,例vs2022(17),open3d_install_directory使用当前路径

    2024年02月05日
    浏览(51)
  • 手把手教你基于【SpringBoot+MyBatis】实现员工管理系统‍【附完整源码】

    Hello,你好呀,我是 灰小猿 ,一个超会写 BUG 的程序猿🙊! 近期在学习springboot框架相关的内容,相比于SSM, SpringBoot最大的特点就是集成了Spring和SpringMVC,让之前繁琐的配置工作变得更加简洁, 同时对于业务逻辑层的处理也更加的友好, 所以今天就使用 SpringBoot整合MyBati

    2023年04月08日
    浏览(44)
  • 手把手教你如何在Linux下写进度条小程序(附源码)

    录屏2023 进入ProgressBar这个目录之后,使用ls命令查看是否创建成功 编写makefile文件是为了,使用make命令构建我们的.c文件,生成可执行程序 把源码复制粘贴到我们对应的文件中 使用vim指令先打开头文件 按小写i,进入插入模式(Insert),直接拷贝上去 再按esc,直接输入:wq(

    2024年02月04日
    浏览(45)
  • 手把手教你使用Python实现推箱子小游戏(附完整源码)

    我们这个项目是一个基于Python实现的推箱子小游戏,名叫Sokoban: 这个游戏的目的是让玩家,也就是大写的 P ,推着箱子 # ,填充用小写的 o 标记的地面上的洞 该版本的Sokoban的规则如下: 游戏在矩形的二维网格上举行,其 原点(0,0) 位于左上方 网格上的每个单元格可以随时包

    2024年02月03日
    浏览(49)
  • FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)

    最近趁热打铁做了一个关于STM32与FPGA通信并且控制高速DA模块产生不同频率信号的正弦波、方波、三角波和锯齿波的项目,从中收获到了很多东西,也踩了一些雷和坑,将分为几篇文章将整个过程分享出来。 这一次准备分享的是对串口数据的解析和赋值。解析的数据由STM32发

    2024年02月06日
    浏览(37)
  • 【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 前几天瑶瑶子学习了Go语言的基础语法知识,那么今天我们就写个

    2024年02月06日
    浏览(53)
  • 【换脸详细教程】手把手教你进行AI换脸:换脸流程及源码详解

    最近AI换脸貌似比较火爆,就稍微研究了一下相关了内容。AI换脸是一个娱乐性比较强的应用,这种错位感让人觉得非常有趣。很多人都以为这是什么黑科技,但这里想告诉大家,AI换脸其实很简单。只需要你会一点Python基础,就可以实现自己的AI换脸程序。 本文将详细介绍图

    2024年02月15日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包