Skip to content

Conversation

visitorckw
Copy link
Collaborator

Improve the memory allocator by enhancing safety, correctness, and performance. Key changes include adding panic on heap corruption, consolidating block validation in validate_block(), fixing missing alignment in calloc() and realloc(), introducing a split_block() helper, and adding fast paths for shrinking and growing realloc(). These changes reduce code duplication, ensure consistent validation, and optimize common allocation operations.

The coalesced variable in selective_coalesce() was declared but never
used. Remove it to clean up the code and reduce unnecessary clutter.
The calloc() and realloc() implementations did not align the requested
size before passing it to the allocator. This could result in returning
non-aligned memory blocks, which may trigger unaligned memory access
exceptions on RISC-V.

Align the total size in calloc() and the input size in realloc() to
ensure the allocator always returns properly aligned memory.
Currently, the memory allocator does not trigger a kernel panic on
detected corruption or invalid operations. However, once the heap is
corrupted, the kernel can no longer guarantee safe or correct behavior.

Introduce ERR_HEAP_CORRUPT with a corresponding error message to
prepare for future panic-on-error handling in the memory allocator.
The memory allocator previously attempted to continue execution even if
validate_block() failed, which indicates unexpected heap corruption or
invalid memory operations. Once the heap is corrupted, the kernel can
no longer guarantee safe or correct behavior.

Invoke panic(ERR_HEAP_CORRUPT) when validate_block() fails in malloc(),
free(), or realloc(), ensuring the kernel halts immediately on fatal
allocator errors.
The allocator previously only validated the size and bounds of a memory
block. It did not check whether the block's successor was physically
adjacent in memory.

Extend validate_block() to verify that a block's next pointer matches
the expected location based on its size. This ensures corruption in the
linked list of blocks is detected early and consistently.
Inline adjacency checks were open-coded in free() and
selective_coalesce(). If these checks failed, the allocator would
silently skip the block and continue, masking heap corruption.

Replace the open-coded adjacency logic with calls to validate_block()
to avoid duplication and ensure consistent validation. If a block fails
validation, invoke panic(ERR_HEAP_CORRUPT) instead of silently ignoring
the error, since heap corruption is fatal to kernel safety.
In malloc(), free_blocks_count was decremented only if it was greater
than zero. However, once a usable block has been found,
free_blocks_count should never be zero. If this condition occurs, it
indicates a fatal inconsistency in allocator state.

Replace the conditional decrement with a check that panics with
ERR_HEAP_CORRUPT when free_blocks_count <= 0, ensuring the kernel halts
on heap accounting corruption.
Errors that trigger kernel panic in the memory allocator are expected
to almost never occur during normal operation. Hint these branches with
unlikely() so the compiler can better optimize the common fast path.

This change wraps validation failures and other fatal checks with
unlikely(), without altering runtime behavior.
Splitting a large memory block into two smaller blocks is a common
operation in the allocator.

Introduce a split_block() helper to centralize this functionality and
avoid repeating the same code. The helper also panics if the split size
is invalid, ensuring stronger consistency checks in the allocator.
malloc() previously contained open-coded logic for splitting a large
block into two smaller ones. This is now replaced with a call to the
split_block() helper introduced earlier.

This removes duplicated code and ensures consistency by reusing the
centralized split implementation, which also includes stronger
validation and error handling.
When realloc() is called with a smaller size than the current block,
we can avoid allocating a new block and copying data. Instead, split
the existing block into two smaller blocks using the split_block()
helper.

This fast path improves performance for shrinking realloc() calls and
triggers selective coalescing only when fragmentation exceeds the
threshold.
When realloc() needs a larger size, we can check if the next block is
free and adjacent. If the combined size of the current and next block
is sufficient, merge them into a single block and split it to the
desired size.

This fast path avoids allocating a new block and copying data, and
selective coalescing is triggered only when fragmentation exceeds the
threshold, improving performance for growing realloc() calls.
@visitorckw
Copy link
Collaborator Author

Note that this PR may cause the timer test to encounter a kernel panic. From a quick inspection, the panic is triggered when the timer subsystem calls list_pop() and attempts to free a node from timer_node_pool, resulting in an invalid free. After merging this PR, I plan to create a separate issue to discuss this problem and work on a proper fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant