FreeRTOS五种内存管理详解

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
            }
        }
    }
#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

源码分析

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;
}
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();
            }
        }
    #endif

    return 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大小始终是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 );
}
/*
 * 使用说明:
 *
 * 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}作为结尾。且不连续的内存需要由小到大的排列。

你可能感兴趣的:(算法,RTOS,嵌入式,实时操作系统,FreeRTOS,C,内存管理)