FreeRTOS源码分析:heap

FreeRTOS堆内存管理机制分析

FreeRTOS提供5种堆内存管理方案(heap_1.c至heap_5.c),每种方案针对不同应用场景设计,源码位于FreeRTOS/Source/portable/MemMang目录。

标准 C 库 malloc() 和 free() 函数分配堆空间有以下缺点:

  • 它们在嵌入式系统上并不总是可用。
  • 它们占用了宝贵的代码空间。
  • 它们不是线程安全的。
  • 它们不是确定性的 (执行函数所需时间将因调用而异)。

FreeRTOS 将内存分配 API 保留在其可移植层,提供了五种内存管理算法:

  • heap_1:最简单,不允许释放内存。
  • heap_2:允许释放内存,但不会合并相邻的空闲块。
  • heap_3:简单包装了标准 malloc() 和 free(),以保证线程安全。
  • heap_4:合并相邻的空闲块以避免碎片化。包含绝对地址放置选项。
  • heap_5:如同 heap_4,能够跨越多个不相邻内存区域的堆。

关键配置参数

configTOTAL_HEAP_SIZE:定义堆总大小 configAPPLICATION_ALLOCATED_HEAP:允许用户自定义堆位置 configHEAP_CLEAR_MEMORY_ON_FREE:释放时清零内存

实际选择需考虑应用场景的内存分配模式、实时性要求和硬件资源限制。

heap_1 实现原理

最简单的内存分配方案,仅支持pvPortMalloc()不支持vPortFree()。适用于创建任务后不再删除任务的场景。 内存分配采用静态数组形式:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];     //默认大小17k

pvPortMalloc( size_t xWantedSize )



/*-----------------------------------------------------------*/

void * pvPortMalloc( size_t xWantedSize )
{
    void * pvReturn = NULL;
    static uint8_t * pucAlignedHeap = NULL;

/**************** portBYTE_ALIGNMENT 默认等于8,下面部分就是做字节对齐的 ******************/
    #if ( portBYTE_ALIGNMENT != 1 )         //如果portBYTE_ALIGNMENT !=1,需要对齐操作 
    {
        if( xWantedSize & portBYTE_ALIGNMENT_MASK )    //查看申请的内存大小是否是8的倍数
        {
            
            if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
            {
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
            }
            else
            {
                xWantedSize = 0;
            }
        }
    }
    #endif /* if ( portBYTE_ALIGNMENT != 1 ) */
/**************************************************************************************/
    
/************** 检查空堆空间是否分配成功,对齐,更新xNextFreeByte,指向未分配的空间 **********/
    vTaskSuspendAll();
    {
        if( pucAlignedHeap == NULL )
        {
            /* Ensure the heap starts on a correctly aligned boundary. */
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }

        /* Check there is enough room left for the allocation and. */
        if( ( xWantedSize > 0 ) &&                                /* valid size */
            ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
        {
            /* Return the next free byte then increment the index past this
             * block. */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }

        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();
/***********************************************************************************/
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            vApplicationMallocFailedHook();
        }
    }
    #endif

    return pvReturn;
}
/*-----------------------------------------------------------*/


void vPortInitialiseBlocks( void )
{
    /* Only required when static memory is not cleared. */
    xNextFreeByte = ( size_t ) 0;
}
/*-----------------------------------------------------------*/

size_t xPortGetFreeHeapSize( void )
{
    return( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}
  • heap1通过静态变量xNextFreeByte跟踪剩余空间
  • 由于heap1并没有记录每个块其实结束地址(大小),无法知道该怎么释放,所以heap1没有释放内存的操作

heap_2 实现特点

支持内存分配与释放,使用最佳匹配算法但会产生碎片。采用链表结构管理空闲块:

typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK * pxNextFreeBlock; /*指向块*/
    size_t xBlockSize;                     /*块大小*/
} BlockLink_t;
static void prvHeapInit( void )

FreeRTOS源码分析:heap_第1张图片

pvPortMalloc( size_t xWantedSize ):

void * pvPortMalloc( size_t xWantedSize )
{
    BlockLink_t * pxBlock;
    BlockLink_t * pxPreviousBlock;
    BlockLink_t * pxNewBlockLink;
    /***** xHeapHasBeenInitialised用于判断是否是第一次申请堆空间 *****/
    PRIVILEGED_DATA static BaseType_t xHeapHasBeenInitialised = pdFALSE;
    void * pvReturn = NULL;
    size_t xAdditionalRequiredSize;

    vTaskSuspendAll();                            //关中断
    {
        if( xHeapHasBeenInitialised == pdFALSE )
        {
            prvHeapInit();                        //初始化堆空间
            xHeapHasBeenInitialised = pdTRUE;
        }
        
        if( xWantedSize > 0 )
        {
            //xWantedSize 向上对齐
            xAdditionalRequiredSize = heapSTRUCT_SIZE + portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK );
            //溢出检查
            if( heapADD_WILL_OVERFLOW( xWantedSize, xAdditionalRequiredSize ) == 0 )
            {
                xWantedSize += xAdditionalRequiredSize;  //如果不会溢出,调整wantedSize大小
            }
            else
            {
                xWantedSize = 0;
            }
        }
/******************************
如果xWantedSize合法,遍历链表依据xBlockSize 确定链表插入位置
******************************/
        if( heapBLOCK_SIZE_IS_VALID( xWantedSize ) != 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 )
                    {
                        /* 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 );

                        /* 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 ) );
                    }

                    xFreeBytesRemaining -= pxBlock->xBlockSize;

                    /* The block is being returned - it is allocated and owned
                     * by the application and has no "next" block. */
                    heapALLOCATE_BLOCK( pxBlock );
                    pxBlock->pxNextFreeBlock = NULL;
                }
            }
        }

        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();

    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            vApplicationMallocFailedHook();
        }
    }
    #endif

    return pvReturn;
}
/*-----------------------------------------------------------*/

FreeRTOS源码分析:heap_第2张图片

  • 用掉的块不会添加到空闲列表中,空闲链表中的块指向的内存是可以分配的。
  • 随着分配再释放block的位置和所管理的空间已经被分配好了,可以释放完再使用,但是无法重新分配其大小以及位置,会产生大量的碎片化空间,heap4会做碎片化空间的管理,但是heap2不会。

heap_3 标准库适配

对标准库malloc/free进行线程安全封装:

void *pvPortMalloc( size_t xWantedSize ) {
    vTaskSuspendAll();
    pvReturn = malloc( xWantedSize );
    xTaskResumeAll();
}

通过挂起调度器保证原子操作。

heap_4 优化方案

合并相邻空闲块减少碎片,采用首次适应算法:

if( ( puc + xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ) {
    xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
    pxIterator->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}

你可能感兴趣的:(FreeRTOS,stm32)