freeRTOS五种内存管理详解
heap1
源码分析
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}}}
#endifvTaskSuspendAll(); // 内存申请,涉及到对全局变量以及局部静态变量的操作,故进入临界区{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();}}
#endifreturn pvReturn;
}
总结
作为最早期的内存管理,其实heap1做的事情很简单。首先,他的内存来源是全局静态数组。然后内存只能申请,并不能释放。因此,谈不上内存碎片等问题。在此之上,heap1加入了如下设计:
1. 线程安全:在申请内存的时候通过vTaskSuspendAll()和xTaskResumeAll()。暂停和恢复了任务的调度,让整体申请可以安全使用。
2. 内存对齐:主要分为两个对齐,前半部分对申请的内存大小进行了对齐,后半部分则对申请的起始内存地址进行了对齐。
3. traceMALLOC()宏用于跟踪内存申请
4. vApplicationMallocFailedHook()函数,用于内存申请失败回调
其实这也是同一系列heap有一些通用的特色设计
大致流程总结如下:
1. 申请的内存(xWantedSize)对齐
2. 内存起始地址(pucAlignedHeap)对齐(首次)
3. 返回内存起始地址+已申请的内存
4. 加上申请出的内存大小,更新已申请的内存
heap2
源码分析
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();}}#endifreturn pvReturn;
}
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
源码分析
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();}}#endifreturn pvReturn;
}
void vPortFree( void * pv )
{if( pv ){vTaskSuspendAll();{free( pv );traceFREE( pv, 0 );}( void ) xTaskResumeAll();}
}
总结
实际上这个基本上就是对malloc和free进行了线程保护,这个建立在某些sdk针对自己的内存分配有着自己的方法的前提下。我们只需要保护他们的申请和释放函数,没必要引入特别多的内存管理。
heap4
源码分析
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(); // 忽略,用于测试的宏}
}
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;
}
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
源码分析
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大小始终是0pxEnd->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 );
}
/** 使用说明:** 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}作为结尾。且不连续的内存需要由小到大的排列。