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; } |
在这种简单的管理机制下,内存只能够被申请,不能够被释放。
而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; |
每次需要申请xWantedSize大小的内存的时候,便从xStart节点开始寻找能够容纳所需内存大小的空闲区块,从链表中摘出。
如果该内存区块足够大,那么拆分为两个区块,一个区块返回给调用者,另一个区块再次插回链表。
方案二的插入过程比较特别,按照链表中内存区块大小,使链表在插入后呈从小向大排列。
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; } |
通过释放时的合并机制解决了方案二中的内存碎片问题。
值得一提的是,在新版的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.
* };
*/ |