[PATCH] Nano-malloc: Fix for unwanted external heap fragmentation
Corinna Vinschen
vinschen@redhat.com
Mon Apr 26 09:41:09 GMT 2021
The patch looks good to me at first glance, but I'd like to
get some review(s) from other nano-malloc stakeholders.
Thanks,
Corinna
On Apr 26 10:57, Ola Olsson wrote:
> The only reason why it is tough for us to use nano malloc
> is because of the small shortcoming where nano_malloc()
> splits a bigger chunk from the free list into two pieces
> while handing back the second one (the tail) to the user.
> This is error prone and especially bad for smaller heaps,
> where nano malloc is supposed to be superior. The normal
> malloc doesn't have this issue and we need to use it even
> though it costs us ~2k bytes compared to nano-malloc.
>
> The problem arise especially after giving back _every_
> malloced memory to the heap and then starting to exercise
> the heap again by allocating something small. This small
> item might split the whole heap in two equally big parts
> depending on how the heap has been exercised before.
>
> I have uploaded the smallest possible application
> (only tested on ST and Nordic devices) to show the issue
> while the real customer applications are far more complicated:
> https://drive.google.com/file/d/1kfSC2KOm3Os3mI7EBd-U0j63qVs8xMbt/view?usp=sharing
>
> The application works like the following pseudo code,
> where we assume a heap of 100 bytes
> (I haven't taken padding and other nitty and gritty
> details into account. Everything to simplify understanding):
>
> void *ptr = malloc(52); // We get 52 bytes and we have
> // 48 bytes to use.
> free(ptr); // Hand back the 52 bytes to nano_malloc
> // This is the magic line that shows the issue of
> // nano_malloc
> ptr = malloc(1); // Nano malloc will split the 52 bytes
> // in the free list and hand you a pointer
> // somewhere in the
> // middle of the heap.
> ptr2 = malloc(52); // Out of memory...
>
> I have done a fix which hands back the first part of the
> splitted chunk. Once this is fixed we obviously
> have the 1 byte placed in position 0 of the heap instead
> of somewhere in the middle.
>
> However, this won't let us malloc 52 new bytes even though
> we potentially have 99 bytes left to use in the heap. The
> reason is that when we try to do the allocation,
> nano-malloc looks into the free list and sees a 51 byte
> chunk to be used.
> This is not big enough so nano-malloc decides to call
> sbrk for _another_ 52 bytes which is not possible since
> there is only 48 bytes left to ask for.
>
> The solution for this problem is to check if the last
> item in the free list is adjacent to sbrk(0). If it is,
> as it is in this case, we can just ask sbrk for the
> remainder of what is needed. In this case 1 byte.
>
> NB! I have only tested the solution on our ST device.
> ---
> newlib/libc/stdlib/nano-mallocr.c | 70 +++++++++++++++++++++++++++----
> 1 file changed, 61 insertions(+), 9 deletions(-)
>
> diff --git a/newlib/libc/stdlib/nano-mallocr.c b/newlib/libc/stdlib/nano-mallocr.c
> index 1e0703948..1c7ab390b 100644
> --- a/newlib/libc/stdlib/nano-mallocr.c
> +++ b/newlib/libc/stdlib/nano-mallocr.c
> @@ -264,11 +264,22 @@ void * nano_malloc(RARG malloc_size_t s)
> {
> if (rem >= MALLOC_MINCHUNK)
> {
> - /* Find a chunk that much larger than required size, break
> - * it into two chunks and return the second one */
> - r->size = rem;
> - r = (chunk *)((char *)r + rem);
> - r->size = alloc_size;
> + if (p == r)
> + {
> + /* First item in the list, break it into two chunks
> + * and return the first one */
> + r->size = alloc_size;
> + free_list = (chunk *)((char *)r + alloc_size);
> + free_list->size = rem;
> + free_list->next = r->next;
> + } else {
> + /* Any other item in the list. Split and return
> + * the first one */
> + r->size = alloc_size;
> + p->next = (chunk *)((char *)r + alloc_size);
> + p->next->size = rem;
> + p->next->next = r->next;
> + }
> }
> /* Find a chunk that is exactly the size or slightly bigger
> * than requested size, just return this chunk */
> @@ -297,11 +308,52 @@ void * nano_malloc(RARG malloc_size_t s)
> /* sbrk returns -1 if fail to allocate */
> if (r == (void *)-1)
> {
> - RERRNO = ENOMEM;
> - MALLOC_UNLOCK;
> - return NULL;
> + /* sbrk didn't have the requested amount. Let's check
> + * if the last item in the free list is adjacent to the
> + * current heap end (sbrk(0)). In that case, only ask
> + * for the difference in size and merge them */
> + p = free_list;
> + r = p;
> +
> + while (r)
> + {
> + p=r;
> + r=r->next;
> + }
> +
> + if ((char *)p + p->size == (char *)_SBRK_R(RCALL 0))
> + {
> + /* The last free item has the heap end as neighbour.
> + * Let's ask for a smaller amount and merge */
> + alloc_size -= p->size;
> + alloc_size = ALIGN_SIZE(alloc_size, CHUNK_ALIGN); /* size of aligned data load */
> + alloc_size += MALLOC_PADDING; /* padding */
> + alloc_size += CHUNK_OFFSET; /* size of chunk head */
> + alloc_size = MAX(alloc_size, MALLOC_MINCHUNK);
> +
> + if (sbrk_aligned(RCALL alloc_size) != (void *)-1)
> + {
> + p->size += alloc_size;
> + r = p;
> + }
> + else
> + {
> + RERRNO = ENOMEM;
> + MALLOC_UNLOCK;
> + return NULL;
> + }
> + }
> + else
> + {
> + RERRNO = ENOMEM;
> + MALLOC_UNLOCK;
> + return NULL;
> + }
> + }
> + else
> + {
> + r->size = alloc_size;
> }
> - r->size = alloc_size;
> }
> MALLOC_UNLOCK;
>
> --
> 2.25.1
More information about the Newlib
mailing list