freeRTOS五种内存管理详解
heap1
源码分析
c
void * pvPortMalloc( size_t xWantedSize )
{
void * pvReturn = NULL; // 申请的内存地址
static uint8_t * pucAlignedHeap = NULL; // 用于指向堆内存的起始地址
#if ( portBYTE_ALIGNMENT != 1 ) // 如果对齐为1则不对齐,否则进入对齐操作
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK ) // 与操作后有数据则表示尚未对齐
{
/* 首先, xWantedSize & portBYTE_ALIGNMENT_MASK是收集结尾有几个字节未对齐。
然后,计算距离下一个对齐(portBYTE_ALIGNMENT)还剩多少字节,给申请的(xWantedSize)补上。
正常情况下,这个数字补齐后肯定大于xWantedSize,除非加上的字节让它溢出了。
所以如果小于xWantedSize,则将其置为0
*/
if ( (xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) )) > xWantedSize )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
else
{
xWantedSize = 0; // 当申请为0的时候后续最关键的内存分配会被跳过,内存返回为NULL
}
}
}
#endif
vTaskSuspendAll(); // 内存申请,涉及到对全局变量以及局部静态变量的操作,故进入临界区
{
if( pucAlignedHeap == NULL )
{
/* ( portPOINTER_SIZE_TYPE )是强转成指针对应的整数类型,以便后续计算。
~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) 的目的是为了向上对齐,也就是&后可以去除不对齐的部分。
由于去除不对其的部分可能会导致超出内存范围,所以加上portBYTE_ALIGNMENT留有一定空间用于向上对其。
本质上是对ucHeap进行对齐操作。
*/
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; // pucAlignedHeap可以理解成ucHeap对齐后的地址,xNextFreeByte则是已申请的内存
xNextFreeByte += xWantedSize; // 统计被使用空间(名字好像取错了)
}
traceMALLOC( pvReturn, xWantedSize ); // 用于跟踪调试的宏,未设置则为空
}
( void ) xTaskResumeAll(); // 退出临界区
#if ( configUSE_MALLOC_FAILED_HOOK == 1 ) // malloc失败的处理钩子函数
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
总结
作为最早期的内存管理,其实heap1做的事情很简单。首先,他的内存来源是全局静态数组。然后内存只能申请,并不能释放。因此,谈不上内存碎片等问题。在此之上,heap1加入了如下设计:
1. 线程安全:在申请内存的时候通过vTaskSuspendAll()和xTaskResumeAll()。暂停和恢复了任务的调度,让整体申请可以安全使用。
2. 内存对齐:主要分为两个对齐,前半部分对申请的内存大小进行了对齐,后半部分则对申请的起始内存地址进行了对齐。
3. traceMALLOC()宏用于跟踪内存申请
4. vApplicationMallocFailedHook()函数,用于内存申请失败回调
其实这也是同一系列heap有一些通用的特色设计
大致流程总结如下:
1. 申请的内存(xWantedSize)对齐
2. 内存起始地址(pucAlignedHeap)对齐(首次)
3. 返回内存起始地址+已申请的内存
4. 加上申请出的内存大小,更新已申请的内存
heap2
源码分析
c
void * pvPortMalloc( size_t xWantedSize )
{
BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void * pvReturn = NULL;
vTaskSuspendAll();
{
/* 如果第一次调用,则调用prvHeapInit()函数来初始化 */
if( xHeapHasBeenInitialised == pdFALSE )
{
prvHeapInit();
xHeapHasBeenInitialised = pdTRUE;
}
/*
* 首先,heapSTRUCT_SIZE指的是BlockLink_t结构体,它是一个保存内存块的单向链表。
* 而heapSTRUCT_SIZE计算,实际上自带内存对齐
*/
if( ( xWantedSize > 0 ) &&
( ( xWantedSize + heapSTRUCT_SIZE ) > xWantedSize ) ) /* 判断剩余空间是否溢出 */
{
xWantedSize += heapSTRUCT_SIZE; // 将结构体的大小算入到申请的内存中去
/* xWantedSize & portBYTE_ALIGNMENT_MASK是未对齐的部分,
portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK )是算出需要补多少才能对齐
xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) )是对齐后的内存大小
判断其>xWantedSize本质上就是判断是否溢出*/
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;
}
// 判断申请内存大于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();
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
c
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* 被释放的内存前面会有一个 BlockLink_t 结构,所以反推真实申请的内存起始地址 */
puc -= heapSTRUCT_SIZE;
/* 这种强行转换是为了防止某些编译器发出字节对齐警告。 */
pxLink = ( void * ) puc;
vTaskSuspendAll();
{
/* 将释放的内存添加到内存区域内 */
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
xFreeBytesRemaining += pxLink->xBlockSize; // 内存添加到内存剩余中去
traceFREE( pv, pxLink->xBlockSize );
}
( void ) xTaskResumeAll();
}
}
总结
相对1而言,2最精彩的地方莫过于,通过数据结构来将内存进行管理,每个内存的头部分都用于结构体,结构体内的内存是按照由小到大的顺序排列,heapSTRUCT_SIZE之后的内存用来保存数据。这样的做法带来了一个好处(内存可以自由的申请释放)。同时也带来一个坏处,每次释放内存后,并没有进行相邻区域的合并,导致其内存会在多次申请和释放中不断的碎片化。
大致流程可以总结如下:
1. 初始化xStart, xEnd和pucAlignedHeap(首次)
2. 申请内存 (xWantedSize)加上结构体(heapSTRUCT_SIZE)并进行内存对齐
3. 遍历到空闲链表内满足且最小内存(空闲链表自身是由小到大)
4. 该内存块从空闲链表弹出,并返回出内存块+heapSTRUCT_SIZE的地址
5. 内存剩余空间大于heapMINIMUM_BLOCK_SIZE则将原先区块分成已用内存块和空闲内存块
6. 空闲内存块在插入到空闲链表中
heap3
源码分析
c
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;
}
c
void vPortFree( void * pv )
{
if( pv )
{
vTaskSuspendAll();
{
free( pv );
traceFREE( pv, 0 );
}
( void ) xTaskResumeAll();
}
}
总结
实际上这个基本上就是对malloc和free进行了线程保护,这个建立在某些sdk针对自己的内存分配有着自己的方法的前提下。我们只需要保护他们的申请和释放函数,没必要引入特别多的内存管理。
heap4
源码分析
c
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) /* PRIVILEGED_FUNCTION */
{
BlockLink_t * pxIterator;
uint8_t * puc;
/* 迭代列表,直到找到地址介于二者之间 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* 这里不做任何事情,直到迭代后找到后,后续进行操作 */
}
/* 计算插入的数据块是否和上一个是连续的内存。
* 如果是则合并(地址为上一个内存的地址,大小增加)
* 本质上没有将pxBlockToInsert插入进链表,只是扩大了上一个内存的size
*/
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; // 直接将上个数据库扩大就完成了插入
pxBlockToInsert = pxIterator; // 这里表示pxBlockToInsert已经和pxIterator一个意思了。用于后续判断
}
else
{
mtCOVERAGE_TEST_MARKER(); // 忽略,用于测试的宏
}
/* 插入的数据块和它下一个数据块是否是连续
* 如果是则合并(当前内存地址不变,大小增加,当前的pxNextFreeBlock地址改为下一个pxNextFreeBlock)
* 其实这里的做法, 是修改当前的pxBlockToInsert,然后替换下一个数据块(差一步pxIterator->pxNextFreeBlock的修改)
*/
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; // 这里的意图是想替换pxIterator->pxNextFreeBlock
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxEnd; // 如果到结尾了,则插入尾巴 tag1
}
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; // 如果不是,则准备作为一个独立的块插入空闲块链表里 tag2
}
/*
* 这里比较巧妙,当合并了前区块的时候,这里直接跳过
* 如果和前面区块无关,和后面区块无关,则进入tag2,插入空闲块,最后,将pxBlockToInsert接入链表
* 如果和前面区块无关,但是和后面区块相连,则修改pxBlockToInsert并且替换pxIterator->pxNextFreeBlock
* 如果合并了前区块,但是和后面区块相连,将pxIterator的大小再扩大,无需将pxBlockToInsert接入链表
* 如果和前面区块无关,但是已经到了pxEnd,则进入tag1,在pxEnd前插入
*/
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert; // 接入空闲链表中
}
else
{
mtCOVERAGE_TEST_MARKER(); // 忽略,用于测试的宏
}
}
c
void * pvPortMalloc( size_t xWantedSize )
{
BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
void * pvReturn = NULL;
vTaskSuspendAll();
{
/* 如果这是第一次调用 malloc,则堆需要初始化来设置空闲块列表。 */
if( pxEnd == NULL )
{
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 取出最高位,最高位必须是0,因为后续需要用它作为标志位 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* 这部分和heap2.c一样主要为了扩大申请内存,以便保存结构体
* 其中,xHeapStructSize是对齐后的大小, 最终的xWantedSize还进行了一次对齐
*/
if( ( xWantedSize > 0 ) &&
( ( xWantedSize + xHeapStructSize ) > xWantedSize ) ) /* Overflow check */
{
xWantedSize += xHeapStructSize;
/* 判断是否对齐 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* 判断对齐后有无溢出 */
if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) )
> xWantedSize )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); // 加上了xHeapStructSize后,进行一次对齐操作
configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
}
else
{
xWantedSize = 0;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
xWantedSize = 0;
}
// 判断申请内存大于0且剩余内存足够
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
/* 开始遍历列表.(内存地址从小到大) */
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/*这里操作基本上和heap2.c差别不大*/
if( pxBlock != pxEnd )
{
/* 返回给用户内存使用的地址(需要跳过结构体头部分) */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
/* 里区块被使用,所以将它从空闲列表里剔除 */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* 如果区块剩余够大,则分成两个部分,一个使用区块,一个空闲区块. */
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 );
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; // 最高位用作是否被初始化的标志,在free的时候即便被free了两遍也
pxBlock->pxNextFreeBlock = NULL; //
xNumberOfSuccessfulAllocations++;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */
configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
c
void vPortFree( void * pv )
{
uint8_t * puc = ( uint8_t * ) pv;
BlockLink_t * pxLink;
if( pv != NULL )
{
/* 被释放的内存前面将紧接着一个 BlockLink_t 结构 */
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();
}
}
}
总结
heap2的prvInsertBlockIntoFreeList是一个宏。在最初的迭代查找中是查找它的大小,然后按照顺序放到空闲块链表里。而heap4的则是一个函数(相对复杂,故直接做成函数)。在最初迭代中,查找的是链表的地址,整体的合并设计的极为巧妙,寻找相邻的前后区块进行合并减少了内存碎片化。在pvPortMalloc的实现上,多了xBlockAllocatedBit, xNumberOfSuccessfulFrees,xNumberOfSuccessfulAllocations,xMinimumEverFreeBytesRemaining等功能。相当于在heap2的基础之上加入了相邻内存合并来解决内存碎片问题。
heap5
源码分析
c
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;
/* 如果尚未设置 xStart,则设置 xStart */
if( xDefinedRegions == 0 )
{
/* xStart 用于保存指向空闲块列表中第一个项目的指针。*/
xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
xStart.xBlockSize = ( size_t ) 0; // 起始位置大小始终为0
}
else
{
/* 只有当一个区域已被添加到堆中时,才会出现这里. */
configASSERT( pxEnd != NULL );
/* 校验块的起始地址递增。 */
configASSERT( xAddress > ( size_t ) pxEnd );
}
/* 记住上一区域的结束标记位置(如果有)。 */
pxPreviousFreeBlock = pxEnd;
/* pxEnd 用于标记空闲区块列表的结束,并插入区域空间的末尾 */
xAddress = xAlignedHeap + xTotalRegionSize; // 指针指到底
xAddress -= xHeapStructSize; // 往前退一个xHeapStructSize大小用于存放结构体
xAddress &= ~portBYTE_ALIGNMENT_MASK; // 地址对齐
pxEnd = ( BlockLink_t * ) xAddress; //
pxEnd->xBlockSize = 0; // xEnd大小始终是0
pxEnd->pxNextFreeBlock = NULL; // xEnd下一个地址始终是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 );
/* 计算出 size_t 变量中最高位的位置。(用于标志区块是否空闲) */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
c
/*
* 使用说明:
*
* vPortDefineHeapRegions() 必须在 pvPortMalloc() 之前调用.
* 如果创建了任何任务对象(任务、队列、事件组等),pvPortMalloc() 将被调用。
* 因此必须在定义任何其他对象之前调用 vPortDefineHeapRegions()。
*
* 数组使用 NULL 零大小区域定义终止,
* 数组中定义的内存区域必须按照从低地址到高地址的地址顺序出现。
* 因此,下面是一个使用该函数的有效示例。
*
* HeapRegion_t xHeapRegions[] =
* {
* { ( uint8_t * ) 0x80000000UL, 0x10000 }, << 从地址 0x80000000 开始定义一个 0x10000 字节的数据块
* { ( uint8_t * ) 0x90000000UL, 0xa0000 }, << 定义从 0x90000000 地址开始的 0xa0000 字节块
* { NULL, 0 } << 终止数组。
* };
*
* vPortDefineHeapRegions( xHeapRegions ); << 将数组传入 vPortDefineHeapRegions()。
*
* 注意 0x80000000 地址较低,因此首先出现在数组中。
*
*/
总结
heap5是基于heap4,对其prvHeapInit()进行了进一步的修改。在heap4中,使用的还是一段连续的内存。通过初次malloc,调用prvHeapInit()。在heap5中,在第一次prvHeapInit()之前,需要单独的使用vPortDefineHeapRegions(),但是可以通过参数一次性注册多块不连续的内存。在别的分析中,没有去阅读其初始的注释,它告诉我们需要以{NULL, 0}作为结尾。且不连续的内存需要由小到大的排列。