I figured the following thing out as I was working on the issue #41 malloc_info() changes heap state and removing the call of _Heap_Protection_free_all_delayed_blocks from a function nested in malloc_info() did prevent it from freeing delayed blocks and altering heap state. But on my trial to freeing delayed blocks in allocation paths, i came to understand and figure out the following thing:
One of the ways I came across to free delayed blocks accumulated because of freeing while HEAP_PROTECTION is active is by freeing them in allocation paths especially when malloc fails. Currently RTEMS provides 2 functions to free blocks that got delayed because of active heap protection namely _Heap_Protection_free_delayed_blocks() and _Heap_Protection_free_all_delayed_blocks() being its iterative implementation to free all delayed blocks. In RTEMS, there is an existing implementation of freeing delayed blocks when allocation fails nested inside one of the functions call inside malloc(), but apparently on testing, it showed that even if it is intentionally tried to make allocation by malloc() fail, the delayed blocks aren’t being freed.
The control flow that I figured out inside malloc() was
malloc() calls --------> rtems_heap_allocate_aligned_with_boundary() acquires locks to call --------> _Heap_Allocate_aligned_with_boundary() which frees delayed blocks if allocation is failing
What I experimented was calling the function _Heap_Protection_free_all_delayed_blocks() explicity inside all 3 above functions and to my surprise none of them freed the delayed blocks, on the other hand calling _Heap_Protection_free_all_delayed_blocks() directly inside the program hello.c I made in my app did free the delayed blocks.
So what I suspect now is the call _Heap_Free() inside _Heap_Protection_free_delayed_blocks() that is possibly responsible for no freeing of delayed blocks when called inside malloc() or any of its 2 above nested functions or when allocation is getting failed inside _Heap_Allocate_aligned_with_boundary().
Upon reading _Heap_Free(), no obvious thing was seen that could be preventing freeing when called inside malloc(). Though I am still reading and understanding this function and other functions and checks called inside it.
Could anyone suggest what could be a possible reason for delayed blocks not getting freed from anywhere inside malloc or its nested functions(not even the already existing implementation of RTEMS to free delayed blocks inside malloc() when allocation fails)?
bool _Heap_Free( Heap_Control *heap, void *alloc_begin_ptr )
{
Heap_Statistics *const stats = &heap->stats;
uintptr_t alloc_begin;
Heap_Block *block;
Heap_Block *next_block = NULL;
uintptr_t block_size = 0;
uintptr_t next_block_size = 0;
bool next_is_free = false;
/*
* If NULL return true so a free on NULL is considered a valid release. This
* is a special case that could be handled by the in heap check how-ever that
* would result in false being returned which is wrong.
*/
if ( alloc_begin_ptr == NULL ) {
return true;
}
alloc_begin = (uintptr_t) alloc_begin_ptr;
block = _Heap_Block_of_alloc_area( alloc_begin, heap->page_size );
if ( !_Heap_Is_block_in_heap( heap, block ) ) {
return false;
}
_Heap_Protection_block_check( heap, block );
block_size = _Heap_Block_size( block );
next_block = _Heap_Block_at( block, block_size );
if ( !_Heap_Is_block_in_heap( heap, next_block ) ) {
return false;
}
_Heap_Protection_block_check( heap, next_block );
if ( !_Heap_Is_prev_used( next_block ) ) {
_Heap_Protection_block_error( heap, block, HEAP_ERROR_BAD_USED_BLOCK );
return false;
}
if ( !_Heap_Protection_determine_block_free( heap, block ) ) {
return true;
}
next_block_size = _Heap_Block_size( next_block );
next_is_free = next_block != heap->last_block &&
!_Heap_Is_prev_used(
_Heap_Block_at( next_block, next_block_size )
);
if ( !_Heap_Is_prev_used( block ) ) {
uintptr_t const prev_size = block->prev_size;
Heap_Block *const prev_block = _Heap_Block_at( block, -prev_size );
if ( !_Heap_Is_block_in_heap( heap, prev_block ) ) {
_HAssert( false );
return ( false );
}
/* As we always coalesce free blocks, the block that preceedes prev_block
must have been used. */
if ( !_Heap_Is_prev_used( prev_block ) ) {
_HAssert( false );
return ( false );
}
if ( next_is_free ) { /* coalesce both */
uintptr_t const size = block_size + prev_size + next_block_size;
_Heap_Free_list_remove( next_block );
stats->free_blocks -= 1;
prev_block->size_and_flag = size | HEAP_PREV_BLOCK_USED;
next_block = _Heap_Block_at( prev_block, size );
_HAssert( !_Heap_Is_prev_used( next_block ) );
next_block->prev_size = size;
} else { /* coalesce prev */
uintptr_t const size = block_size + prev_size;
prev_block->size_and_flag = size | HEAP_PREV_BLOCK_USED;
next_block->size_and_flag &= ~HEAP_PREV_BLOCK_USED;
next_block->prev_size = size;
}
} else if ( next_is_free ) { /* coalesce next */
uintptr_t const size = block_size + next_block_size;
_Heap_Free_list_replace( next_block, block );
block->size_and_flag = size | HEAP_PREV_BLOCK_USED;
next_block = _Heap_Block_at( block, size );
next_block->prev_size = size;
} else { /* no coalesce */
/* Add 'block' to the head of the free blocks list as it tends to
produce less fragmentation than adding to the tail. */
_Heap_Free_list_insert_after( _Heap_Free_list_head( heap ), block );
block->size_and_flag = block_size | HEAP_PREV_BLOCK_USED;
next_block->size_and_flag &= ~HEAP_PREV_BLOCK_USED;
next_block->prev_size = block_size;
/* Statistics */
++stats->free_blocks;
if ( stats->max_free_blocks < stats->free_blocks ) {
stats->max_free_blocks = stats->free_blocks;
}
}
/* Statistics */
--stats->used_blocks;
++stats->frees;
stats->free_size += block_size;
stats->lifetime_freed += block_size;
return ( true );
}