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

2023-05-16

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
    • 1.1 内存申请函数pvPortMalloc详解
    • 1.2 vPortFree、vPortInitialiseBlocks、xPortGetFreeHeapSize函数
  • 2、heap_2.c
    • 2.1 内存堆初始化函数prvHeapInit
    • 2.2 内存释放函数prvInsertBlockIntoFreeList
    • 2.3 内存分配函数pvPortMalloc
  • 3、heap_3.c
  • 4、heap_4.c
    • 4.1 内存堆初始化函数prvHeapInit
    • 4.2 内存堆释放函数prvInsertBlockIntoFreeList
    • 4.3 内存堆申请函数pvPortMalloc
    • 4.4 内存堆释放函数vPortFree
  • 5、heap_5.c

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

在这里插入图片描述

如上图,假如内存堆的起始地址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;

在这里插入图片描述
链表的实际结构也考虑了字节的对齐,这里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;

初始化完成后内存堆如下图所示:
在这里插入图片描述

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;

在这里插入图片描述

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;

在这里插入图片描述
如果申请的内存堆足够大,则将其划分成两个。

			/* 如果所申请的块足够大,则将其划分成两个 */
				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 );

内存堆初始化完成后如下图:
在这里插入图片描述

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的基础上,实现了跨越多个非相邻的内存区域来申请内存,即可以同时从多种存储介质上申请内存。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你FreeRTOS源码解析(一)——内存管理 的相关文章

  • Intel Realsense T265 在ubuntu下的环境配置

    Intel Realsense T265 在ubuntu下的环境配置 一 T265介绍二 realsense SDK 安装配置1 注册服务器的公钥2 将服务器添加到存储库列表3 安装所需的库 xff0c 开发者和调试包5 插上T265打开
  • SLAM图优化一

    前言 SLAM问题的处理方法主要分为滤波和图优化两类 滤波的方法中常见的是扩展卡尔曼滤波 粒子滤波 信息滤波等 xff0c 熟悉滤波思想的同学应该容易知道这类SLAM问题是递增的 实时的处理数据并矫正机器人位姿 比如基于粒子滤波的SLAM的
  • 编程实现MapReduce操作

    文章目录 一 MapReduce的WordCount应用二 Partitioner 操作三 xff0e 排序实现四 xff0e 二次排序实现五 hadoop实现六 出现的问题与解决方案 提示 xff1a 以下是本篇文章正文内容 xff0c
  • React项目中TS报错解决方案

    Umi amp amp React amp amp Vue3 amp amp TS报错解决方案总结 个人向 Redux开发工具报错window下没有某属性 解决方案 项目根目录创建global d ts文件 span class token
  • Nvidia NX 运行vins-fusion + DenseSurfelMapping

    Nvidia NX 运行vins fusion 43 DenseSurfelMapping 实现姿态估计和稠密建图 xff0c 记录自用 参考博客 xff1a 使用Realsense D435i运行VINS Fusion并建图 从零开始使用
  • UR5机械臂与realsense相机在Gazebo仿真环境下的手眼标定(眼在手上)

    简介 这是一个Gazebo仿真环境下利用UR5机械臂和realsense相机进行手眼标定的教程 xff08 眼在手上 xff09 准备相关文件 span class token constant UR5 span git clone htt
  • 六,WiFi天猫精灵零配详解

    1 xff0c IOT设备配网方法 smartconfig手机热点配网设备热点配网路由器热点配网扫描二维码配网 2 xff0c 什么是零配 概念 xff1a 让已连网的设备告诉未配网的设备路由器的SSID和密码 xff08 天猫精灵语音寻找
  • 蓝牙Mesh

    1 蓝牙mesh介绍 蓝牙Mesh网络模型 xff1a 蓝牙Mesh提高灵活度 xff1a 代理节点 xff08 Proxy xff09 低功耗节点 xff08 Low Power xff09 转发节点 xff08 Relay xff09
  • 实践:设计SLAM系统

    实现一个双目视觉里程计在Kitti数据集中的运行效果 很有必要多看几遍的例程 这个视觉里程计由一个光流追踪的前端和一个局部BA的后端组成 双目只需单帧就可初始化 xff0c 双目存在3D观测 xff0c 实现效果比单目好 程序 xff1a
  • Pytorch 中 Embedding 类详解

    在 NLP 领域 xff0c 可以使用 Pytorch 的 torch nn Embeding 类对数据进行词嵌入预处理 关于词嵌入的解释这里就不做解释咯 xff0c 不明白的阔以先出门左拐找百度 重点说下这个 Embeding 类怎么用的
  • 工业相机测距开发(2):实战篇

    前言 本文将不再涉及原理部分 xff0c 想要了解基础知识的话 xff0c 请看上一篇的文章 xff0c 我们使用的是opencv的里面的函数 xff0c 这里面也是重点看这个函数们 xff0c 我们通过这个函数来得到外参 xff0c 在通
  • LeetCode406:根据身高重建队列

    要求 假设有打乱顺序的一群人站成一个队列 xff0c 数组 people 表示队列中一些人的属性 xff08 不一定按顺序 xff09 每个 people i 61 hi ki 表示第 i 个人的身高为 hi xff0c 前面 正好 有 k
  • LeetCode416:分割等和子集

    要求 给你一个 只包含正整数 的 非空 数组 nums 请你判断是否可以将这个数组分割成两个子集 xff0c 使得两个子集的元素和相等 思路 做一个等价转换 xff1a 是否可以从输入数组中挑选出一些正整数 xff0c 使得这些数的和 等于
  • LeetCode437:路径总和III

    要求 给定一个二叉树的根节点 root xff0c 和一个整数 targetSum xff0c 求该二叉树里节点值之和等于 targetSum 的 路径 的数目 路径 不需要从根节点开始 xff0c 也不需要在叶子节点结束 xff0c 但是
  • LeetCode438:找到字符串中所有字母异位词

    要求 给定两个字符串 s 和 p xff0c 找到 s 中所有 p 的 异位词 的子串 xff0c 返回这些子串的起始索引 不考虑答案输出的顺序 异位词 指由相同字母重排列形成的字符串 xff08 包括相同的字符串 xff09 思路 方法一
  • LeetCode448:找到所有数组中消失的数字

    要求 给你一个含 n 个整数的数组 nums xff0c 其中 nums i 在区间 1 n 内 请你找出所有在 1 n 范围内但没有出现在 nums 中的数字 xff0c 并以数组的形式返回结果 思路 可以把数组中的元素与索引建立一一对应
  • LeetCode461:汉明距离

    要求 两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目 给你两个整数 x 和 y xff0c 计算并返回它们之间的汉明距离 思路 方法一 xff1a 使用内置位计数功能 大多数编程语言都内置了计算二进制表达中 1 的数
  • LeetCode494:目标和

    要求 给你一个整数数组 nums 和一个整数 target 向数组中的每个整数前添加 43 或 xff0c 然后串联起所有整数 xff0c 可以构造一个 表达式 xff1a 例如 xff0c nums 61 2 1 xff0c 可以在 2
  • Java中Integer.parseInt()方法最全解析

    介绍 是Integer类中提供的一个静态方法 用于将传入的string类型字符串根据要求转为相应进制的int值 如果没有要求进制则按10进制计算 xff0c 属于java lang包的 xff1b 使用讲解 1 parseInt Strin
  • LeetCode538:把二叉搜索树转换为累加树

    要求 给出二叉 搜索 树的根节点 xff0c 该树的节点值各不相同 xff0c 请你将其转换为累加树 xff08 Greater Sum Tree xff09 xff0c 使每个节点 node 的新值等于原树中大于或等于 node val

随机推荐

  • python 最小堆类型: heapq

    目录 1 heapq 的常用方法 2 几个例子 a 最小堆的创建以及增删 b 如何使用 heapq 创建最大堆 c 获取第 k 大 第 k 小数据 d 列表中的元素是元组 heapq 是 python 的一个库 xff0c 用一个列表来维护
  • LeetCode543:二叉树的直径

    要求 给定一棵二叉树 xff0c 你需要计算它的直径长度 一棵二叉树的直径长度是任意两个结点路径长度中的最大值 这条路径可能穿过也可能不穿过根结点 题目解析 这里返回的是 xff1a 两结点之间的路径长度是以它们之间边的数目表示 最大的直径
  • LeetCode560:和为K的子数组

    要求 给你一个整数数组 nums 和一个整数 k xff0c 请你统计并返回 该数组中和为 k 的连续子数组的个数 思路 方法一 xff1a 暴力解 先固定左边界 xff0c 再去枚举右边 span class token keyword
  • LeetCode581:最短无序连续子数组

    要求 给你一个整数数组 nums xff0c 你需要找出一个 连续子数组 xff0c 如果对这个子数组进行升序排序 xff0c 那么整个数组都会变为升序排序 请你找出符合题意的 最短 子数组 xff0c 并输出它的长度 思路 我们可以假设把
  • LeetCoed617:合并二叉树

    要求 给你两棵二叉树 xff1a root1 和 root2 想象一下 xff0c 当你将其中一棵覆盖到另一棵之上时 xff0c 两棵树上的一些节点将会重叠 xff08 而另一些不会 xff09 你需要将这两棵树合并成一棵新二叉树 合并的规
  • LeetCode621:任务调度器

    要求 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表 其中每个字母表示一种不同种类的任务 任务可以以任意顺序执行 xff0c 并且每个任务都可以在 1 个单位时间内执行完 在任何一个单位时间 xff0c CPU 可以完成
  • LeetCode647:回文子串

    要求 给你一个字符串 s xff0c 请你统计并返回这个字符串中 回文子串 的数目 回文字符串 是正着读和倒过来读一样的字符串 子字符串 是字符串中的由连续字符组成的一个序列 具有不同开始位置或结束位置的子串 xff0c 即使是由相同的字符
  • LeetCode739:每日温度

    要求 给定一个整数数组 temperatures xff0c 表示每天的温度 xff0c 返回一个数组 answer xff0c 其中 answer i 是指对于第 i 天 xff0c 下一个更高温度出现在几天后 如果气温在这之后都不会升高
  • 八大排序算法

    介绍 排序也称排序算法 Sort Algorithm xff0c 排序是将一组数据 xff0c 依指定的顺序进行排列的过程 排序分类 1 内部排序 指将需要处理的所有数据都加载到内部存储器 内存 中进行排序 2 外部排序法 数据量过大 xf
  • 时间复杂度和空间复杂度详解及排序算法复杂度

    时间复杂度 度量一个程序 算法 执行时间的两种方法 1 事前估算法 通过分析某个算法的时间复杂度来判断哪个算法更优 2 事后统计法 这种方法可行 xff0c 但是有两个问题 xff1a 一是要想对设计的算法的运行性能进行评测 xff0c 需
  • Java反编译工具JAD的安装与简单使用

    jad介绍 jad 是一个使用非常广泛的 Java 反编译工具 可以将java编译后的class文件反编译成对应的java文件 下载地址 JAD下载地址 xff0c 点击跳转https varaneckas com jad 按照自己的需求
  • python Counter() 函数

    目录 1 以统计列表中的词频为例 2 寻找出现次数最多的 k 个数 刷 leetcode 时发现了可以很方便地统计词频的 Counter 函数 xff0c 有了这个函数就不用手动的使用 for 循环来手动统计词频啦 xff01 Counte
  • C++11 新特性简介

    1 auto auto是旧关键字 xff0c 在C 43 43 11之前 xff0c auto用来声明自动变量 xff0c 表明变量存储在栈 xff0c 很少使用 在C 43 43 11中被赋予了新的含义和作用 xff0c 用于类型推断 a
  • Java反射(Reflex)机制

    反射概述 Reflection 反射 是Java被视为动态语言的关键 xff0c 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息 xff0c 并能直接操作任意对象的内部属性及方法 加载完类之后 xff0c 在
  • 偏航角、俯仰角、滚动角

    偏航角 俯仰角 滚动角 在姿态估计中 xff0c 常常会提到三个概念 xff0c 就是偏航角 俯仰角和滚动角 姿态估计是物体在三维空间内方向的表征 通常描述物体姿态以大地作为参考系 xff08 标准坐标系 xff09 将世界坐标系 xff0
  • 定时器与计数器的区别

    曾经我在北方工业大学复试的时候 xff0c 有个老师问了我个问题 他说同学你好 xff0c 我看你前面一直都在说深度学习的问题 xff0c 请问你对单片机了解吗 xff1f xff0c 请问定时器与计数器的区别是什么 xff1f 我告诉他
  • 编译make px4_fmu-v2_default upload时,报错ERROR Board can accept larger flash images (2080768 bytes)

    编译make px4 fmu v2 default upload时 xff0c 报错ERROR Board can accept larger flash images 2080768 bytes than board config 103
  • vue中实现axios封装

    vue中实现axios封装 为什么要进行axios封装vue项目安装axios封装前的get和post请求封装后的get和post请求 为什么要进行axios封装 实际开发过程中可能有几种环境 xff0c 开发环境 xff0c 测试环境 x
  • 项目实训—场景划分(一)

    1 什么是场景 xff08 Scene xff09 场景作为电影讲故事的关键单元 xff0c 包含了演员的复杂活动及其在物理位置上的互动 识别场景的组成和语义是视觉理解复杂长视频 xff08 如电影 电视剧 娱乐节目和综艺节目 xff09
  • 手把手教你FreeRTOS源码解析(一)——内存管理

    FreeRTOS中一共有5种内存分配的方法 xff0c 分别在文件heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c种 虽然标准C库中的 malloc 和 free 也可以实现动态内存管理 xff0c