FreeRTOS学习——heap4

FreeRTOS学习——内存管理heap4,仅用于记录自己阅读与学习源码
FreeRTOS Kernel V10.5.1
port :GCC/ARM_CM7


参考:
FreeRTOS:4.内存管理_freertos heap4内存管理-CSDN博客
FreeRTOS内存管理之heap_4.c_freertos heap4源码解读-CSDN博客


文章目录

    • 宏定义
    • 变量及声明
    • 函数
      • prvHeapInit
      • pvPortMalloc
      • vPortFree
      • prvInsertBlockIntoFreeList
      • vPortGetHeapStats


宏定义

  1. configSUPPORT_DYNAMIC_ALLOCATION
    如果 configSUPPORT_DYNAMIC_ALLOCATION 为0,则不得使用此文件
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 0 )  
    #error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0  
#endif  
  
  1. configHEAP_CLEAR_MEMORY_ON_FREE
    如果这个宏为0,在free内存函数中,并没有将内存空间清0,只是将其插入到空闲内存块链表中。
    此宏为1,才清0
#ifndef configHEAP_CLEAR_MEMORY_ON_FREE  
    #define configHEAP_CLEAR_MEMORY_ON_FREE    0  
#endif  
  1. heapMINIMUM_BLOCK_SIZE 宏

    #define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( xHeapStructSize << 1 ) )
    
    • 这个宏定义了一个最小的块大小,即xHeapStructSize乘以2。
    • xHeapStructSize表示存储堆信息的数据结构(BlockLink_t)的大小。
    • heapMINIMUM_BLOCK_SIZE确保内存分配的最小单位至少能够存储两个这样的数据结构,这样即使在最坏的情况下,内存也能被有效管理,避免过小的内存块导致碎片化严重。
      heapMINIMUM_BLOCK_SIZE限制了内存块的最小值,当内存块小于heapMINIMUM_BLOCK_SIZE时,就不再维护这个内存碎片了。

size_t类型
1.无符号整数:size_t 是无符号的,这意味着它只能表示非负值。
2.平台相关:size_t 的具体大小(即它是多少位的整数)取决于编译器和目标平台。例如,在32位系统上,size_t 通常是32位的;在64位系统上,size_t 通常是64位的

  1. heapBITS_PER_BYTE 宏

    #define heapBITS_PER_BYTE         ( ( size_t ) 8 )
    
    • 这个宏定义了一个字节包含的位数,即8位。
    • 该定义通常用于计算字节到位或位到字节的转换。
  2. heapSIZE_MAX 宏

    #define heapSIZE_MAX              ( ~( ( size_t ) 0 ) )
    
    • 这个宏定义了size_t类型能够表示的最大值。
    • ~( ( size_t ) 0 )是一个位运算,对size_t类型的0进行按位取反操作,得到的就是该类型的全1,即最大值。
    • 该定义用于判断内存操作是否会超出size_t类型的表示范围
  3. heapMULTIPLY_WILL_OVERFLOW 宏

    #define heapMULTIPLY_WILL_OVERFLOW( a, b )    ( ( ( a ) > 0 ) && ( ( b ) > ( heapSIZE_MAX / ( a ) ) ) )
    
    • 这个宏用于检查两个数字相乘是否会引发溢出。
    • 如果a大于0且b大于heapSIZE_MAX / a,则相乘的结果会超出size_t类型的表示范围,引发溢出。
    • 该宏在进行内存分配前检查相乘的结果是否会超出size_t类型的表示范围,以防止潜在的溢出错误。
  4. heapADD_WILL_OVERFLOW 宏

    #define heapADD_WILL_OVERFLOW( a, b )         ( ( a ) > ( heapSIZE_MAX - ( b ) ) )
    
    • 这个宏用于检查两个数字相加是否会引发溢出。
    • 如果a大于heapSIZE_MAX - b,则相加的结果会超出size_t类型的表示范围,引发溢出。
    • 该宏同样在进行内存分配前检查相加的结果是否会超出size_t类型的表示范围,以防止潜在的溢出错误。

BlockLink_t 结构中 xBlockSize 成员的 MSB 用于跟踪块的分配状态。
当 BlockLink_t 结构的 xBlockSize 成员的 MSB 位被设置时,该块属于应用程序。
当该位为空闲时,该数据块仍是空闲堆空间的一部分。

  1. heapBLOCK_ALLOCATED_BITMASK 宏定义了一个位掩码,用于标记一个内存块是否被分配。具体来说,它是通过将一个size_t类型的1左移(sizeof(size_t) * heapBITS_PER_BYTE) - 1位得到的。sizeof(size_t)返回size_t类型的大小(字节数),乘以heapBITS_PER_BYTE(在本代码中定义为8,表示一个字节有8位)再减去1,得到的是size_t类型的最高位的位索引。因此,这个宏定义的值是一个只有最高位为1,其余位为0的掩码

  2. heapBLOCK_SIZE_IS_VALID 宏用于检查一个给定的块大小值是否有效。这个宏通过将给定的块大小值xBlockSizeheapBLOCK_ALLOCATED_BITMASK进行按位与运算,并检查结果是否为0来实现这一点。如果最高位为0,则块大小被认为是有效的(即该块未被分配),否则无效(已被分配)。

  3. heapBLOCK_IS_ALLOCATED 宏用于检查一个特定的内存块是否已被分配。它同样使用按位与运算heapBLOCK_ALLOCATED_BITMASK与内存块大小pxBlock->xBlockSize进行操作。如果最高位为1,则返回真(非0值),表示该块已被分配;如果最高位为0,则返回假(0值),表示该块未被分配。

  4. heapALLOCATE_BLOCK 宏用于将一个内存块标记为已分配。通过将内存块大小pxBlock->xBlockSizeheapBLOCK_ALLOCATED_BITMASK按位或运算,可以将xBlockSize的最高位置为1,从而标记该块已被分配。

  5. heapFREE_BLOCK 宏用于将一个内存块标记为未分配。通过将内存块大小pxBlock->xBlockSizeheapBLOCK_ALLOCATED_BITMASK按位非运算后再按位与运算,可以将xBlockSize的最高位清零,从而标记该块未被分配。

/* MSB of the xBlockSize member of an BlockLink_t structure is used to track  
 * the allocation status of a block.  When MSB of the xBlockSize member of * an BlockLink_t structure is set then the block belongs to the application. * When the bit is free the block is still part of the free heap space. *//*BlockLink_t 结构中 xBlockSize 成员的 MSB 用于跟踪块的分配状态。  
 * 当 BlockLink_t 结构的 xBlockSize 成员的 MSB 位被设置时,该块属于应用程序。  
 * 当该位为空闲时,该数据块仍是空闲堆空间的一部分。*/  
#define heapBLOCK_ALLOCATED_BITMASK    ( ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 ) )  
#define heapBLOCK_SIZE_IS_VALID( xBlockSize )    ( ( ( xBlockSize ) & heapBLOCK_ALLOCATED_BITMASK ) == 0 )  
#define heapBLOCK_IS_ALLOCATED( pxBlock )        ( ( ( pxBlock->xBlockSize ) & heapBLOCK_ALLOCATED_BITMASK ) != 0 )  
#define heapALLOCATE_BLOCK( pxBlock )            ( ( pxBlock->xBlockSize ) |= heapBLOCK_ALLOCATED_BITMASK )  
#define heapFREE_BLOCK( pxBlock )                ( ( pxBlock->xBlockSize ) &= ~heapBLOCK_ALLOCATED_BITMASK )

变量及声明

  1. ucHeap[ configTOTAL_HEAP_SIZE ]
    ucHeap其实是一个大数组,是uint8_t类型,其大小在FreeRTOSConfig.h中定义
    define configTOTAL_HEAP_SIZE ( ( size_t ) ( 46 * 1024 ) )
    如果宏configAPPLICATION_ALLOCATED_HEAP为1,用户可自行定义ucHeap。
    反之则定义一个静态数据ucHeap
/* Allocate the memory for the heap. */  
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )  
  
/* The application writer has already defined the array used for the RTOS  
* heap - probably so it can be placed in a special segment or address. */  
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];#else  
    PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];  
#endif /* configAPPLICATION_ALLOCATED_HEAP */
  1. BlockLink_t
    定义链接列表结构。 该结构用于按内存地址顺序链接空闲数据块
    BlockLink_t的成员xBlockSize的最高位即MSB,为1时表示已分配,为0表示空闲状态。
/* Define the linked list structure.  This is used to link free blocks in order  
 * of their memory address. *//*定义链接列表结构。 该结构用于按内存地址顺序链接空闲数据块。*/  
typedef struct A_BLOCK_LINK  
{  
    struct A_BLOCK_LINK * pxNextFreeBlock; /*<< The next free block in the list. */  
    size_t xBlockSize;                     /*<< The size of the free block. */  
} BlockLink_t;
  1. xHeapStructSize

    static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
    

    这段代码计算了放置在每个已分配内存块开头的结构的大小,并确保它按字节正确对齐。sizeof( BlockLink_t ) 获取了 BlockLink_t 结构的原始大小。( portBYTE_ALIGNMENT - 1 ) 用于计算需要对齐的额外字节数。~( ( size_t ) portBYTE_ALIGNMENT_MASK ) 是一个按位与操作,用于将计算出的大小截断到最近的字节对齐边界。这样可以确保每个分配的内存块都从正确的字节边界开始,以避免在某些处理器上可能出现的对齐错误。

    #define portBYTE_ALIGNMENT    8   //按8字节对齐
    #define portBYTE_ALIGNMENT_MASK    ( 0x0007 )  // 0000 0111
这段代码的结果是xHeapStructSize为8字节

例:
- 例如,假设 `sizeof( BlockLink_t )` 是 10 字节,`portBYTE_ALIGNMENT` 是 4(4字节对齐),那么 `sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) )` 是 13 字节。
- `13 & 0xFFFFFFFC` 的结果是 12 字节,因为 13 的二进制表示是 `1101`,截断到 4 字节对齐边界的结果是 `1100`,即 12 字节。
  1. **xStartpxEnd **:

    PRIVILEGED_DATA static BlockLink_t xStart;
    PRIVILEGED_DATA static BlockLink_t * pxEnd = NULL;
    

    这里创建了两个 BlockLink_t 类型的静态变量来标记内存分配列表的开始和结束。xStart 作为链表的头节点,指向第一个空闲内存块。pxEnd 则是一个指向链表尾节点的指针,初始值为 NULL,表示链表尚未初始化。

    除了xStart以外,其余内存控制块都是在内存池中,用指针的形式进行访问。也就是说,xStart是作为哨兵节点,xStart的pxNextFreeBlock指针就是第一个空闲块节点。

  2. 内存分配和释放的统计信息:

    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;
    

    这些静态变量用于跟踪内存分配和释放的状态信息:

    • xFreeBytesRemaining 记录了当前剩余的空闲内存字节数。
    • xMinimumEverFreeBytesRemaining 记录了自启动以来剩余空闲内存字节数的最小值。
    • xNumberOfSuccessfulAllocations 记录了成功的内存分配次数。
    • xNumberOfSuccessfulFrees 记录了成功的内存释放次数。

函数

prvHeapInit

  1. 堆地址内存对齐
  2. 设置xStart和pxEnd
  3. 创建第一个空闲块
static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */  
{  
    BlockLink_t * pxFirstFreeBlock;   /*指向第一个空闲块的指针*/  
    uint8_t * pucAlignedHeap;         /*指向对齐的堆空间的指针*/  
    portPOINTER_SIZE_TYPE uxAddress;  /*用于地址计算的变量,是一个地址*/  
    size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/*总堆空间大小*/  
  
    /* Ensure the heap starts on a correctly aligned boundary.     * 确保堆从正确对齐的边界开始*/  
    uxAddress = ( portPOINTER_SIZE_TYPE ) ucHeap;/*数组名是地址,将ucHeap的地址转换为portPOINTER_SIZE_TYPE类型*/  
  
    /**堆内存地址对齐*/  
    if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )  
    {  
        /*调整地址以确保对齐*/  
        uxAddress += ( portBYTE_ALIGNMENT - 1 );  
        uxAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );  
        xTotalHeapSize -= uxAddress - ( portPOINTER_SIZE_TYPE ) ucHeap;//重新计算总堆空间大小  
    }  
  
    pucAlignedHeap = ( uint8_t * ) uxAddress;//将对齐后的地址赋值给pucAlignedHeap  
  
    /**设置xStart和pxEnd*/  
    /* xStart is used to hold a pointer to the first item in the list of free     * blocks.  The void cast is used to prevent compiler warnings.     * xStart 用于保存指向空闲块列表中第一个项的指针。 void cast 用于防止编译器发出警告*/  
    xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;//将xStart.pxNextFreeBlock设为对齐后的堆地址  
    xStart.xBlockSize = ( size_t ) 0;  
  
    /* pxEnd is used to mark the end of the list of free blocks and is inserted  
     * at the end of the heap space.     * pxEnd 用于标记空闲块列表的结束,并插入堆空间的末尾。*/  
    uxAddress = ( ( portPOINTER_SIZE_TYPE ) pucAlignedHeap ) + xTotalHeapSize;//计算堆结束地址  
    uxAddress -= xHeapStructSize;//减去BlockLink_t结构大小,为pxEnd提供空间  
    uxAddress &= ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK );//对齐  
    pxEnd = ( BlockLink_t * ) uxAddress;//将pxEnd指向对齐后的地址  
    pxEnd->xBlockSize = 0;  
    pxEnd->pxNextFreeBlock = NULL;//表示链表的结束  
  
    /**创建第一个空闲块*/  
    /* To start with there is a single free block that is sized to take up the     * entire heap space, minus the space taken by pxEnd.     * 首先有一个单独的空闲块,其大小为占用整个堆空间,减去 pxEnd 占用的空间*/  
    pxFirstFreeBlock = ( BlockLink_t * ) pucAlignedHeap;//pucAlignedHeap指向对齐后的堆地址  
    pxFirstFreeBlock->xBlockSize = ( size_t ) ( uxAddress - ( portPOINTER_SIZE_TYPE ) pxFirstFreeBlock );  
    pxFirstFreeBlock->pxNextFreeBlock = pxEnd;  
  
    /* Only one block exists - and it covers the entire usable heap space.  
     * 只存在一个数据块,它覆盖了整个可用堆空间*/  
    xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;  
    xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;  
}

创建了一个空闲块列表,并将整个可用堆空间作为一个空闲块放入列表中
FreeRTOS学习——heap4_第1张图片

pvPortMalloc

  1. 第一次调用malloc需初始化
  2. 计算所需内存大小,要额外增加BlockLink_t 结构体的大小和字节对齐所需的额外空间
  3. 遍历空闲链表,找到足够大的空闲块
  4. 如果找到合适大小的空闲块,则从空闲块列表中移除该块,
  5. 并根据情况决定是否将该块分割成两部分,将分割后的空闲块重新插入链表
  6. 更新统计信息
  7. 最后直接返回内存空间地址,返回的内存空间不包含 BlockLink_t
  
void * pvPortMalloc( size_t xWantedSize )  
{  
    BlockLink_t * pxBlock;            /*找到的空闲块指针*/  
    BlockLink_t * pxPreviousBlock;    /*前一个块指针*/  
    BlockLink_t * pxNewBlockLink;     /*新块链接指针*/  
    void * pvReturn = NULL;           /*返回值指针*/  
    size_t xAdditionalRequiredSize;   /*额外所需大小*/  
  
    vTaskSuspendAll();//暂停任务调度,以确保在此期间没有任务切换  
    {  
        /* If this is the first call to malloc then the heap will require  
         * initialisation to setup the list of free blocks. */        /*检查 pxEnd 是否为 NULL,如果是,则是第一次调用 malloc,则堆将需要初始化以设置空闲块列表。*/  
        if( pxEnd == NULL )  
        {  
            prvHeapInit();  
        }  
        else  
        {  
            mtCOVERAGE_TEST_MARKER();  
        }  
  
        /*计算所需内存大小 xWantedSize */        if( xWantedSize > 0 )  
        {  
            /* The wanted size must be increased so it can contain a BlockLink_t  
             * structure in addition to the requested amount of bytes. Some             * additional increment may also be needed for alignment. */            /*计算附加的所需大小 xAdditionalRequiredSize,  
             * 这个大小包括 BlockLink_t 结构体的大小和字节对齐所需的额外空间*/  
            xAdditionalRequiredSize = xHeapStructSize + portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );  
  
            if( heapADD_WILL_OVERFLOW( xWantedSize, xAdditionalRequiredSize ) == 0 )  
            {  
                xWantedSize += xAdditionalRequiredSize;  
            }  
            else  
            {  
                xWantedSize = 0;  
            }  
        }  
        else  
        {  
            mtCOVERAGE_TEST_MARKER();  
        }  
  
        /* Check the block size we are trying to allocate is not so large that the  
         * top bit is set.  The top bit of the block size member of the BlockLink_t         * structure is used to determine who owns the block - the application or         * the kernel, so it must be free. */        if( heapBLOCK_SIZE_IS_VALID( xWantedSize ) != 0 )  
        {  
            /*分配的内存大小大于 0 并且小于或等于剩余的空闲字节数*/  
            if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )  
            {  
                /* Traverse the list from the start (lowest address) block until  
                 * one of adequate size is found. */                pxPreviousBlock = &xStart;  
                pxBlock = xStart.pxNextFreeBlock;  
  
                /*遍历空闲块列表,找到一个足够大小的块*/  
                while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )  
                {  
                    pxPreviousBlock = pxBlock;  
                    pxBlock = pxBlock->pxNextFreeBlock;  
                }  
  
                /* If the end marker was reached then a block of adequate size  
                 * was not found. */                /*如果找到合适大小的空闲块,则从空闲块列表中移除该块,并根据情况决定是否将该块分割成两部分*/  
                if( pxBlock != pxEnd )  
                {  
                    /* Return the memory space pointed to - jumping over the  
                     * BlockLink_t structure at its start. */                    /*返回指向的内存空间 - 从 BlockLink_t 结构的开始跳过去,  
                     * 即直接返回内存空间地址,返回的内存空间不包含 BlockLink_t 结构*/  
                    pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );  
  
                    /* This block is being returned for use so must be taken out  
                     * of the list of free blocks. */                    /*将pxBlock从空闲块列表中移除*/  
                    pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;  
  
                    /* If the block is larger than required it can be split into  
                     * two. */                    /*如果找到的空闲块比希望分配的内存大,则将该块分割成两部分*/  
                    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 );  
  
                        /* Calculate the sizes of two blocks split from the  
                         * single block. */                        pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;  
                        pxBlock->xBlockSize = xWantedSize;  
  
                        /* Insert the new block into the list of free blocks. */  
                        /*将新分割出来的空闲块插入到空闲块列表中*/  
                        prvInsertBlockIntoFreeList( pxNewBlockLink );  
                    }  
                    else  
                    {  
                        mtCOVERAGE_TEST_MARKER();  
                    }  
  
                    /*更新空闲字节数*/  
                    xFreeBytesRemaining -= pxBlock->xBlockSize;  
  
                    if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )  
                    {  
                        /*更新历史最小空闲字节数*/  
                        xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;  
                    }  
                    else  
                    {  
                        mtCOVERAGE_TEST_MARKER();  
                    }  
  
                    /* The block is being returned - it is allocated and owned  
                     * by the application and has no "next" block. */                    /*将找到的空闲块标记为已分配状态*/  
                    heapALLOCATE_BLOCK( pxBlock );  
                    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 )        {            vApplicationMallocFailedHook();        }        else        {            mtCOVERAGE_TEST_MARKER();        }    }    #endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */  
  
    configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );  
    return pvReturn;  
}
  • 在分配内存时,函数会检查指定的内存大小是否有效,并根据需要调整大小以容纳 BlockLink_t 结构体和对齐要求。
  • 如果找到的空闲块比请求的大小大,函数会将该块分割,并将剩余部分重新插入空闲块链表。
  • 它还维护了一些全局变量以记录内存分配的统计信息,如剩余空闲字节数、最小空闲字节数等。
  • 函数在分配内存期间会暂停调度器,确保操作的原子性和线程安全。

vPortFree

  1. 把申请释放的内存地址前移xHeapStructSize找到内存头部的位置
  2. 确认要释放的内存块已被分配且不在空闲块链表中。
  3. 将该内存块标记为未分配状态。
  4. 更新空闲字节数,并记录释放操作。
  5. 将释放的内存块插入到空闲块链表中,并与相邻的空闲块合并以减少内存碎片。

注:如果没有设置这个宏configHEAP_CLEAR_MEMORY_ON_FREE的话,free 的内存块并没有清0,只是把它重新插到空闲块链表中,并标记为空闲状态

void vPortFree( void * pv )  
{  
    uint8_t * puc = ( uint8_t * ) pv;  
    BlockLink_t * pxLink;  
  
    if( pv != NULL )  
    {  
        /* The memory being freed will have an BlockLink_t structure immediately  
         * before it. */        /*由于 pvPortMalloc 分配的内存块前有一个 BlockLink_t 结构,  
         * 这里将 puc 向前移动 xHeapStructSize 个字节以指向该结构,也是内存块的起始地址*/  
        puc -= xHeapStructSize;  
  
        /* This casting is to keep the compiler from issuing warnings. */  
        pxLink = ( void * ) puc;  
  
        /*这两个断言用于确保要释放的内存块确实已经被分配(即 xBlockSize 的最高位被设置)  
         * 并且目前不在空闲块链表中(即 pxNextFreeBlock 为 NULL)*/  
        configASSERT( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 );  
        configASSERT( pxLink->pxNextFreeBlock == NULL );  
  
        if( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 )  
        {  
            if( pxLink->pxNextFreeBlock == NULL )  
            {  
                /* The block is being returned to the heap - it is no longer  
                 * allocated. */                /*标记内存块为未分配状态*/  
                heapFREE_BLOCK( pxLink );  
                /*如果配置 configHEAP_CLEAR_MEMORY_ON_FREE 为 1,  
                 * 则使用 memset 将释放的内存块内容清零,以确保数据安全。*/  
                #if ( configHEAP_CLEAR_MEMORY_ON_FREE == 1 )  
                {  
                    ( void ) memset( puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize );                }                #endif  
  
                vTaskSuspendAll();  
                {  
                    /* Add this block to the list of free blocks. */  
                    /*更新空闲字节数*/  
                    xFreeBytesRemaining += pxLink->xBlockSize;  
                    traceFREE( pv, pxLink->xBlockSize );  
                    /*将释放的内存块插入空闲块链表*/  
                    prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );  
                    xNumberOfSuccessfulFrees++;  
                }  
                ( void ) xTaskResumeAll();  
            }  
            else  
            {  
                mtCOVERAGE_TEST_MARKER();  
            }  
        }  
        else  
        {  
            mtCOVERAGE_TEST_MARKER();  
        }  
    }  
}

prvInsertBlockIntoFreeList

  1. 遍历内存块列表寻址合适位置
  2. 判断插入空闲块能否和左边和右边空闲块合并,如果相等即可合并
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) /* PRIVILEGED_FUNCTION */  
{  
    BlockLink_t * pxIterator;/*用于遍历空闲内存块列表的指针*/  
    uint8_t * puc;  
  
    /* Iterate through the list until a block is found that has a higher address  
     * than the block being inserted. */    /*在列表中迭代,直到找到地址高于插入区块的区块为止*/  
    for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )  
    {  
        /* Nothing to do here, just iterate to the right position. */  
        /*从空闲内存块列表的起始块 xStart 开始遍历。  
         寻找合适的位置,使得要插入的内存块 pxBlockToInsert 的地址大于当前遍历到的内存块 pxIterator,  
         但是小于下一个内存块 pxIterator->pxNextFreeBlock 的地址。  
         这确保了内存块列表始终按内存地址从小到大排序。*/  
    }  
  
    /* Do the block being inserted, and the block it is being inserted after  
     * make a contiguous block of memory? */    /*插入的数据块和其后插入的数据块是否是一个连续的内存块*/  
    puc = ( uint8_t * ) pxIterator;  
    /*如果 pxIterator 的结束地址与 pxBlockToInsert 的起始地址相同,则它们是相邻的空闲块*/  
    if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )  
    {  
        /*合并 pxIterator 和 pxBlockToInsert*/        pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;  
        pxBlockToInsert = pxIterator;/*将 pxBlockToInsert 指向合并后的块 pxIterator*/    }  
    else  
    {  
        mtCOVERAGE_TEST_MARKER();  
    }  
  
    /* Do the block being inserted, and the block it is being inserted before  
     * make a contiguous block of memory? */    puc = ( uint8_t * ) pxBlockToInsert;  
  
    /*如果 pxBlockToInsert 的结束地址与 pxIterator->pxNextFreeBlock 的起始地址相同,则它们是相邻的空闲块*/  
    if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )  
    {  
        if( pxIterator->pxNextFreeBlock != pxEnd )  
        {  
            /* Form one big block from the two blocks. */  
            /*合并 pxBlockToInsert 和 pxIterator->pxNextFreeBlock */            pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;  
            pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;  
        }  
        else  
        {  
            /*如果 pxIterator->pxNextFreeBlock 是最后一个空闲块(即 pxEnd),  
             * 则将 pxBlockToInsert->pxNextFreeBlock 直接指向 pxEnd。*/  
            pxBlockToInsert->pxNextFreeBlock = pxEnd;  
        }  
    }  
    else  
    {  
        /*插入到 pxIterator 和 pxIterator->pxNextFreeBlock 之间*/  
        /*如果和右边的不能合并,则将 pxBlockToInsert的pxNextFreeBlock指向pxIterator->pxNextFreeBlock*/  
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;  
    }  
  
    /* If the block being inserted plugged a gab, so was merged with the block  
     * before and the block after, then it's pxNextFreeBlock pointer will have     * already been set, and should not be set here as that would make it point     * to itself. */    if( pxIterator != pxBlockToInsert )  
    {  
        /*如果 pxIterator 和 pxBlockToInsert 不相同,说明没合并,将pxBlockToInsert插入到pxIterator之后。  
         * 上面通过检查有没有和右边合并,已经维护了pxBlockToInsert->pxNextFreeBlock的正确性  
         * 这样就将 pxBlockToInsert 插入到 pxIterator 和 pxIterator->pxNextFreeBlock 之间了。  
         如果 pxIterator 和 pxBlockToInsert 相同,说明该内存块已经被合并,不需要重新插入。*/  
        pxIterator->pxNextFreeBlock = pxBlockToInsert;  
    }  
    else  
    {  
        mtCOVERAGE_TEST_MARKER();  
    }  
}

heap4_c所谓的空闲内存可合并,只是合并相邻的内存碎片,这是基于内存池的线性连续而设计的。

因此,为了尽可能的减少内存碎片,提升内存合并的作用,尽可能把上电后不释放的动态内存在初始化阶段申请(比如说动态分配的任务,包括TCB和任务栈),然后对于重复申请释放的动态内存,在初始化阶段结束后再分配和使用。也就是说,堆的前半部分内存都用于不释放的动态内存,然后后半部分就用来一些频繁申请释放的动态内存

vPortGetHeapStats

从 FreeRTOS 的堆管理中收集统计信息,并将其存储在一个 HeapStats_t 结构体中。具体来说,它遍历空闲内存块链表,统计空闲块的数量,计算最大的空闲块和最小的空闲块的大小,最后将这些信息以及总的可用堆空间大小、成功的内存分配和释放次数、历史上最少的空闲堆空间大小存储到 HeapStats_t 结构体中。通过暂停任务调度和使用临界区,确保了在统计期间堆的状态不会被其他任务或中断改变,从而保证了统计信息的准确性

/* Used to pass information about the heap out of vPortGetHeapStats(). */  
typedef struct xHeapStats  
{  
    size_t xAvailableHeapSpaceInBytes;      /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */  
    size_t xSizeOfLargestFreeBlockInBytes;  /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */  
    size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */  
    size_t xNumberOfFreeBlocks;             /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */  
    size_t xMinimumEverFreeBytesRemaining;  /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */  
    size_t xNumberOfSuccessfulAllocations;  /* The number of calls to pvPortMalloc() that have returned a valid memory block. */  
    size_t xNumberOfSuccessfulFrees;        /* The number of calls to vPortFree() that has successfully freed a block of memory. */  
} HeapStats_t;
void vPortGetHeapStats( HeapStats_t * pxHeapStats )  
{  
    BlockLink_t * pxBlock;//用于遍历空闲内存块列表的指针  
    size_t xBlocks = 0, xMaxSize = 0, xMinSize = portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */  
  
    vTaskSuspendAll();  
    {  
        pxBlock = xStart.pxNextFreeBlock;  
  
        /* pxBlock will be NULL if the heap has not been initialised.  The heap  
         * is initialised automatically when the first allocation is made. */        /* pxBlock 为 NULL 的情况表示堆尚未初始化。堆会在第一次内存分配时自动初始化。 */        if( pxBlock != NULL )  
        {  
            /*遍历空闲块链表,直到找到 pxEnd 结束块*/  
            while( pxBlock != pxEnd )  
            {  
                /* Increment the number of blocks and record the largest block seen  
                 * so far. */                /*统计空闲块的数量和最大及最小的空闲块*/  
                xBlocks++;  
  
                if( pxBlock->xBlockSize > xMaxSize )  
                {  
                    xMaxSize = pxBlock->xBlockSize;  
                }  
  
                if( pxBlock->xBlockSize < xMinSize )  
                {  
                    xMinSize = pxBlock->xBlockSize;  
                }  
  
                /* Move to the next block in the chain until the last block is  
                 * reached. */                pxBlock = pxBlock->pxNextFreeBlock;  
            }  
        }  
    }  
    ( void ) xTaskResumeAll();  
  
    pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize;  
    pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize;  
    pxHeapStats->xNumberOfFreeBlocks = xBlocks;  
  
    /*使用临界区保护,以确保在填充统计信息时不会被其他中断或任务改变:*/  
    taskENTER_CRITICAL();  
    {  
        pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining;// 填充可用堆空间大小  
        pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations;// 填充成功的分配次数  
        pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees;// 填充成功的释放次数  
        pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining;// 填充历史上最少的空闲堆空间大小  
    }  
    taskEXIT_CRITICAL();  
}

你可能感兴趣的:(FreeRTOS学习,嵌入式,FreeRTOS,内存管理)