• 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;
    }
    
    • 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

    总结

    作为最早期的内存管理,其实heap1做的事情很简单。首先,他的内存来源是全局静态数组。然后内存只能申请,并不能释放。因此,谈不上内存碎片等问题。在此之上,heap1加入了如下设计:

    1. 线程安全:在申请内存的时候通过vTaskSuspendAll()和xTaskResumeAll()。暂停和恢复了任务的调度,让整体申请可以安全使用。
    2. 内存对齐:主要分为两个对齐,前半部分对申请的内存大小进行了对齐,后半部分则对申请的起始内存地址进行了对齐。
    3. traceMALLOC()宏用于跟踪内存申请
    4. vApplicationMallocFailedHook()函数,用于内存申请失败回调
    
    • 1
    • 2
    • 3
    • 4

    其实这也是同一系列heap有一些通用的特色设计
    大致流程总结如下:

    1. 申请的内存(xWantedSize)对齐
    2. 内存起始地址(pucAlignedHeap)对齐(首次)
    3. 返回内存起始地址+已申请的内存
    4. 加上申请出的内存大小,更新已申请的内存
    
    • 1
    • 2
    • 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;
    }
    
    • 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
    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    总结

    相对1而言,2最精彩的地方莫过于,通过数据结构来将内存进行管理,每个内存的头部分都用于结构体,结构体内的内存是按照由小到大的顺序排列,heapSTRUCT_SIZE之后的内存用来保存数据。这样的做法带来了一个好处(内存可以自由的申请释放)。同时也带来一个坏处,每次释放内存后,并没有进行相邻区域的合并,导致其内存会在多次申请和释放中不断的碎片化。
    大致流程可以总结如下:

    1. 初始化xStart, xEnd和pucAlignedHeap(首次)
    2. 申请内存 (xWantedSize)加上结构体(heapSTRUCT_SIZE)并进行内存对齐
    3. 遍历到空闲链表内满足且最小内存(空闲链表自身是由小到大)
    4. 该内存块从空闲链表弹出,并返回出内存块+heapSTRUCT_SIZE的地址
    5. 内存剩余空间大于heapMINIMUM_BLOCK_SIZE则将原先区块分成已用内存块和空闲内存块
    6. 空闲内存块在插入到空闲链表中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 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;
    }
    
    • 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 )
    {
        if( pv )
        {
            vTaskSuspendAll();
            {
                free( pv );
                traceFREE( pv, 0 );
            }
            ( void ) xTaskResumeAll();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    总结

    实际上这个基本上就是对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(); // 忽略,用于测试的宏
        }
    }
    
    • 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 )
    {
        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;
    }
    
    • 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
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    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();
            }
        }
    }
    
    • 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

    总结

    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 );
    }
    
    • 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
    /*
     * 使用说明:
     *
     * 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 地址较低,因此首先出现在数组中。
     *
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    总结

    heap5是基于heap4,对其prvHeapInit()进行了进一步的修改。在heap4中,使用的还是一段连续的内存。通过初次malloc,调用prvHeapInit()。在heap5中,在第一次prvHeapInit()之前,需要单独的使用vPortDefineHeapRegions(),但是可以通过参数一次性注册多块不连续的内存。在别的分析中,没有去阅读其初始的注释,它告诉我们需要以{NULL, 0}作为结尾。且不连续的内存需要由小到大的排列。

  • 相关阅读:
    前端实现打字效果
    vue部署,chunk文件有部分404,解决方案
    C语言基础篇 —— 3.4 二重指针
    不使用canvas怎么实现一个刮刮卡效果?
    2021年数维杯数学建模A题外卖骑手的送餐危机求解全过程文档及程序
    手把手基于YOLOv5定制实现FacePose之《YOLO结构解读、YOLO数据格式转换、YOLO过程修改》
    Proxifier联动BurpSuite抓取小程序
    LeetCode(力扣)968. 监控二叉树Python
    Web自动化测试工具哪家强? Selenium与Cypress的比较
    【webrtc】PacketBuffer的VCMPacket管理
  • 原文地址:https://blog.csdn.net/qq_42679566/article/details/134316249