FreeRTOS剖析——堆管理4

在使用单片机做项目时,难免会用到malloc等申请内存的操作,特别是在一些开源代码的移植上面。为此将FreeRTOS的内存管理拿出来研究。FreeRTOS有5种内存管理方式。这里是它的第四种。

 

一、在详细探讨管理方式前,有必要了解一下堆栈的对齐方式,它定义的对齐方式是8字节对齐的:

#define portBYTE_ALIGNMENT 8

#define portBYTE_ALIGNMENT_MASK ( 0x0007 )

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

#define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )

#define heapBITS_PER_BYTE ( ( size_t ) 8 )

#define heapADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )//减去需要对齐的字节数,对齐后能够确保堆起始地址还在数组范围内。



static unsigned char ucHeap[ configTOTAL_HEAP_SIZE ];

static const unsigned short heapSTRUCT_SIZE = ( ( sizeof ( xBlockLink ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );

static const size_t xTotalHeapSize = ( ( size_t ) heapADJUSTED_HEAP_SIZE ) & ( ( size_t ) ~portBYTE_ALIGNMENT_MASK );

8字节对齐大概有两个好处:

  1. 对内存读取粒度为偶数的cpu来说,加快了操作内存的速度。
  2. 对OS系统来说,栈必须是4、8字节对齐的。调用按AAPCS规则写的函数时,栈以8字节对齐能减少很多奇怪的问题出现。

 

二、在可以使用内存管理前,还要对相应的管理结构初始化。

static void prvHeapInit( void )
{
xBlockLink *pxFirstFreeBlock;
unsigned char *pucHeapEnd, *pucAlignedHeap;

	//确保堆以portBYTE_ALIGNMENT字节对齐
	pucAlignedHeap = ( unsigned char * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) );
	
	//堆块为空块,xStart作为空块的链表头,下一个空块链接到堆块。
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	//设置堆尾
	pucHeapEnd = pucAlignedHeap + xTotalHeapSize;
	pucHeapEnd -= heapSTRUCT_SIZE;
	pxEnd = ( void * ) pucHeapEnd;
	//configASSERT( ( ( ( unsigned long ) pxEnd ) & ( ( unsigned long ) portBYTE_ALIGNMENT_MASK ) ) == 0UL );
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;
	
	//设置空块数据结构
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = xTotalHeapSize - heapSTRUCT_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	//统计空块大小
	xFreeBytesRemaining -= heapSTRUCT_SIZE;

	/* Work out the position of the top bit in a size_t variable. */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

在初始化完成后,其内存分布如下:

三、申请、分配内存。

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

	//vTaskSuspendAll();
	{
		//初始化
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}

		/* Check the requested block size is not so large that the top bit is
		set.  The top bit of the block size member of the xBlockLink structure 
		is used to determine who owns the block - the application or the
		kernel, so it must be free. */
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			//实际申请的内存 = 需要申请的内存 + 块表的内存大小
			if( xWantedSize > 0 )
			{
				xWantedSize += heapSTRUCT_SIZE;

				//字节对齐
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
				}
			}

			if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
			{
				//查找空块链表,找到足够大的空间的那一块
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}
				
				/* If the end marker was reached then a block of adequate size 
				was	not found. */
				if( pxBlock != pxEnd )
				{
					//需要分配的内存的起始地址
					pvReturn = ( void * ) ( ( ( unsigned char * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

					//把待分配的空块踢出链表。此时被踢出的块大小 >= 实际分配的内存大小
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					//被踢出的块是否有足够的空间建立一个新的空块
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						//把多余的内在从被踢出的块中提取出来
						pxNewBlockLink = ( void * ) ( ( ( unsigned char * ) pxBlock ) + xWantedSize );

						//建立一个新的链表节点
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						//把新的链表节点插入到空块链表中
						//插入空块时,会检查块与块之间地址的连续性,连续则合成一个空块
						prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
					}

					xFreeBytesRemaining -= pxBlock->xBlockSize;

					//处理被踢出块的节点信息,以表内存被分配
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
			}
		}

		//traceMALLOC( pvReturn, xWantedSize );
	}
	//xTaskResumeAll();

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		//分配失败的处理
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;
}

分配内存的动态如下图:

  1. 把待分配的空块踢出链表。此时被踢出的块大小 >= 实际分配的内存大小。

     

  2. 把多余的内在从被踢出的块中提取出来,建立一个新的链表节点。

     

  3. 把新的链表节点插入到空块链表中。

四、释放内在。

void vPortFree( void *pv )
{
unsigned char *puc = ( unsigned char * ) pv;
xBlockLink *pxLink;

	if( pv != NULL )
	{
		//找到节点地址
		puc -= heapSTRUCT_SIZE;

		/* This casting is to keep the compiler from issuing warnings. */
		pxLink = ( void * ) puc;

		/* Check the block is actually allocated. */
		//configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		//configASSERT( pxLink->pxNextFreeBlock == NULL );
		
		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				//处理节点信息,以表未分配
				pxLink->xBlockSize &= ~xBlockAllocatedBit;

				//vTaskSuspendAll();
				{
					//插入空块链表
					//插入空块时,会检查块与块之间地址的连续性,连续则合成一个空块
					xFreeBytesRemaining += pxLink->xBlockSize;
					prvInsertBlockIntoFreeList( ( ( xBlockLink * ) pxLink ) );
					//traceFREE( pv, pxLink->xBlockSize );
				}
				//xTaskResumeAll();
			}
		}
	}
}

 

到此为止,FreeRTOS第四种内存管理方式的分析已经完成啦。

附源码:

链接:https://pan.baidu.com/s/1bmTqv8tnmqzrULolgRjGAw

提取码:ktba

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值