[Feature] Support pvPortRealloc

This commit is contained in:
skb666 2025-12-25 21:36:23 +08:00 committed by Anubhav Rawal
parent 67f59a5f58
commit bf149971b6
6 changed files with 497 additions and 0 deletions

View file

@ -72,6 +72,7 @@
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSUPPORT_HEAP_REALLOC 0
#define configTOTAL_HEAP_SIZE 4096U
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0

View file

@ -283,6 +283,11 @@
* https://www.freertos.org/Static_Vs_Dynamic_Memory_Allocation.html. */
#define configSUPPORT_DYNAMIC_ALLOCATION 1
/* Set configSUPPORT_HEAP_REALLOC to 1 to include FreeRTOS API functions
* that support reallocating memory blocks in the build. Set to 0 to exclude
* realloc support from the build. Defaults to 0 if left undefined. */
#define configSUPPORT_HEAP_REALLOC 0
/* Sets the total size of the FreeRTOS heap, in bytes, when heap_1.c, heap_2.c
* or heap_4.c are included in the build. This value is defaulted to 4096 bytes
* but it must be tailored to each application. Note the heap will appear in

View file

@ -2840,6 +2840,14 @@
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#endif
#ifndef configSUPPORT_HEAP_REALLOC
#define configSUPPORT_HEAP_REALLOC 0
#endif
#if ( ( configSUPPORT_HEAP_REALLOC > 0 ) && ( configSUPPORT_DYNAMIC_ALLOCATION != 1 ) )
#error configSUPPORT_HEAP_REALLOC cannot be used without dynamic allocation, but configSUPPORT_HEAP_REALLOC is not set to 1.
#endif
#if ( ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) && ( configSUPPORT_DYNAMIC_ALLOCATION != 1 ) )
#error configUSE_STATS_FORMATTING_FUNCTIONS cannot be used without dynamic allocation, but configSUPPORT_DYNAMIC_ALLOCATION is not set to 1.
#endif

View file

@ -189,6 +189,10 @@ void vPortGetHeapStats( HeapStats_t * pxHeapStats );
void * pvPortMalloc( size_t xWantedSize ) PRIVILEGED_FUNCTION;
void * pvPortCalloc( size_t xNum,
size_t xSize ) PRIVILEGED_FUNCTION;
#if ( configSUPPORT_HEAP_REALLOC == 1 )
void *pvPortRealloc( void *pv,
size_t xWantedSize ) PRIVILEGED_FUNCTION;
#endif
void vPortFree( void * pv ) PRIVILEGED_FUNCTION;
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION;
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION;

View file

@ -410,6 +410,244 @@ void vPortFree( void * pv )
}
/*-----------------------------------------------------------*/
#if ( configSUPPORT_HEAP_REALLOC == 1 )
/*
* pvPortRealloc - Reallocate memory block size
*
* Description: Resize an allocated memory block, attempting to expand or shrink
* the block in place. If in-place resize is not possible, allocate a new block
* and copy the data.
*
* Parameters:
* pv - Pointer to the previously allocated memory block
* xWantedSize - New requested size of user data (in bytes)
*
* Return Value:
* On success: Pointer to the new memory block (may be the same as original)
* On failure: NULL
*
* Behavior:
* - When pv is NULL, equivalent to pvPortMalloc(xWantedSize)
* - When xWantedSize is 0, equivalent to vPortFree(pv)
* - Resize strategy:
* 1. If new size <= original size, attempt to shrink the block
* 2. If new size > original size, attempt to expand by merging with adjacent free block
* 3. If in-place resize fails, allocate new block and copy data
*/
void *pvPortRealloc( void *pv,
size_t xWantedSize )
{
BlockLink_t *pxBlock;
BlockLink_t *pxPreviousBlock;
BlockLink_t *pxNewBlockLink;
BlockLink_t *pxAdjacentFreeBlock;
void *pvReturn = NULL;
size_t xOriginalSize;
size_t xNewBlockSize;
size_t xAdditionalRequiredSize;
size_t xCopySize;
/* Handle NULL pointer case - equivalent to malloc */
if( pv == NULL )
{
return pvPortMalloc( xWantedSize );
}
/* Handle zero size case - equivalent to free */
if( xWantedSize == 0 )
{
vPortFree( pv );
return NULL;
}
/* Calculate new block size with overhead (header size and alignment) */
xNewBlockSize = xWantedSize;
if( heapADD_WILL_OVERFLOW( xNewBlockSize, xHeapStructSize ) == 0 )
{
xNewBlockSize += xHeapStructSize;
if( ( xNewBlockSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
xAdditionalRequiredSize = portBYTE_ALIGNMENT - ( xNewBlockSize & portBYTE_ALIGNMENT_MASK );
if( heapADD_WILL_OVERFLOW( xNewBlockSize, xAdditionalRequiredSize ) == 0 )
{
xNewBlockSize += xAdditionalRequiredSize;
}
else
{
return NULL;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
return NULL;
}
/* Get the block header from the user pointer and validate it */
pxBlock = ( BlockLink_t * )( ( uint8_t * )pv - xHeapStructSize );
heapVALIDATE_BLOCK_POINTER( pxBlock );
if( ( heapBLOCK_IS_ALLOCATED( pxBlock ) == 0 ) || ( pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( NULL ) ) )
{
return NULL;
}
/* Calculate the original block size (without the allocated bit)
* Check if there's enough free memory to expand the block */
xOriginalSize = pxBlock->xBlockSize & ~heapBLOCK_ALLOCATED_BITMASK;
if( ( xOriginalSize < xNewBlockSize ) && ( xFreeBytesRemaining < ( xNewBlockSize - xOriginalSize ) ) )
{
/* Not enough memory to expand the block */
return NULL;
}
/* Calculate the amount of user data to copy (excluding the block header).
* The user data size is the block size minus the header size.
* Limit the copy size to the requested size to avoid copying too much data. */
configASSERT( heapSUBTRACT_WILL_UNDERFLOW( xOriginalSize, xHeapStructSize ) == 0 );
xCopySize = xOriginalSize - xHeapStructSize;
if( xWantedSize < xCopySize )
{
xCopySize = xWantedSize;
}
/* Enter critical section - protect heap structure from concurrent access */
vTaskSuspendAll();
{
/* Case 1: Shrink the block (new size is smaller than or equal to original)
* Check if the remaining space is large enough to create a separate free block */
if( xNewBlockSize <= xOriginalSize )
{
/* Create a new free block from the remaining space */
if( ( xOriginalSize - xNewBlockSize ) > heapMINIMUM_BLOCK_SIZE )
{
pxNewBlockLink = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xNewBlockSize );
configASSERT( ( ( ( size_t )pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
pxNewBlockLink->xBlockSize = xOriginalSize - xNewBlockSize;
xFreeBytesRemaining += pxNewBlockLink->xBlockSize;
prvInsertBlockIntoFreeList( pxNewBlockLink );
heapFREE_BLOCK( pxBlock );
pxBlock->xBlockSize = xNewBlockSize;
heapALLOCATE_BLOCK( pxBlock );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pvReturn = pv;
}
else
{
/* Case 2: Try to expand by merging with next free block */
pxAdjacentFreeBlock = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xOriginalSize );
configASSERT( ( ( ( size_t )pxAdjacentFreeBlock ) & portBYTE_ALIGNMENT_MASK ) == 0 );
/* Traverse the free list to find if the adjacent block is actually free.
* The free list is ordered by address, so we can search efficiently.*/
pxPreviousBlock = &xStart;
while( ( pxPreviousBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( pxAdjacentFreeBlock ) ) &&
( pxPreviousBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( NULL ) ) )
{
pxPreviousBlock = heapPROTECT_BLOCK_POINTER( pxPreviousBlock->pxNextFreeBlock );
heapVALIDATE_BLOCK_POINTER( pxPreviousBlock );
}
if( pxPreviousBlock->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER( pxAdjacentFreeBlock ) )
{
configASSERT( heapBLOCK_SIZE_IS_VALID( pxAdjacentFreeBlock->xBlockSize ) );
if( !heapADD_WILL_OVERFLOW( xOriginalSize, pxAdjacentFreeBlock->xBlockSize ) )
{
/* Found a suitable adjacent free block that can provide enough space. */
if( ( xOriginalSize + pxAdjacentFreeBlock->xBlockSize ) >= xNewBlockSize )
{
/* Remove the adjacent free block from the free list and merge it with the allocated block. */
pxPreviousBlock->pxNextFreeBlock = pxAdjacentFreeBlock->pxNextFreeBlock;
xFreeBytesRemaining -= pxAdjacentFreeBlock->xBlockSize;
heapFREE_BLOCK( pxBlock );
pxBlock->xBlockSize = xOriginalSize + pxAdjacentFreeBlock->xBlockSize;
/* If the merged block is larger than needed, split the excess space
* into a new free block. */
if( ( pxBlock->xBlockSize - xNewBlockSize ) > heapMINIMUM_BLOCK_SIZE )
{
pxNewBlockLink = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xNewBlockSize );
configASSERT( ( ( ( size_t )pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xNewBlockSize;
xFreeBytesRemaining += pxNewBlockLink->xBlockSize;
prvInsertBlockIntoFreeList( pxNewBlockLink );
pxBlock->xBlockSize = xNewBlockSize;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
heapALLOCATE_BLOCK( pxBlock );
pvReturn = pv;
/* Update minimum free size statistic if memory was consumed */
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* Exit critical section - heap structure modification complete */
( void ) xTaskResumeAll();
/* Case 3: If in-place resize failed, allocate a new block and move the data.
* This is more expensive as it involves:
* 1. Allocating a new block with the requested size
* 2. Copying the user data from the old block to the new block
* 3. Freeing the old block
* Note: Statistics are updated by the called functions (malloc and free). */
if( pvReturn == NULL )
{
pvReturn = pvPortMalloc( xWantedSize );
if( pvReturn != NULL )
{
/* Copy user data from old block to new block (up to the smaller of old or new size) */
( void )memcpy( pvReturn, pv, xCopySize );
vPortFree( pv );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( ( ( ( size_t )pvReturn ) & ( size_t )portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
#endif /* if ( configSUPPORT_HEAP_REALLOC == 1 ) */
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;

View file

@ -445,6 +445,244 @@ void vPortFree( void * pv )
}
/*-----------------------------------------------------------*/
#if ( configSUPPORT_HEAP_REALLOC == 1 )
/*
* pvPortRealloc - Reallocate memory block size
*
* Description: Resize an allocated memory block, attempting to expand or shrink
* the block in place. If in-place resize is not possible, allocate a new block
* and copy the data.
*
* Parameters:
* pv - Pointer to the previously allocated memory block
* xWantedSize - New requested size of user data (in bytes)
*
* Return Value:
* On success: Pointer to the new memory block (may be the same as original)
* On failure: NULL
*
* Behavior:
* - When pv is NULL, equivalent to pvPortMalloc(xWantedSize)
* - When xWantedSize is 0, equivalent to vPortFree(pv)
* - Resize strategy:
* 1. If new size <= original size, attempt to shrink the block
* 2. If new size > original size, attempt to expand by merging with adjacent free block
* 3. If in-place resize fails, allocate new block and copy data
*/
void *pvPortRealloc( void *pv,
size_t xWantedSize )
{
BlockLink_t *pxBlock;
BlockLink_t *pxPreviousBlock;
BlockLink_t *pxNewBlockLink;
BlockLink_t *pxAdjacentFreeBlock;
void *pvReturn = NULL;
size_t xOriginalSize;
size_t xNewBlockSize;
size_t xAdditionalRequiredSize;
size_t xCopySize;
/* Handle NULL pointer case - equivalent to malloc */
if( pv == NULL )
{
return pvPortMalloc( xWantedSize );
}
/* Handle zero size case - equivalent to free */
if( xWantedSize == 0 )
{
vPortFree( pv );
return NULL;
}
/* Calculate new block size with overhead (header size and alignment) */
xNewBlockSize = xWantedSize;
if( heapADD_WILL_OVERFLOW( xNewBlockSize, xHeapStructSize ) == 0 )
{
xNewBlockSize += xHeapStructSize;
if( ( xNewBlockSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
xAdditionalRequiredSize = portBYTE_ALIGNMENT - ( xNewBlockSize & portBYTE_ALIGNMENT_MASK );
if( heapADD_WILL_OVERFLOW( xNewBlockSize, xAdditionalRequiredSize ) == 0 )
{
xNewBlockSize += xAdditionalRequiredSize;
}
else
{
return NULL;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
return NULL;
}
/* Get the block header from the user pointer and validate it */
pxBlock = ( BlockLink_t * )( ( uint8_t * )pv - xHeapStructSize );
heapVALIDATE_BLOCK_POINTER( pxBlock );
if( ( heapBLOCK_IS_ALLOCATED( pxBlock ) == 0 ) || ( pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( NULL ) ) )
{
return NULL;
}
/* Calculate the original block size (without the allocated bit)
* Check if there's enough free memory to expand the block */
xOriginalSize = pxBlock->xBlockSize & ~heapBLOCK_ALLOCATED_BITMASK;
if( ( xOriginalSize < xNewBlockSize ) && ( xFreeBytesRemaining < ( xNewBlockSize - xOriginalSize ) ) )
{
/* Not enough memory to expand the block */
return NULL;
}
/* Calculate the amount of user data to copy (excluding the block header).
* The user data size is the block size minus the header size.
* Limit the copy size to the requested size to avoid copying too much data. */
configASSERT( heapSUBTRACT_WILL_UNDERFLOW( xOriginalSize, xHeapStructSize ) == 0 );
xCopySize = xOriginalSize - xHeapStructSize;
if( xWantedSize < xCopySize )
{
xCopySize = xWantedSize;
}
/* Enter critical section - protect heap structure from concurrent access */
vTaskSuspendAll();
{
/* Case 1: Shrink the block (new size is smaller than or equal to original)
* Check if the remaining space is large enough to create a separate free block */
if( xNewBlockSize <= xOriginalSize )
{
/* Create a new free block from the remaining space */
if( ( xOriginalSize - xNewBlockSize ) > heapMINIMUM_BLOCK_SIZE )
{
pxNewBlockLink = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xNewBlockSize );
configASSERT( ( ( ( size_t )pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
pxNewBlockLink->xBlockSize = xOriginalSize - xNewBlockSize;
xFreeBytesRemaining += pxNewBlockLink->xBlockSize;
prvInsertBlockIntoFreeList( pxNewBlockLink );
heapFREE_BLOCK( pxBlock );
pxBlock->xBlockSize = xNewBlockSize;
heapALLOCATE_BLOCK( pxBlock );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pvReturn = pv;
}
else
{
/* Case 2: Try to expand by merging with next free block */
pxAdjacentFreeBlock = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xOriginalSize );
configASSERT( ( ( ( size_t )pxAdjacentFreeBlock ) & portBYTE_ALIGNMENT_MASK ) == 0 );
/* Traverse the free list to find if the adjacent block is actually free.
* The free list is ordered by address, so we can search efficiently.*/
pxPreviousBlock = &xStart;
while( ( pxPreviousBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( pxAdjacentFreeBlock ) ) &&
( pxPreviousBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER( NULL ) ) )
{
pxPreviousBlock = heapPROTECT_BLOCK_POINTER( pxPreviousBlock->pxNextFreeBlock );
heapVALIDATE_BLOCK_POINTER( pxPreviousBlock );
}
if( pxPreviousBlock->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER( pxAdjacentFreeBlock ) )
{
configASSERT( heapBLOCK_SIZE_IS_VALID( pxAdjacentFreeBlock->xBlockSize ) );
if( !heapADD_WILL_OVERFLOW( xOriginalSize, pxAdjacentFreeBlock->xBlockSize ) )
{
/* Found a suitable adjacent free block that can provide enough space. */
if( ( xOriginalSize + pxAdjacentFreeBlock->xBlockSize ) >= xNewBlockSize )
{
/* Remove the adjacent free block from the free list and merge it with the allocated block. */
pxPreviousBlock->pxNextFreeBlock = pxAdjacentFreeBlock->pxNextFreeBlock;
xFreeBytesRemaining -= pxAdjacentFreeBlock->xBlockSize;
heapFREE_BLOCK( pxBlock );
pxBlock->xBlockSize = xOriginalSize + pxAdjacentFreeBlock->xBlockSize;
/* If the merged block is larger than needed, split the excess space
* into a new free block. */
if( ( pxBlock->xBlockSize - xNewBlockSize ) > heapMINIMUM_BLOCK_SIZE )
{
pxNewBlockLink = ( BlockLink_t * )( ( ( uint8_t * )pxBlock ) + xNewBlockSize );
configASSERT( ( ( ( size_t )pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xNewBlockSize;
xFreeBytesRemaining += pxNewBlockLink->xBlockSize;
prvInsertBlockIntoFreeList( pxNewBlockLink );
pxBlock->xBlockSize = xNewBlockSize;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
heapALLOCATE_BLOCK( pxBlock );
pvReturn = pv;
/* Update minimum free size statistic if memory was consumed */
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* Exit critical section - heap structure modification complete */
( void ) xTaskResumeAll();
/* Case 3: If in-place resize failed, allocate a new block and move the data.
* This is more expensive as it involves:
* 1. Allocating a new block with the requested size
* 2. Copying the user data from the old block to the new block
* 3. Freeing the old block
* Note: Statistics are updated by the called functions (malloc and free). */
if( pvReturn == NULL )
{
pvReturn = pvPortMalloc( xWantedSize );
if( pvReturn != NULL )
{
/* Copy user data from old block to new block (up to the smaller of old or new size) */
( void )memcpy( pvReturn, pv, xCopySize );
vPortFree( pv );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( ( ( ( size_t )pvReturn ) & ( size_t )portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
#endif /* if ( configSUPPORT_HEAP_REALLOC == 1 ) */
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
@ -560,6 +798,9 @@ void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) /* PRIVI
portPOINTER_SIZE_TYPE xAddress;
const HeapRegion_t * pxHeapRegion;
/* Check for NULL pointer */
configASSERT( pxHeapRegions != NULL );
/* Can only call once! */
configASSERT( pxEnd == NULL );