Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/coverity/FreeRTOSConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions examples/template_configuration/FreeRTOSConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions include/FreeRTOS.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_DYNAMIC_ALLOCATION 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
Expand Down
4 changes: 4 additions & 0 deletions include/portable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
238 changes: 238 additions & 0 deletions portable/MemMang/heap_4.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To adhere to MISRA rule 15.5, FreeRTOS doesn't use multiple return statements in a single function.
We'd need to refactor this a bit to accommodate that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the realloc function in the standard library does not follow the single-responsibility principle, the design here aims to maintain a consistent user experience with it. I would like to know whether the refactoring will alter the original behavior of the function.

}

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there not be a 4th case?

----------------------------------------------------------------------------------------------
|  \\\\ block full \\\\  |    free block   |    Current block  |    \\\\\\ block full \\\\\\ |      
----------------------------------------------------------------------------------------------

free block is 200 bytes long, the current block is 300 bytes long, and we call pvRealloc with size 400. Ideally, it would fit by merging the current block with the free block and moving data forward.

But in the algorithm that you propose, we are ignoring that possibility altogether.

Let me know if I am missing something.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I overlooked the consideration of this scenario, and I appreciate your pointing it out. I have two proposed approaches:

Option 1: First merge with the subsequent free block, and then consider merging with the preceding free block.
Option 2: Merge the current block with both the preceding and subsequent free blocks as a single unit, then perform the judgment and operation.

Which one do you think is more appropriate?

configASSERT( ( ( ( size_t )pvReturn ) & ( size_t )portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
#endif /* if ( configSUPPORT_HEAP_REALLOC == 1 ) */
/*-----------------------------------------------------------*/

size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
Expand Down
Loading