[toc]
FreeRTOS 提供了 5 种不同的内存管理策略以应对不同的应用场景,本章将对这 5 种不同的内存管理策略的实现进行分析。
我参考的源码是:FreeRTOS-Kernel-10.4.3-LTS-Patch-3\portable\MemMang
,该路径下记录了 heap_1.c
、heap_2.c
、heap_3.c
、heap_4.c
、heap_5.c
,讲解会从这 5 个文件的源码入手。
一、heap_1
1、源码讲解
首先,FreeRTOS 将堆定义为一个大数组,并使用变量 xNextFreeByte
记录已用内存大小。
1 2 3 4 5 6 7
| #define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
static size_t xNextFreeByte = ( size_t ) 0;
|
核心代码 pvPortMalloc()
函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| void * pvPortMalloc( size_t xWantedSize ) { void * pvReturn = NULL; static uint8_t * pucAlignedHeap = NULL;
#if ( portBYTE_ALIGNMENT != 1 ) {
if( xWantedSize & portBYTE_ALIGNMENT_MASK ) {
if ( (xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) )) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); } else { xWantedSize = 0; } } } #endif
vTaskSuspendAll(); { if( pucAlignedHeap == NULL ) {
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); }
if( ( xWantedSize > 0 ) && ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) && ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) { pvReturn = pucAlignedHeap + xNextFreeByte; xNextFreeByte += xWantedSize; }
traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll();
return pvReturn; }
|
需要注意的是,并未使用 configTOTAL_HEAP_SIZE
代表堆大小,而是用 configADJUSTED_HEAP_SIZE
表示堆大小,configADJUSTED_HEAP_SIZE
定义如下
1
| #define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
|
这里简单粗暴的丢弃掉了对齐字节数个字节,以此来表示堆的起始地址对齐的操作中损失的字节数(最多不会损失掉对齐字节数个字节)
heap_1.c
剩下的还有如下 3 个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void vPortFree( void * pv ) { ( void ) pv;
configASSERT( pv == NULL ); }
void vPortInitialiseBlocks( void ) { xNextFreeByte = ( size_t ) 0; }
size_t xPortGetFreeHeapSize( void ) { return( configADJUSTED_HEAP_SIZE - xNextFreeByte ); }
|
2、总结
heap_1.c
所实现的内存管理方法十分简单,其可以使用 pvPortMalloc()
函数来申请内存,一旦申请成功了,便无法被释放。其实现大致可以用一句话概括,==在堆空间剩余时,按需分割出内存,并记录已用的内存大小==。heap_1.c
使用的内存管理算法虽然简单,但对于许多嵌入式应用场景是适用且有效的。
二、heap_2
1、源码讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #define configADJUSTED_HEAP_SIZE ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
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;
static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;
|
然后 FreeRTOS 还定义了一个将内存块插入链表的宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
#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; \ }
|
简单来说就是先查找适当的位置,然后将内存块插入到链表中,非常简单,不多说。
BlockLink_t
只描述了内存块的大小和内存块的链接关系,具体分配出的内存表示方式如下图所示:
![](1.png)
1.1 堆的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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 ) ) );
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; xStart.xBlockSize = ( size_t ) 0;
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE; xEnd.pxNextFreeBlock = NULL;
pxFirstFreeBlock = ( void * ) pucAlignedHeap; pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE; pxFirstFreeBlock->pxNextFreeBlock = &xEnd; }
|
大体来看,这个函数干了三件事,分别初始化链表起始时的三个内存块:
- 一块是链表的头部,被定义为
xStart
,内存块大小为 0;
- 另一块是链表的尾部,被定义为
xEnd
,内存块大小为整个堆大小 configADJUSTED_HEAP_SIZE
(configADJUSTED_HEA_SIZE
是堆对齐操作后的大小);
- 最后一个内存块是实际用于内存分配的普通块,其被定义在堆对齐后的起始地址上,块大小为整个堆大小
configADJUSTED_HEAP_SIZE
。
普通内存块相比,xStart
和 xEnd
具有一些特殊性。
- 不参与实际的内存分配操作。
xStart
和 xEnd
都不存储在堆上。
经过该函数初始化后,整个堆的状态可以用下图表示:
![](2.png)
1.2 内存分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| void * pvPortMalloc( size_t xWantedSize ) { BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink; static BaseType_t xHeapHasBeenInitialised = pdFALSE; void * pvReturn = NULL;
vTaskSuspendAll(); { if( xHeapHasBeenInitialised == pdFALSE ) { prvHeapInit(); xHeapHasBeenInitialised = pdTRUE; }
if( ( xWantedSize > 0 ) && ( ( xWantedSize + heapSTRUCT_SIZE ) > xWantedSize ) ) { xWantedSize += heapSTRUCT_SIZE;
if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 ); } else { xWantedSize = 0; } } else { xWantedSize = 0; }
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ) { pxPreviousBlock = &xStart; pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ) { pxPreviousBlock = pxBlock; pxBlock = pxBlock->pxNextFreeBlock; }
if( pxBlock != &xEnd ) { pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize;
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) ); }
xFreeBytesRemaining -= pxBlock->xBlockSize; } }
traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll();
return pvReturn; }
|
代码看似很长,其实逻辑比较简单,大致分为如下几个步骤:
- 调整实际需要申请的内存(内存对齐)
- 检测申请字节数是否合法,若合法,则寻找合适的内存块进行分配。
- 将分配出去的内存块从链表中移除,若剩余内存大于最小内存块大小,则将剩余的内存块重新添加回链表中。
- 记录剩余字节数,返回分配内存空间的地址。
堆在初始状态下,进行一次成功的内存分配后,其状态如下图所示:
![](3.png)
1.3 内存释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void vPortFree( void * pv ) { uint8_t * puc = ( uint8_t * ) pv; BlockLink_t * pxLink;
if( pv != NULL ) { puc -= heapSTRUCT_SIZE;
pxLink = ( void * ) puc;
vTaskSuspendAll(); { prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) ); xFreeBytesRemaining += pxLink->xBlockSize; traceFREE( pv, pxLink->xBlockSize ); } ( void ) xTaskResumeAll(); } }
|
释放内存,就是把这个块重新插入到空闲数据块链表中。
释放内存后,堆的空间状态如下图所示:
![](4.png)
2、总结
与 heap_1.c
不同,heap_2.c
允许使用 vPortFree()
函数来释放申请的内存,其算法原理是将空闲堆分为若干个大小不等的内存块,并将其按大小排序,使用单向链表连接起来。
申请内存时,便从这些链表中寻找最小可满足申请需求的内存块进行分配。分配过程分为两步,首先将原先的内存块的链表项从链表中删除,其次是对当前内存块进行分割,将多余申请数的那部分内存变为新的链表项重新插入到链表中。释放过程则更为简单,只需要将释放的内存块重新插入到链表中即可。
从源码分析中,可以看出:随着申请释放的次数增加,heap_2.c
将使得内存块被越分越小(内存碎片),这会导致以下两个问题:
- 当需要再次请求一个大的内存块时,即使
xFreeBytesRemaining
大于请求的内存块,其也无法进行分配了。
- 大量的内存被
BlockLink_t
头部占用,导致堆的利用率降低
那有什么改进办法呢?学习操作系统的时候,提到==将相邻的内存块合并可以缓解碎片化的问题==,在我以前写的 Linux 内存管理(三)之分页机制 中也曾提到过这个。而 FreeRTOS
在 heap_4.c
中实现了内存块合并。
三、heap_3
这个就没什么好说的了,纯粹是封装了 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 24 25 26
| void * pvPortMalloc( size_t xWantedSize ) { void * pvReturn;
vTaskSuspendAll(); { pvReturn = malloc( xWantedSize ); traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll();
return pvReturn; }
void vPortFree( void * pv ) { if( pv ) { vTaskSuspendAll(); { free( pv ); traceFREE( pv, 0 ); } ( void ) xTaskResumeAll(); } }
|
下面重点看 heap_4
的原理和实现。
四、heap_4
1、源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( xHeapStructSize << 1 ) )
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
typedef struct A_BLOCK_LINK { struct A_BLOCK_LINK * pxNextFreeBlock; size_t xBlockSize; } BlockLink_t;
static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
PRIVILEGED_DATA static BlockLink_t xStart, * pxEnd = NULL;
PRIVILEGED_DATA static size_t xFreeBytesRemaining = 0U; PRIVILEGED_DATA static size_t xMinimumEverFreeBytesRemaining = 0U; PRIVILEGED_DATA static size_t xNumberOfSuccessfulAllocations = 0; PRIVILEGED_DATA static size_t xNumberOfSuccessfulFrees = 0;
PRIVILEGED_DATA static size_t xBlockAllocatedBit = 0;
|
1.1 插入链表
内存块合并算法主要是在 prvInsertBlockIntoFreeList()
函数中实现。与 heap_2.c
中按内存块大小顺序插入不同,heap_4.c
是按地址大小的顺序插入,这样便于合并相邻的内存块。插入过程分为以下两步:
- 查找链表中链接的下一个内存块地址大于待插入内存块地址的第一个内存块(也就是与待插入内存块相邻的且地址较低的那一个内存块)的地址。
- 检测待插入内存块是否能与相邻的内存块合并。
- 若能与低地址的相邻内存块合并,直接在低地址相邻的内存块大小上加上待插入内存块大小;
- 若能与高地址的相邻内存块合并或可以同时将高低地址邻近内存块相连,则需要同时调整链表指针与内存块大小。
![](5.png)
如上图,要插入 BlockToInsert
:
- 其会先遍历链表寻找
LowAddressAdjacentBlock
,然后判断:
- 若
BlockToInsert
仅能和 LowAddressAdja-centBlock
合并,则将 LowAddressAdjacentBlock
的块大小更改为 LowAddressAdjacentBlock
与BlockToInsert
大小的和;
- 若
BlockToInsert
仅能和 HighAddressAdjacentBlock
合并,则用 BlockToInsert
替换 HighAddressAdjacentBlock
在链表中的位置,并修改块大小为两者之和;
- 若
BlockToInsert
能将 LowAddressAdjacentBlock
与 HighAddressAdjacentBlock
连接成一整块,则从链表中删除 HighAddressAdjacentBlock
,并将 LowAddressAdjacentBlock
的块大小变为三者之和;
- 若
BlockToInsert
是一个孤立的内存块则将其正常的插入到 LowAddressAdjacentBlock
与 HighAddressAdjacentBlock
之间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) { BlockLink_t * pxIterator; uint8_t * puc;
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ) {
}
puc = ( uint8_t * ) pxIterator; if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; } else { mtCOVERAGE_TEST_MARKER(); }
puc = ( uint8_t * ) pxBlockToInsert; if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ) { if( pxIterator->pxNextFreeBlock != pxEnd ) { pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; } else { pxBlockToInsert->pxNextFreeBlock = pxEnd; } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; }
if( pxIterator != pxBlockToInsert ) { pxIterator->pxNextFreeBlock = pxBlockToInsert; } else { mtCOVERAGE_TEST_MARKER(); } }
|
这样每次插入时,便可自动的合并掉相邻的内存块,以生成更大的内存块。但这并不意味着内存的碎片化问题被解决了。可以看以下的一个示例,当其中的 Used2
被释放,是其仍然会产生内存碎片,除非 Used1
或 Used3
被释放,其才可能被拼接成较大的内存块。
![](6.png)
1.2 堆的初始化
heap_4.c
的堆初始化与 heap_2.c
的初始化大同小异,不同点有以下两点:
- 其使用
BlockLink_t
结构体成员 xBlockSize
的最高位来标记一个内存块是否被使用,1 表示在使用,0 表示空闲。
- 原本的 xEnd 被定义在了堆上,且是堆的尾部,用 pxEnd 指向其地址。
heap_4.c
初始化堆后,堆状态为:
![](7.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static void prvHeapInit( void ) { BlockLink_t * pxFirstFreeBlock; uint8_t * pucAlignedHeap; size_t uxAddress; size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
uxAddress = ( size_t ) ucHeap; if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) { uxAddress += ( portBYTE_ALIGNMENT - 1 ); uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); xTotalHeapSize -= uxAddress - ( size_t ) ucHeap; }
pucAlignedHeap = ( uint8_t * ) uxAddress;
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; xStart.xBlockSize = ( size_t ) 0;
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize; uxAddress -= xHeapStructSize; uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); pxEnd = ( void * ) uxAddress; pxEnd->xBlockSize = 0; pxEnd->pxNextFreeBlock = NULL;
pxFirstFreeBlock = ( void * ) pucAlignedHeap; pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock; pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ); }
|
1.3 内存的申请
heap_4.c
的内存的申请与释放过程与 heap_2.c
相比除了增加了对 xBlockSize
最高位的处理外,没有太大的不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| void * pvPortMalloc( size_t xWantedSize ) { BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink; void * pvReturn = NULL;
vTaskSuspendAll(); { if( pxEnd == NULL ) { prvHeapInit(); } else { mtCOVERAGE_TEST_MARKER(); }
if( ( xWantedSize & xBlockAllocatedBit ) == 0 ) { if( ( xWantedSize > 0 ) && ( ( xWantedSize + xHeapStructSize ) > xWantedSize ) ) { xWantedSize += xHeapStructSize;
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ) { if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 ); } else { xWantedSize = 0; } } else { mtCOVERAGE_TEST_MARKER(); } } else { xWantedSize = 0; }
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ) { pxPreviousBlock = &xStart; pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ) { pxPreviousBlock = pxBlock; pxBlock = pxBlock->pxNextFreeBlock; }
if( pxBlock != pxEnd ) { pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize;
prvInsertBlockIntoFreeList( pxNewBlockLink ); } else { mtCOVERAGE_TEST_MARKER(); }
xFreeBytesRemaining -= pxBlock->xBlockSize;
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ) { xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; } else { mtCOVERAGE_TEST_MARKER(); }
pxBlock->xBlockSize |= xBlockAllocatedBit; pxBlock->pxNextFreeBlock = NULL; xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); }
traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll();
configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 ); return pvReturn; }
|
1.4 内存的释放
同 heap_2.c
版本的 vPortFree
类似,不过多了对 xBlockSize
最高位的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| void vPortFree( void * pv ) { uint8_t * puc = ( uint8_t * ) pv; BlockLink_t * pxLink;
if( pv != NULL ) { puc -= xHeapStructSize;
pxLink = ( void * ) puc;
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; traceFREE( pv, pxLink->xBlockSize ); prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) ); xNumberOfSuccessfulFrees++; } ( void ) xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } }
|
2、总结
相比 heap_2.c
,heap_4.c
可以实现相邻小内存块的合并,在一定程度上缓解内存碎片化的问题。
五、heap_5
1、源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #define heapMINIMUM_BLOCK_SIZE ( ( size_t ) ( xHeapStructSize << 1 ) )
#define heapBITS_PER_BYTE ( ( size_t ) 8 )
typedef struct A_BLOCK_LINK { struct A_BLOCK_LINK * pxNextFreeBlock; size_t xBlockSize; } BlockLink_t;
static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
static BlockLink_t xStart, * pxEnd = NULL;
static size_t xFreeBytesRemaining = 0U; static size_t xMinimumEverFreeBytesRemaining = 0U; static size_t xNumberOfSuccessfulAllocations = 0; static size_t xNumberOfSuccessfulFrees = 0;
static size_t xBlockAllocatedBit = 0;
|
1.1 堆的初始化
heap_5.c
的堆初始化由 vPortDefineHeapRegions()
这一函数实现,其传入参数是一个具有特定格式的结构体数组。
1 2 3 4 5 6 7 8 9
| HeapRegion_t xHeapRegions[] = { { ( uint8_t * ) 0x80000000UL, 0x10000 }, { ( uint8_t * ) 0x90000000UL, 0xa0000 }, { NULL, 0 } };
|
vPortDefineHeapRegions()
所做的工作就是读取结构体数组中的每一个内存块信息,并将其编入链表中。以上面的参数为例,初始化后堆的状态如下图所示:
![](8.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) { BlockLink_t * pxFirstFreeBlockInRegion = NULL, * pxPreviousFreeBlock; size_t xAlignedHeap; size_t xTotalRegionSize, xTotalHeapSize = 0; BaseType_t xDefinedRegions = 0; size_t xAddress; const HeapRegion_t * pxHeapRegion;
configASSERT( pxEnd == NULL );
pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
while( pxHeapRegion->xSizeInBytes > 0 ) { xTotalRegionSize = pxHeapRegion->xSizeInBytes;
xAddress = ( size_t ) pxHeapRegion->pucStartAddress; if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) { xAddress += ( portBYTE_ALIGNMENT - 1 ); xAddress &= ~portBYTE_ALIGNMENT_MASK;
xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress; }
xAlignedHeap = xAddress;
if( xDefinedRegions == 0 ) { xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap; xStart.xBlockSize = ( size_t ) 0; } else { configASSERT( pxEnd != NULL );
configASSERT( xAddress > ( size_t ) pxEnd ); }
pxPreviousFreeBlock = pxEnd;
xAddress = xAlignedHeap + xTotalRegionSize; xAddress -= xHeapStructSize; xAddress &= ~portBYTE_ALIGNMENT_MASK; pxEnd = ( BlockLink_t * ) xAddress; pxEnd->xBlockSize = 0; pxEnd->pxNextFreeBlock = NULL;
pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap; pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion; pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd;
if( pxPreviousFreeBlock != NULL ) { pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion; }
xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize;
xDefinedRegions++; pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] ); }
xMinimumEverFreeBytesRemaining = xTotalHeapSize; xFreeBytesRemaining = xTotalHeapSize;
configASSERT( xTotalHeapSize );
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ); }
|
1.2 链表的插入、内存分配、释放
同 heap_4
的实现,参照前面即可。
2、总结
之前的 heap_1.c
,heap_2.c
和 heap_4.c
都将堆定义成了一个大数组,这意味着堆的地址必须是连续的,但在实际使用时,有时需要管理两大块或更多的不连续内存,这时便可以使用 heap_5.c
这一实现。其实在之前的 heap_2.c
和 heap_4.c
中,已经实现了对不连续内存的管理,与 heap_4.c
相比,heap_5.c
的改变仅仅是在堆的初始化上,其将多个堆内存块加入了链表而已。