FreeRTOS(V8.0.1)系统之Heap_2


#include <stdlib.h>

#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "task.h"

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE


#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

// 在使用之前初始化堆结构体
static void prvHeapInit( void );

//为堆分配空间 ucHeap[17 * 1024] 
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

//定义一个连接链表结构,按照其空闲块的大小连接。
typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	//链表中下一个空闲块指针
	size_t xBlockSize;					//空闲块的大小
} BlockLink_t;


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;//创建xStart和xEnd链表连接体,用来指示开始链表和结束链表

static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;//跟踪剩余空闲字节数目,但是对于存储碎片没有提示

/* STATIC FUNCTIONS ARE DEFINED AS MACROS TO MINIMIZE THE FUNCTION CALL DEPTH. */
 //插入一个空闲块到空闲块链表--其遵守大小排序。小的空闲块在链表开始,大的空闲块在链表末尾。
#define prvInsertBlockIntoFreeList( pxBlockToInsert )															\
{																								\
BlockLink_t *pxIterator;																				\
size_t xBlockSize;																					\
	/*获取要插入空闲块的大小*/													\
	xBlockSize = pxBlockToInsert->xBlockSize;																\
																								\
																								\
	/*  遍历链表寻找一块比我们要插入的区域还要大的区域*/		\
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )	\
	{																							\
		/*循环内部不做任何事情,只是在不断遍历整个链表*/		\
	}																							\
																								\
	/* 更新链表,将要插入的区域插入到链表中正确位置*/			\
																								\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;											\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;															\
}
/*-----------------------------------------------------------*/

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;//标志是否首次使用pvPortMalloc
void *pvReturn = NULL;

	vTaskSuspendAll();//将任务调度器挂起
	{
		//若是初次使用malloc,那么需要初始化block链表
		if( xHeapHasBeenInitialised == pdFALSE )
		{
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}
		//第1次调用pvPortMalloc(),那么需要初始化一些关键量,主要是在xStart和xEnd之间将xHeap.ucHeap插进去,下面是一个简单的单向链接图示
		//xStart->xHeap.ucHeap->xEnd->NULL

		//对heapSTRUCT_SIZE的定义是这样的: 
		//static const uint16_t heapSTRUCT_SIZE	= ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
		//我觉得这句话讲不出什么道理来,可能和linux中page之间的hole空间类似,是为了防止越界之类在空间上做的额外申请,
		//因为xBlockLink数据区作为管理本段内存所使用的关键域,如果因为数据操作越界而被修改,那是相当可怕的尤其在动态内存申请、释放比较频繁的时候
		//最后计算效果是这样的:如果sizeof( xBlockLink )=7[注:当然编译器不会让它是这个值,怎么着也是2的倍数,这里只是做个极端的例子(gliethttp)],
		//那么heapSTRUCT_SIZE = 7+3=10,不过实际在at91sam7s64上sizeof( xBlockLink )=8,所以heapSTRUCT_SIZE=8

		/*要求数量是增强的所有它除了要求的字节数外还包含一个BlockLink_t结构体*/
		if( xWantedSize > 0 )
		{
			xWantedSize += heapSTRUCT_SIZE;
			/*确保这个块总是对齐到要求的字节数上 。*/
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
			{
			//和方法1中一样,将xWantedSize调整为4的整倍数。字节对齐,at91sam7s64默认使用4字节对齐所以下面折行程序,将xWantedSize扩展成4字节的倍数,
			//比如:xWantedSize=13,那么xWantedSize=13+(4-13%4)=13+3=16(gliethttp)
				/* Byte alignment required. */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}

		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )//若想要的空闲块的大小在0~configTOTAL_HEAP_SIZE
		{//xWantedSize是一个合法的数值,那么下面将检测自己是否还有这么大的空闲块,因为在内存释放时不提供小内存块合并整理机制,
			//所以小内存块会随着时间和不同大小内存块。动态申请、释放的频繁发生最终变的越来越多,直到没有大内存可以申请到,全部都是小内存为止。
			//可以将相邻的小块内存合并成大块内存,很好的解决了内存合并生成大内存块的问题。
			//当然这种方法,可能远远不如linux中的Buddy伙伴算法灵活,但是在嵌入式系统中应该能够满足用户对内存的基本需求了。
			//[其实可以采用以前一篇文章《一种轻巧的“内存动态分配管理机制”》中提到的动态内存分配和申请方式,
			//文章地址http://blog.chinaunix.net/u1/38994/showart_351550.html]
			pxPreviousBlock = &xStart;//从空闲内存链表头开始找
			pxBlock = xStart.pxNextFreeBlock;
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				//空闲块的大小小于需要的大小并且存在下一个空闲块
			{
				pxPreviousBlock = pxBlock;			//将每次寻找到的空闲块都保存到pxPreviousBlock中,以备后面调用
				pxBlock = pxBlock->pxNextFreeBlock;	//获取下一个空闲块用以检测
			}

			if( pxBlock != &xEnd ) //若我们没有到整个内存的最后就找到我们需要的空闲块
			{
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
				//pxPreviousBlock->pxNextFreeBlock~pxPreviousBlock->pxNextFreeBlock+heapSTRUCT_SIZE之间的空间存放管理数据pxNextFreeBlock和xBlockSize
				pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;//把本pxBlock从空闲链表上摘下

				//#define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )
				//如果本pxBlock剩余的字节数大于heapMINIMUM_BLOCK_SIZE,即:还可以用来申请heapSTRUCT_SIZE个字节数据
        //那么切割本pxBlock内存块(注意:xWantedSize是已经包含heapSTRUCT_SIZE的了[gliethttp]),
				//pxNewBlockLink为切割出来的新的空闲内存块首地址,已经4字节对齐
				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );//将新的生成的空闲块的地址存起来
					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;//计算新空闲内存块包括heapSTRUCT_SIZE控制域和数据域的总大小
					pxBlock->xBlockSize = xWantedSize;							   //将申请到的内存块的大小,填入内存块控制域
					//把切割出来的内存块添加到空闲内存链表xStart~xEnd之间(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
          //也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块,在内存释放回收的时候,并不能自动进行内存合并整理,
          //也就是空闲内存块不能变大,最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) ); //将新生成的空闲块重新插入到链表中[链表内部按空闲块的大小排序]
				}
				
				xFreeBytesRemaining -= pxBlock->xBlockSize;//剩余空闲块比特数更新
			}
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	(void)xTaskResumeAll();
	//在运行上面临界区的程序时,可能有任务需要调度,但因为调度器的挂起而没有被调度,只是给出了登记,而这个xTaskResumeAll函数就是要把放进
	//xPendingReadyList链表中的任务节点转移到真正的就绪链表pxReadyTasksLists里面,如果任务是因为tick缺失或者因为在恢复实际走过的滴答数时有
	//任务需要抢占CPU,则 xAlreadyYielded 都为真,从而导致下面不会运行,如果没有被抢占也就是说当前还是处于最高级任务,但是上面的延时已经使
	//其阻塞,从而在下面发生抢占
	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn;//返回申请到的内存数据区地址,pvReturn-heapSTRUCT_SIZE处存放了管理该pvReturn内存的控制数据
}
/*-----------------------------------------------------------*/

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

	if( pv != NULL )
	{
		puc -= heapSTRUCT_SIZE;//内存数据区的前heapSTRUCT_SIZE空间存放了管理本段内存数据区的控制数据

		pxLink = ( void * ) puc;

		vTaskSuspendAll();//锁住调度器
		{
			//把释放出来的内存块添加到空闲内存链表xStart~xEnd之间
			//(注:xStart~xEnd是按空闲内存大小,以从小到达的顺序链起来的单向链表[gliethttp])
			//也就是这样虽然看上去,FreeRTOS可以实现各种内存块的申请,但是因为切割出去的小内存块
			//在内存释放回收的时候,并不能自动进行内存合并整理,也就是空闲内存块不能变大,
			//最后只能导致小内存空闲块越来越多,大内存空闲块越来越少,直到不能申请到大内存块
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );//将新的空闲块重新插入到链表中[链表内部按空闲块的大小排序]
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}
/*-----------------------------------------------------------*/

size_t xPortGetFreeHeapSize( void )
{
	return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/

void vPortInitialiseBlocks( void )
{
	/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;

	//确保堆栈是从一个正确的对齐边界开始的。
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) );
	//*start存放的空block链表模块的第一个项的指针,主要用来预防编译警告的。
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;
	//*xEnd被用来标记block空链表中最后一个项的。
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;
	//首先将整个空间设成一个空闲块
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
/*-----------------------------------------------------------*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值