freeRTOS动态内存heap4源码分析

news2024/9/24 11:32:58

1 前言

        随着功能安全的推广,动态内存分配在RTOS领域的用武之地将越来越小。但heap4毕竟是为RTOS量身打造,相对简单,作为堆内存管理的入门学习,仍是很不错的选择。

1.1 标准c库动态内存函数的弊端

        对于标准C库的malloc和free函数,freeRTOS官方的说法如下:

1.在嵌入式系统上并不完全通用(移植性不好);

2.占用了宝贵的代码空间(代码资源消耗大);

3.不是线程安全的(不安全);

4.执行时间不确定(实时性不好);

        因此,freeRTOS在可移植层(port层)封装了类似的接口,比如pvPortMalloc和vPortFree分别用于申请和释放动态内存。

        此外,FreeRTOS 提供了几种堆管理方案, 其复杂性和功能各不相同。 用户也可以提供自己的堆实现, 甚至同时使用两个堆实现。 同时使用两个堆实现 允许将任务堆栈和其他 RTOS 对象放置在 内部 RAM 中,并将应用程序数据放置在较慢的外部 RAM 中。

1.2 五种堆实现方式

  • heap_1 —— 最简单,,具有确定性,从静态数组中分配内存,不允许释放内存,不会导致内存碎片化,一锤子买卖,不算真正的动态内存分配;
  • heap_2—— 非确定性,允许释放内存,但不会合并相邻的空闲块,也就是说没有内存碎片优化措施;
  • heap_3 —— 简单包装了标准 malloc() 和 free(),以保证线程安全,借壳上市,需要连接器设置堆空间分布,且需要编译器库提供malloc和free函数的实现,可能回增加RTOS内核大小;
  • heap_4 —— 非确定性,使用第一适应算法(first fit,FF),支持合并相邻的空闲块以避免碎片化,允许将堆放置在内存中的特定地址,官方称比大多数标准 C 库 malloc 的实现要快;
  • heap_5—— 如同 heap_4,但能够跨越多个不相邻内存区域的堆,且使用动态内存分配前,必须调用 vPortDefineHeapRegions() 进行初始化。 

         目前来说,heap_4的使用最为广泛,也是下面分析的重点。

2. 源码分析

2.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 */

        显然,从上述代码中可以看出:

        ①传说中的内存池采用了最简单的线性数组结构,字节大小由configTOTAL_HEAP_SIZE定义;

         ②通过配置configAPPLICATION_ALLOCATED_HEAP可以由应用层自定义内存池ucHeap[ configTOTAL_HEAP_SIZE ],即可以由应用划分堆位于特定内存段,而RTOS引用该内存池;

2.2 内存块链表结构

        首先看链表的数据结构:

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;

/* Create a couple of list links to mark the start and end of the list. */
PRIVILEGED_DATA static BlockLink_t xStart, * pxEnd = NULL;

         显然,该链表记录了空闲内存块的信息(姑且称为内存块头),包括内存块大小和指向下一个内存块头的指针,并由两个静态链表元素指针xStartpxEnd标识开头和结尾。从该链表元素构成可以看出,每次申请动态内存,堆中至少需要多划分处8个字节的空间来存放内存块头,随后就是应用层申请的动态内存空间。可以想象,在释放内存时,根据动态内存的起始地址,向前偏移sizeof(BlockLink_t)字节的位置,就是该内存块头的位置,在32位的ARM V7下,该偏移量为8字节。

        在堆内存池进行初始化后,由于整个内存池由一个线性数组提供,还没有划分过内存块出去,空闲空间还是一块连续的大内存,链表的状态如图1所示:

图1 堆初始化后内存块链表示意图

       其中,图1中假设了ucHeap的起始地址为0x3400 0002(为了更好的演示8字节对齐的情况,通常最好使用已对齐地址),configTOTAL_HEAP_SIZE为128k,除了xStart外,其余内存控制块的实体都是在内存池中,只是以指针的形式来访问。至于为什么要8字节对齐呢?据说,是AAPCS (Procedure Call Standard for the ARM Architecture)规范的要求,本质还是为了兼容64位对齐访存吧。

/* Block sizes must not get too small. */
#define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( xHeapStructSize << 1 ) )

        此外,heapMINIMUM_BLOCK_SIZE限制了最小内存块的大小,在32位CPU的情况下,该值位16字节。当内存碎片化到小于此值时,heap_4 就不再维护并使用该内存碎片了。

2.3 堆状态字

        再来点直观的,用户可以通过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;

       其中:

        ①xAvailableHeapSpaceInBytes记录了当前堆空间中所有剩余的内存大小,单位为字节,在prvHeapInit的时候,该值达到人生巅峰,随后,每次pvPortMalloc时,都会自裁掉分配出去的空间;每次vPortFree的时候,又会加回来回收的内存空间,总之它记录了整个堆内存池中所有的可分配内存大小,这些内存有可能时连续的,也可能是分布在不连续的块中;在此基础上,xMinimumEverFreeBytesRemaining则是记录了xAvailableHeapSpaceInBytes的历史最小值,类似watermark的概念,可以用来评估堆内存空间大小是否够用

        ②xSizeOfLargestFreeBlockInBytes,xSizeOfSmallestFreeBlockInBytes和xNumberOfFreeBlocks分别表示堆内存池中,最大空闲内存块的的大小、最小空闲内存块的大小和空闲内存块的大小,这两个极值和空闲内存块总数量主要是通过遍历所有空闲内存块链表来获取的;

        ③xNumberOfSuccessfulAllocations和xNumberOfSuccessfulFrees则比较酱油,主要记录了成功pvPortMalloc和vPortFree的次数;

可以预想的是,heap_4维护了一些静态变量来记录堆内存的状态变量,尤其是对于①和③,果不其然:        

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;

         这些静态变量贯穿整个heap_4所有的API实现,记录了堆内存池的实时状态,再接下来的源码分析中,随处可见。

2.4 vPortGetHeapStats

        下面,是vPortGetHeapStats的源码:

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

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. */
        if( pxBlock != NULL )
        {
            do
            {
                /* 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;
            } while( pxBlock != pxEnd );
        }
    }
    ( 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();
}

        整个函数很简单,主要是遍历内存块链表获取总的内存块数、最大空闲内存块和最小空闲内存块大小,并从前文所说的静态状态字变量获取实时的内存池空闲空间大小、历史最小空闲空间大小、成功申请和释放内存的次数等。

2.5 prvHeapInit

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

static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */
{
    BlockLink_t * pxFirstFreeBlock;
    uint8_t * pucAlignedHeap;
    size_t uxAddress;
    size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

    /* Ensure the heap starts on a correctly aligned boundary. */
    uxAddress = ( size_t ) ucHeap;

    if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
    {
        uxAddress += ( portBYTE_ALIGNMENT - 1 );
        uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
        xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
    }

    pucAlignedHeap = ( uint8_t * ) uxAddress;

    /* 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.pxNextFreeBlock = ( void * ) pucAlignedHeap;
    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. */
    uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
    uxAddress -= xHeapStructSize;
    uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
    pxEnd = ( void * ) uxAddress;
    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. */
    pxFirstFreeBlock = ( void * ) pucAlignedHeap;
    pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
    pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

    /* Only one block exists - and it covers the entire usable heap space. */
    xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
    xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

    /* Work out the position of the top bit in a size_t variable. */
    xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
/*-----------------------------------------------------------*/

        该初始化函数主要完成了内存块链表的初始化,具体见2.2章节,值得注意的是:

        ①初始化完成后,xStart.pxNextFreeBlock指向了内存池对齐后的首地址,pxEnd指针则指向了内存池末尾倒数第二个对齐的位置(即不再是空指针),也就是说,除了掐头去尾的对齐外(如有必要),内存池完成初始化后,还要缩水8字节用于存放pxEnd指向的内存头,用于标识内存之的末尾;

        ②在初始化函数的结尾,初始化了一个静态变量,该变量本质上是一个只有最高位是1、其余位为0的无符号的long,可以理解为是一个用于标识内存块是否已经被分配的掩码,(不清楚为什么没采用宏或者const变量),这个掩码也贯穿了整个heap_4;

2.6 pvPortMalloc

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

void * pvPortMalloc( size_t xWantedSize )
{
    BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
    void * pvReturn = NULL;

    vTaskSuspendAll();
    {
        /* If this is the first call to malloc then the heap will require
         * initialisation to setup the list of free blocks. */
        if( pxEnd == NULL )
        {
            prvHeapInit();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* Check the requested block size 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( ( xWantedSize & xBlockAllocatedBit ) == 0 )
        {
            /* The wanted size must be increased so it can contain a BlockLink_t
             * structure in addition to the requested amount of bytes. */
            if( ( xWantedSize > 0 ) &&
                ( ( xWantedSize + xHeapStructSize ) >  xWantedSize ) ) /* Overflow check */
            {
                xWantedSize += xHeapStructSize;

                /* Ensure that blocks are always aligned. */
                if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
                {
                    /* Byte alignment required. Check for overflow. */
                    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
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                xWantedSize = 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. */
                    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. */
                    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. */
                    pxBlock->xBlockSize |= xBlockAllocatedBit;
                    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;
}

        其中:

        ①首次调用pvPortMalloc分配内存时,由于pxEnd还没初始化,所以会调用prvHeapInit对内存链表进行初始化;

        ②应用申请的内存大小为xWantedSize,该大小有上限,在32位CPU上,long为4字节,所以该上限为0x7FFFFFFF,嵌入式环境下也很少有这么大的内存池吧;

        ③由于需要额外的内存块头八字节的开销,xWantedSize会在应用申请的大小上自增八字节,这里的溢出校验在32位环境下显得没那么必要;

        ④随后,会对xWantedSize进行圆整,即向上八字节对齐;

        ⑤如果xWantedSize小于xFreeBytesRemaining(内存池空余的总内存大小,能分配的不充分必要条件),就从xStart开始遍历各空闲内存块,从内存块头上读取该内存块大小是否足够分配xWantedSize个字节;

        ⑥如果找到了足够大小的内存块,则将该空闲内存块头后的内存地址返回给应用,并将该内存块从内存块链表中剔除;在此基础上,如果该内存块剩余的空间大于heapMINIMUM_BLOCK_SIZE(32位上为8*2字节),则在此移植上建立新的内存块,并将该内存块头插入内存块链表中;

        ⑦如果内存分配成功,更新xFreeBytesRemaining,即减去xWantedSize(包含应用所需内存大小和内存头大小以及可能存在的对齐开销),并更新xMinimumEverFreeBytesRemaining(如果内存池剩余量创历史新低)和xNumberOfSuccessfulAllocations,并将返回给用户的该内存块头打上已分配的标记,即pxBlock->xBlockSize的最高位置1,同时将pxBlock->pxNextFreeBlock置为空指针;

        ⑧如果分配失败,且应用层注册了对应的钩子函数,则运行该函数;

        整体来看,动态内存对于的内存池的消耗,是要大于应用层申请的内存大小的,包括内存块头和对齐产生的内存开销,所以其优势还是在于灵活性。另一方面,内存块头中记录的pxBlock->xBlockSize的值包含了内存块BlockLink_t本身的大小,该内存块中可用的大小是pxBlock->xBlockSize - sizeof(BlockLink_t),缩水的这部分就是内存块头占用内存池的内存大小,这对于后边内存块链表操作的理解很重要。

2.7 prvInsertBlockIntoFreeList        

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

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. */
    }

    /* Do the block being inserted, and the block it is being inserted after
     * make a contiguous block of memory? */
    puc = ( uint8_t * ) pxIterator;

    if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
    {
        pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
        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;

    if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
    {
        if( pxIterator->pxNextFreeBlock != pxEnd )
        {
            /* Form one big block from the two blocks. */
            pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
            pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
        }
        else
        {
            pxBlockToInsert->pxNextFreeBlock = pxEnd;
        }
    }
    else
    {
        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->pxNextFreeBlock = pxBlockToInsert;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
/*-----------------------------------------------------------*/

        由于内存池是一片线性连续的内存,且其起始地址由xStart标识,且其起始地址是低地址,而后链表中的各内存块头记录的内存块起始地址必定是一山更比一山高的。所以,当一个内存块需要插入该链表时,其插入位置必定是在xStart标识的位置之后,且可以从链表中各内存块头标识的起始地址定位到插入位置。

        因此,在prvInsertBlockIntoFreeList函数中:

        ①首先从xStart标识的位置开始遍历链表中各空闲内存块头,找到一个位置,该位置前面的链表元素的地址比插入元素(pxBlockToInsert指针指向的元素,记为BlockToInsert)的地址低,而后面的链表元素地址比插入元素的地址高,此处即是插入位置;

        ②既然找到了插入位置,则比较BlockToInsert的首尾地址与前后链表元素标识的地址是否可以衔接起来,即边界处是否相等,如果可以衔接起来,则将其合并成一个大的内存块;

        所以说,heap_4所谓的内存碎片合并只能合并相邻的内存碎片,这归功于内存池的线性连续性,其碎片合并也反过来受制于整个线性连续上。反过来说,如果我们要使用heap_4的动态内存,对于那些上电后不释放的一类内存,要尽量集中在初始化时申请,对于那些需要反复申请并释放,甚至每次大小都不同的动态内存,则尽量在初始化完成后再开始使用。

2.8 vPortFree

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

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. */
        puc -= xHeapStructSize;

        /* This casting is to keep the compiler from issuing warnings. */
        pxLink = ( void * ) puc;

        /* Check the block is actually allocated. */
        configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
        configASSERT( pxLink->pxNextFreeBlock == NULL );

        if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
        {
            if( pxLink->pxNextFreeBlock == NULL )
            {
                /* The block is being returned to the heap - it is no longer
                 * allocated. */
                pxLink->xBlockSize &= ~xBlockAllocatedBit;

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

         其中:

        ①通过申请释放的内存地址pv前移xHeapStructSize个字节找到内存块头的位置,即pxLink指向的位置;

        ②将内存块头中xBlockSize的最高位中已分配的标识清除;

        ③关调度,将该内存块插入空闲内存块链表,并将其大小增加到xFreeBytesRemaining变量中去,完成后打开调度;

3.总结

        回过头来看:

        ①heap_4的内存是完全是建立在一片线性连续的内存上的,heapMINIMUM_BLOCK_SIZE的定义可以抑制过度的内存碎片化;

        ②而在申请和释放内存时,必然要有查询空闲块链表的操作,其所谓的不确定性也主要存在这里,即链表的查询时间;

        ③另外,除了链表的首个元素xStart外,其余链表元素(内存块头)都是占用内存池的空间,这也在一定程度上导致了可分配内存的缩水;

        ④如果需要且允许使用动态内存,heap_4支持指定内存池的起始地址,需要注意内存池是否与其他内存空间隔离开,防止内存踩踏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年【安全员-A证】免费试题及安全员-A证作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【安全员-A证】免费试题及安全员-A证作业考试题库&#xff0c;包含安全员-A证免费试题答案和解析及安全员-A证作业考试题库练习。安全生产模拟考试一点通结合国家安全员-A证考试最新大纲及安全员-A证考试真题汇…

动态规划——斐波那契问题(Java)

目录 什么是动态规划&#xff1f; 练习 练习1&#xff1a;斐波那契数 练习2&#xff1a;三步问题 练习3&#xff1a;使用最小花费爬楼梯 练习4&#xff1a;解码方法 什么是动态规划&#xff1f; 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;&…

8个国产全能型AI写作神器,给个标题就能自动生成全文 #其他#知识分享

国外ChatGPT爆火&#xff0c;AI写作在国内也引起不小的瞩目&#xff0c;目前国内的AI写作工具少说也有几十上百个&#xff0c;要在这么多AI写作中找出适合自己的工具&#xff0c;一个一个尝试是不太现实的&#xff0c;所以今天就给大家推荐一些款AI写作工具。帮助你少走弯路&am…

递归和递推的区别

目录 1、递推 2、递归 3、结言 递归 递推 1、递推 递推就是说从初值出发后一直运算到所需的结果。 ——从已知到未知。&#xff08;从小到大&#xff09; 举一个简单的例子&#xff1a; 每天能学习一个小时的编程&#xff0c;那么一个月之后可以学到三十小时的编程知识。…

SSL加密:保护数据传输的安全盾牌

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

尝试Docker Dev Environments

无法从本地目录创建容器环境 创建的容器环境无法在VS Code打开 从官方仓库打开 结果vscode报错。fine&#xff0c;告辞。老老实实用本地环境开发。

2024公认口碑最好的洗地机有哪些?若看重清洁力,这四款最值得买

每当我们要清洁卫生时&#xff0c;是否总是感到腰酸背痛、疲劳不堪&#xff0c;甚至头昏眼花&#xff1f;地板是家中的重要门面&#xff0c;不容忽视的卫生焦点。如今&#xff0c;我们终于多了一位家务打扫的救星——家用洗地地机。一次操作&#xff0c;即可完成扫地除尘、地除…

鸿蒙ArkUI【开发移植Carbon】

项目介绍 本项目是基于开源项目[Carbon] 进行harmonyos化的移植和开发的。 移植版本&#xff1a;Branches/master 这不是单纯只是API和基本功能展示demo&#xff0c;它是最有用的自定义控件的实现&#xff0c;如设计规范中所示。 Carbon试图&#xff1a; 让事情变得更简单&…

飞桨ONNX推理部署初探

ONNX&#xff0c;全称Open Neural Network Exchange&#xff08;开放神经网络交换&#xff09;&#xff0c;是一个用于表示深度学习模型的标准&#xff0c;它定义了一组与环境、平台均无关的标准格式。这使得不同的人工智能框架&#xff0c;如飞桨、MXNet等&#xff0c;可以采用…

【API调用gpt-4 (vision-preview)】基于微软的Azure OpenAI API

微软的Azure页面 &#xff1a; https://learn.microsoft.com/zh-cn/azure/ai-services/openai/concepts/models 调用代码&#xff1a;https://learn.microsoft.com/zh-cn/azure/ai-services/openai/how-to/switching-endpoints openai说明: https://platform.openai.com/docs/g…

Kubernetes kafka系列 | Strimzi 快速部署kafka集群 (可外部通信)

一、Strimzi介绍 Strimzi 是一个用于 Apache Kafka 在 Kubernetes 上部署和管理的开源项目。它提供了一组 Kubernetes 自定义资源定义(Custom Resource Definitions,CRDs)、控制器和操作符,使得在 Kubernetes 环境中轻松地部署、管理和操作 Kafka 集群成为可能。Strimzi 项…

[AIGC] 主流工作流引擎对比与适用场景介绍

主流工作流引擎对比与适用场景介绍 工作流引擎在业务流程管理中扮演着重要的角色&#xff0c;它可以帮助组织将复杂的工作流程自动化&#xff0c;降低错误率&#xff0c;提高工作效率。目前市面上有许多优秀的工作流引擎&#xff0c;各自都有着独特的优点和适用的场景。本文将介…

C++:类的6大默认成员函数(拷贝构造函数篇)

文章目录 1、拷贝构造函数的概念const用途 2、拷贝构造函数的特性浅拷贝/值拷贝 前言:Hello,大家好&#xff0c;咱这篇博客继续默认成员函数&#xff0c;今天的笔记分享为拷贝构造函数~ 1、拷贝构造函数的概念 在创建对象时&#xff0c;我们能否创建一个与已存在对象一某一样的…

【JS】深度学习JavaScript

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【JS】深度学习JavaScript &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一:JavaScript1.1 JavaScript是什么1.2 JS的引入方式1.3 JS变量1.4 数据类型1.5 …

(005)ssh Load key “./id_rsa“: invalid format (一直好好的,突然变心了)

文章目录 问题解决 问题 一直用的好好的&#xff0c;重装系统之后&#xff0c;使用 ssh命令&#xff0c;报了 &#xff1a; 解决 1.查看证书的换行符号&#xff1a; 2.证书的换行符需要是 “LF”&#xff1a;

鸿蒙ArkTS实战开发-Native XComponent组件的使用

介绍 本篇Codelab主要介绍如何使用XComponent组件调用NAPI来创建EGL/GLES环境&#xff0c;实现在主页面绘制一个正方形&#xff0c;并可以改变正方形的颜色。本篇CodeLab使用Native C模板创建。 如图所示&#xff0c;点击绘制矩形按钮&#xff0c;XComponent组件绘制区域中渲…

搭建Linux内核开发环境——保姆教程(持续更新中)

搭建Linux内核开发环境——保姆教程&#xff08;持续更新中&#xff09; git版本管理汇编器链接器调试器编辑器构建系统模拟器文档工具图形设计工具data manage 在此文中&#xff0c;持续完善&#xff0c;搭建内核开发环境的细节&#xff0c;有需要的小伙伴儿可以持续关注下 g…

[Java、Android面试]_13_map、set和list的区别

本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天整理一点&#xff0c;后续会陆续分享出来&#xff0c;感兴趣的朋友可关注收…

【python_往企业微信群中发送文件】

python_往企业微信群中发送文件 这个是用企业微信群机器人的功能&#xff0c;没有用到后台应用。群机器人 #-*- coding:utf-8-* import requests#类型&#xff1a;voice,file file_type"file" file_path"D:\desktop\不过.jpg" webhookkey"xxxx"#…

掌握这6大工具,自媒体ai写作之路畅通无阻! #知识分享#媒体#科技

从事自媒体运营光靠自己手动操作效率是非常低的&#xff0c;想要提高运营效率就必须要学会合理的使用一些辅助工具。下面小编就跟大家分享一些自媒体常用的辅助工具&#xff0c;觉得有用的朋友可以收藏分享。 1.元芳写作 这是一个微信公众号 面向专业写作领域的ai写作工具&am…