FreeRTOS --(5)内存管理 heap4
阅读原文时间:2022年05月22日阅读:1

FreeRTOS 中的 heap 4 内存管理,可以算是 heap 2 的增强版本,在 《FreeRTOS --(3)内存管理 heap2》中,我们可以看到,每次内存分配后都会产生一个内存块,多次分配后,会产生很多内存碎片,在较为复杂的场景(需要经常动态分配和释放场景)下,几乎是无法胜任;

所以就有了 heap 4,它相比 heap 2 来说,提供了相邻空闲的内存块合并的功能,一定程度上减少了内存碎片,使得释放了的内存能够再度合并称为较为大的内存块,以供有大内存块的分配场景使用;

1、内存大小

和 heap 2 一样,用于内存管理的内存大小来自于一个大数组,数组的下标就是整个需要被管理的内存的大小,这个是和具体芯片所支持的 RAM 大小相关:

configTOTAL_HEAP_SIZE

被管理的内存定义为:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

ucHeap 就是管理的对象;

这些基本的结构都是和之前的 heap2 保持一致,不再多说;

2、对齐

对齐的部分也是和 heap 2 一致,不在多说,更多的参考:《FreeRTOS --(3)内存管理 heap2》的对齐章节;

3、内存块

内存块的定义依然是:

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;

没有任何差别,依然有 xStart 和 xEnd 来描述两个 marker,和 heap 2 几乎一样《FreeRTOS --(3)内存管理 heap2》;这里不再多说;

4、内存初始化

和 heap 2 一样,内存初始化使用 prvHeapInit,在第一次调用内存分配的时候,检查是否有初始化,否则进行 prvHeapInit 的调用,初始化相关的结构:

static void prvHeapInit( void )
{
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 );  

}

从编码风格的角度上来说,看上去感觉和 heap 2 不是同一个人写的(同样的逻辑,不同的表达),不过没关系,含义都是一样的;

依然是初始化了 xStart 和 xEnd 两个 marker;做了一些对齐处理后,将能够分配的所有的空间一并挂到了 xStart->pxNextFreeBlock 链表;

和 heap 2 不一样的是,heap 4 定义了一个标记,以表示内存是否有被使用,这里定义了 xBlockAllocatedBit;如果是 32bit CPU 的话,这个 xBlockAllocatedBit = 0x01<<31;32 bit 的最高位,这显然是安全的;

5、内存分配

内存分配,还是使用的 pvPortMalloc 接口,正确分配,返回可用的内存地址,出错返回 NULL:

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 is increased so it can contain a BlockLink\_t  
        structure in addition to the requested amount of bytes. \*/  
        if( xWantedSize > 0 )  
        {  
            xWantedSize += xHeapStructSize;

            /\* Ensure that blocks are always aligned to the required number  
            of bytes. \*/  
            if( ( xWantedSize & portBYTE\_ALIGNMENT\_MASK ) != 0x00 )  
            {  
                /\* Byte alignment required. \*/  
                xWantedSize += ( portBYTE\_ALIGNMENT - ( xWantedSize & portBYTE\_ALIGNMENT\_MASK ) );  
                configASSERT( ( xWantedSize & portBYTE\_ALIGNMENT\_MASK ) == 0 );  
            }  
            else  
            {  
                mtCOVERAGE\_TEST\_MARKER();  
            }  
        }  
        else  
        {  
            mtCOVERAGE\_TEST\_MARKER();  
        }

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

configASSERT( ( ( ( size\_t ) pvReturn ) & ( size\_t ) portBYTE\_ALIGNMENT\_MASK ) == 0 );  
return pvReturn;  

}

内存分配部分的代码和之前的 heap 2 的代码几乎完全一样,这里不做过多的解释;

6、内存释放

内存释放是 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 ) );  
            }  
            ( void ) xTaskResumeAll();  
        }  
        else  
        {  
            mtCOVERAGE\_TEST\_MARKER();  
        }  
    }  
    else  
    {  
        mtCOVERAGE\_TEST\_MARKER();  
    }  
}  

}

释放的时候,将之前的内存通过调用 prvInsertBlockIntoFreeList 插入到 Free List 中,内存块的合并就体现在这个函数;

当释放内存的时候调用到了 prvInsertBlockIntoFreeList,它实现了相邻内存块的合并工作,这也是 heap 4 与 heap 2 最大不同的地方:

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
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();  
}  

}

在 heap 2中,也有这个函数,但是它是按照内存块的小到大进行排序,由于有相邻内存块合并的要求,所以在 heap 4 中,内存块链表的组织形式,是按照他们的地址顺序由小到大组织的;

首先通过 xStart 开始,获取第一个空闲块的地址,并比较它的下一个空闲块地址和待插入空闲块地址的大小,以便获得合适的插入位置;

获得合适的位置了以后,判断待插入的这个空闲块是否和前一个空闲块相邻,如果相邻的话,就将两个块合并到一起:

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

然后在判断是否和后一个空闲块也相邻,如果相邻,也将他们合并,当然这里需要判断是否是紧挨着 xEnd:

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

打个比方:

多次分配和释放后,内存布局如下所示:

如果要释放中间那个内存,那么就会触发向上和向下的合并:

官方针对这个 heap 4 的官图为:

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章