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

    物联沃分享整理
    物联沃-IOTWORD物联网 » FreeRTOS源码解析(一):详解内存管理

    发表评论