FreeRTOS内存管理heap_2

简要介绍

heap2采用链表的方法管理内存堆。链表结构体的next指针指向下一个空闲内存块。并且链表连接的内存块按照从小到大的顺序排列。分配内存的时候,可以遍历查询。

申请内存时,查询到合适大小的内存后,如果内存有多,那么需要割下多余内存,重新插入到内存链表中,按照从小到大的顺序,在合适的位置插入。释放内存的时候,通过释放的地址找到该内存块的链表结构体,给出要释放的内存的大小,并插入到内存堆链表中,同样依照从小到大的顺序。

内存块分配预备内容

heap堆大小heap[]

在heap_2.c 中定义 static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

内存堆为ucHeap[],大小为 configTOTAL_HEAP_SIZE,由宏定义 #define configTOTAL_HEAP_SIZE     ((size_t) (40 * 1024)),可以得到内存堆大小为40K字节。

字节对齐

"portmacro.h"
#define portBYTE_ALIGNMENT		8


"portable.h"
#if portBYTE_ALIGNMENT == 8
	#define portBYTE_ALIGNMENT_MASK ( 0x0007U ) 
#endif

void *pvPortMalloc( size_t xWantedSize ){

xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}

static void prvHeapInit( void ){

pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK ) );
}

宏 portBYTE_ALIGNMENT 是需要对齐的字节数,默认为8,需进行8字节对齐也就是说xWantedSize 要为8的倍数,若不是就需要调整为8的倍数。如下:

xWantedSize += ( 8 - (xWantedSize & 0x07) )

eg1. xWantedSize = 30;

xWantedSize = 30 + (8 - (30 & 0x07))= 30 + (8 - 6)= 32

所以需要申请的内存大小应该为32个字节

pucAlignedHeap = ( uint8_t * )( ( uint32_t ) &ucHeap[8] ) & ( ( uint32_t ) ~0x07)

eg2. pucAlignedHeap = 0x200006C4;

pucAlignedHeap = 0x200006CC & 0xFFFFFFFFF8 = 0x200006C8

多以内存块的首地址应该是0x200006C8

内存块结构体BlockLink_t

typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	
	size_t xBlockSize;	
} BlockLink_t;

为了实现内存释放,heap_2引入了内存块概念,每分出一段内存就是一个内存块,剩下的空闲内存也是一个内存块,内存块大小不定。

每个内存块前面都会有一个BlockLink_t类型的变量来描述此内存块,比如现在申请一个16个字节的内存块。那么就需要申请24个字节的内存块,16字节给申请的内存,另外8个字节来保存BlockLink_t类型结构体变量,xBlockSize记录的是整个内存块的大小。

FreeRTOS内存管理heap_2_第1张图片

 

内存管理函数详解

内存堆初始化函数

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

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

	/* xStart is used to hold a pointer to the first item in the list of free
	blocks.  The void cast is used to prevent compiler warnings. */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* xEnd is used to mark the end of the list of free blocks. */
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;

	/* To start with there is a single free block that is sized to take up the
	entire heap space. */
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

prvHeapInit()

  1. pucAlignedHeap 内存堆的初始地址,进行八字节地址对齐。
  2. XStart的next指针指向内存堆首地址,大小设为0.
  3. XEnd大小设为内存堆总大小configADJUSTED_HEAP_SIZE,next指针指向NULL。(在内存申请的判断中有用到)
  4. 初始化第一块内存pxFirstFreeBlock,地址为内存堆首地址,大小为内存堆总大小,next指针指向Xend。

FreeRTOS内存管理heap_2_第2张图片

内存块插入函数

#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\
																					\
	/* Iterate through the list until a block is found that has a larger size */	\
	/* than the block we are inserting. */											\
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		/* There is nothing to do here - just iterate to the correct position. */	\
	}																				\
																					\
	/* Update the list to include the block being inserted in the correct */		\
	/* position. */																	\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}

prvInsertBlockIntoFreeList( pxBlockToInsert )宏定义

  1. 给出要插入的内存块的地址 pxBlockToInsert
  2. 在链表结构体内获取插入的内存块的大小xBlockSize,
  3. 从XStart开始不断遍历查找下一个空闲内存块,知道有内存块比pxBlockToInsert 大。当前内存块大小pxIterator->pxNextFreeBlock->xBlockSize 大于 xBlockSize
  4. 将Insert结构体的next指针指向pxIterator->pxNextFreeBlock , 而pxIterator的next指向插入的Insert结构体。因此Insert就插入到了链表中。

FreeRTOS内存管理heap_2_第3张图片FreeRTOS内存管理heap_2_第4张图片

内存申请函数

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;

	vTaskSuspendAll();
	{
		/* If this is the first call to malloc then the heap will require
		initialisation to setup the list of free blocks. */
		if( xHeapHasBeenInitialised == pdFALSE )
		{
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}

		/* The wanted size is increased so it can contain a BlockLink_t
		structure in addition to the requested amount of bytes. */
		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 ) );
			}
		}

		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
		{
			/* Blocks are stored in byte order - traverse the list from the start
			(smallest) block until one of adequate size is found. */
			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
			{
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;    
			}

			/* If we found the end marker then a block of adequate size was not found. */
			if( pxBlock != &xEnd )
			{
				/* Return the memory space - jumping over the BlockLink_t structure
				at its start. */
				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 the block is larger than required it can be split into two. */
				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					/* This block is to be split into two.  Create a new block
					following the number of bytes requested. The void cast is
					used to prevent byte alignment warnings from the compiler. */
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

					/* Calculate the sizes of two blocks split from the single
					block. */
					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
					pxBlock->xBlockSize = xWantedSize;

					/* Insert the new block into the list of free blocks. */
					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
				}

				xFreeBytesRemaining -= pxBlock->xBlockSize;
			}
		}

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

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

	return pvReturn;
}

void *pvPortMalloc( size_t xWantedSize )函数

  1. 判断内存堆是否初始化,若没有则调用prvHeapInit()进行初始化操作。
  2. 判断申请的内存xWantedSize大于0,将xWantedSize加上链表结构体的大小heapSTRUCT_SIZE。把xWantedSize进行8字节对齐。同时heapSTRUCT_SIZE也同样进行过八字节对齐。
  3. 从xStart结构体开始遍历,找到大小匹配的内存块pxBlock,得到pxPreviousBlock和pxBlock的地址。
  4. 判断pxBlock是否是XEnd的地址,如果是,那么表明没找到所需大小的内存块,可能是内存块太大,或者内存块用完。
  5. 给出返回指针pvReturn,找到的内存块,需要加上链表结构体大小heapSTRUCT_SIZE,过滤掉链表。(申请内存后链表不被操作,等下次回收内存后,依然保有内存块链表,能知道内存块的大小)
  6. 上一个内存块的next指针指向block的next内存块。把pxBlock移除内存块链表。
  7. 如果得到的内存块要比所需内存来的大,需要切割内存块,把剩余的内存大小插入到空闲内存块链表中。
  8. xFreeBytesRemaining 减去被取出内内存块大小,调用xPortGetFreeHeapSize()函数能获得xFreeBytesRemaining 的值,从而知晓剩余空闲内存块的大小。

内存块释放函数

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

	if( pv != NULL )
	{
		/* The memory being freed will have an BlockLink_t structure immediately
		before it. */
		puc -= heapSTRUCT_SIZE;

		/* This unexpected casting is to keep some compilers from issuing
		byte alignment warnings. */
		pxLink = ( void * ) puc;

		vTaskSuspendAll();
		{
			/* Add this block to the list of free blocks. */
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}

vPortFree()函数

  1. 获得索要释放的内存块首地址 uint8_t *puc = ( uint8_t * ) pv;
  2. 内存块首地址puc 减去链表结构体大小heapSTRUCT_SIZE,就是该内存块对应的链表结构体的地址。其中包括next指针和内存块大小。
  3. 调用内存块插入函数prvInsertBlockIntoFreeList()把释放的内存块插入到内存块链表中去。
  4. 剩余空闲内存大小xFreeBytesRemaining 加。

FreeRTOS内存管理heap2与QP事件池的内存管理比较

QP

  1. qp事件池管理中,在链表中内存块的大小固定不变的,并在链表头申请或释放内存,并且链表头的内存块作为当前空闲内存块,以便下一次操作。
  2. 链表结构体QFreeBlock中只有一个元素next指针,指向下一个空闲块,申请内存后,得到的内存块,包括链表结构体所在位置均可使用。放内存块时,将该段内存强制转化为QFreeBlock结构体类型。存放next指针,指向之前的free当前空闲块。

FreeRTOS

FreeRTOS内存管理heap_2_第5张图片.

  1. FreeRtos中采用遍历的方法,由从小到大的顺序依次排列,内存块大小不一致,在插入内存块的时候要依照顺序。
  2. 链表结构体BlockLink_t包含next指针和内存块大小。使用内存块的时候皆跳过链表结构体所在地址,当释放内存的时候可以由链表结构体知道要释放多大的内存。

heap2的内存堆大小为configTOTAL_HEAP_SIZE

你可能感兴趣的:(FreeRTOS,c语言,数据结构,QP,FreeRtos,QP,内存管理,C语言)