Free RTOS的内存管理

Free RTOS的内存管理代码位于“memmang”文件夹下,路径是:
../source/portable/memmang
Free RTOS针对不同资源的MCU提供了5个级别的内存管理方案,分别对应文件夹下的:
Heap_1.c
Heap_2.c
Heap_3.c
Heap_4.c
Heap_5.c
注:在旧版本的Free RTOS中只有四种内存管理方案,第五种方案为后期版本新增。
在Free RTOS的内存管理中,堆被存抽象成了一个巨大的数组:
1
2
3
4
5
6
7
/* Allocate the memory for the heap.  The struct is used to force byte
alignment without using any non-portable code. */
extern struct xRTOS_HEAP
{
	unsigned long ulDummy;
	unsigned char ucHeap[ configTOTAL_HEAP_SIZE ];
} xHeap;
所以在第一种方案(Heap_1.c)的实现中,系统维护了一个全局变量 xNextFreeByte,用来指向尚未被占用的内存:
1
static size_t xNextFreeByte = ( size_t ) 0;
内存只能按照顺序被申请,每申请一次内存,则全局变量xNextFreeByte指针向后偏移xWantedSize,xWantedSize就是申请的内存区块大小:
1
2
3
4
5
6
7
8
/* Check there is enough room left for the allocation. */
if( ( ( xNextFreeByte + xWantedSize ) < configTOTAL_HEAP_SIZE ) && ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)/* Check for overflow. */
{
	/* Return the next free byte then increment the index past this
	block. */
	pvReturn = &( xHeap.ucHeap[ xNextFreeByte ] );
	xNextFreeByte += xWantedSize;			
}
1
在这种简单的管理机制下,内存只能够被申请,不能够被释放。
而Free RTOS提供的第三种方案(Heap_3.c),实现方式与其他几种方式均不相同。内存的申请与释放主要依赖于编译器的C语言库中对malloc以及Free函数的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
void vPortFree( void *pv )
{
	if( pv )
	{
		vTaskSuspendAll();
		{
			free( pv );
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();
	}
}
代码极其简单,不再赘述。
第二种方案(Heap_2.c)与第四、五三种管理方案,在内存的管理结构上基本相同,其实也可以看成是在第一种方案上的改进,这三种方案最大的差别在于申请到的内存使用完毕后,释放内存区块时如何处理相邻内存区块:
在方案二中,Free RTOS定义了一个内存区块的通用管理结构,这是个单向链表的节点抽象:
1
2
3
4
5
6
7
/* Define the linked list structure.  This is used to link free blocks in order
of their size. */
typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */
	size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;
同时增加了首、尾指针(xStart、xEnd),指向堆的开始和结束地址:
在初始化时,整个堆中只有三个结构:xStart、xEnd和pxFirstFreeBlock,并链接成一串:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 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;
2
每次需要申请xWantedSize大小的内存的时候,便从xStart节点开始寻找能够容纳所需内存大小的空闲区块,从链表中摘出。
如果该内存区块足够大,那么拆分为两个区块,一个区块返回给调用者,另一个区块再次插回链表。
3
方案二的插入过程比较特别,按照链表中内存区块大小,使链表在插入后呈从小向大排列。
1
2
3
4
5
6
7
8
9
10
11
12
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;
所以方案二有个很明显的缺点——使用一段时间后内存碎片会很多;
于是方案四在方案二的基础上,做了一个改进,内存区块按照地址顺序排列(而不是像方案二那样按照大小排列):
在释放使用完毕后的内存区块的时候:
首先顺序找到需要释放的区块前一个地址,判断是否需要与前一个地址进行合并;
1
2
3
4
5
6
7
8
9
10
11
12
/* Do the block being inserted, and the block it is being inserted after
make a contiguous block of memory? */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
	pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
	pxBlockToInsert = pxIterator;
}
else
{
	mtCOVERAGE_TEST_MARKER();
}
找到后一个内存区块,判断是否需要与后一个地址进行合并;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Do the block being inserted, and the block it is being inserted before
make a contiguous block of memory? */
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
	if( pxIterator->pxNextFreeBlock != pxEnd )
	{
		/* Form one big block from the two blocks. */
		pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
	}
	else
	{
		pxBlockToInsert->pxNextFreeBlock = pxEnd;
	}
}
else
{
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
通过释放时的合并机制解决了方案二中的内存碎片问题。
4
5
值得一提的是,在新版的Free RTOS代码中增加了一个全局变量xBlockAllocatedBit,这个全局变量在初始化函数中,被初始化为(1000000000000000)b;
1
2
3
4
/* The block is being returned - it is allocated and owned
by the application and has no "next" block. */
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
这个变量用于标识内存区块是否被使用。
方案五在内存的管理、申请和释放算法上与四并没有太大不同,唯一差别是,在初始化的时候,可以使用不连续的内存作为堆来使用,例如文件头注释中的例子:
1
2
3
4
5
6
7
/* HeapRegion_t xHeapRegions[] =
* {
* 	{ ( uint8_t * ) 0x80000000UL, 0x10000 }, << Defines a block of 0x10000 bytes starting at address 0x80000000
* 	{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, << Defines a block of 0xa0000 bytes starting at address of 0x90000000
* 	{ NULL, 0 }                << Terminates the array.
* };
*/

发表评论