include $(TOPDIR)Rules.mak
CSRC = malloc.o free.o realloc.o calloc.o heap_alloc.o \
- heap_alloc_at.o heap_free.o heap_append_free.o
+ heap_alloc_at.o heap_free.o
COBJS=$(patsubst %.c,%.o, $(CSRC))
OBJS=$(COBJS)
*/
#include <stdlib.h>
+#include <unistd.h>
#include <sys/mman.h>
#include "malloc.h"
#include "heap.h"
+/* Try to release the free-area FA in HEAP back to the system. */
+static void
+unmap_free_area (struct heap *heap, struct heap_free_area *fa)
+{
+ unsigned long start, end;
+#ifndef MALLOC_USE_SBRK
+ unsigned long unmap_start, unmap_end;
+#endif
+
+ end = (unsigned long)HEAP_FREE_AREA_END (fa);
+
+#ifdef MALLOC_USE_SBRK
+ /* When using sbrk, we only shrink the heap from the end. It would be
+ possible to allow _both_ -- shrinking via sbrk when possible, and
+ otherwise shrinking via munmap, but this results in holes in memory
+ that prevent the brk from every growing back down; since we only ever
+ grow the heap via sbrk, this tends to produce a continuously growing
+ brk (though the actual memory is unmapped), which could eventually run
+ out of address space. Note that `sbrk(0)' shouldn't normally do a
+ system call, so this test is reasonably cheap. */
+ if ((void *)end != sbrk (0))
+ {
+ MALLOC_DEBUG (" not unmapping: 0x%lx - 0x%lx (%d bytes)\n",
+ (unsigned long)HEAP_FREE_AREA_START (fa),
+ (unsigned long)HEAP_FREE_AREA_END (fa),
+ fa->size);
+ return;
+ }
+#endif
+
+ start = (unsigned long)HEAP_FREE_AREA_START (fa);
+
+ MALLOC_DEBUG (" unmapping: 0x%lx - 0x%lx (%ld bytes)\n",
+ start, end, end - start);
+
+ /* Remove FA from the heap. */
+ __heap_unlink_free_area (heap, fa);
+
+ if (!fa->next && !fa->prev)
+ /* We want to avoid the heap from losing all memory, so reserve a bit.
+ This test is only a heuristic -- the existance of another free area,
+ even if it's smaller than MALLOC_MIN_SIZE, will cause us not to
+ reserve anything. */
+ {
+ /* Put the reserved memory back in the heap; we asssume that
+ MALLOC_UNMAP_THRESHOLD is greater than MALLOC_MIN_SIZE, so we use
+ the latter unconditionally here. */
+ __heap_free (heap, (void *)start, MALLOC_MIN_SIZE);
+ start += MALLOC_MIN_SIZE;
+ }
+
+#ifdef MALLOC_USE_SBRK
+
+ sbrk (start - end);
+
+#else /* !MALLOC_USE_SBRK */
+
+ /* MEM/LEN may not be page-aligned, so we have to page-align them, and
+ return any left-over bits on the end to the heap. */
+ unmap_start = MALLOC_ROUND_UP_TO_PAGE_SIZE (start);
+ unmap_end = MALLOC_ROUND_DOWN_TO_PAGE_SIZE (end);
+
+ /* We have to be careful that any left-over bits are large enough to
+ return. Note that we _don't check_ to make sure there's room to
+ grow/shrink the start/end by another page, we just assume that the
+ unmap threshold is high enough so that this is always safe (i.e., it
+ should probably be at least 3 pages). */
+ if (unmap_start > start)
+ {
+ if (unmap_start - start < HEAP_MIN_FREE_AREA_SIZE)
+ unmap_start += MALLOC_PAGE_SIZE;
+ __heap_free (heap, (void *)start, unmap_start - start);
+ }
+ if (end > unmap_end)
+ {
+ if (end - unmap_end < HEAP_MIN_FREE_AREA_SIZE)
+ unmap_end -= MALLOC_PAGE_SIZE;
+ __heap_free (heap, (void *)unmap_end, end - unmap_end);
+ }
+
+ if (unmap_end > unmap_start)
+ munmap ((void *)unmap_start, unmap_end - unmap_start);
+
+#endif /* MALLOC_USE_SBRK */
+}
+
+\f
void free (void *mem)
{
- size_t size;
+ if (mem)
+ {
+ size_t size;
+ struct heap_free_area *fa;
+
+ mem -= MALLOC_ALIGNMENT;
+ size = *(size_t *)mem;
- mem = (size_t *)mem - 1;
- size = *(size_t *)mem;
+ MALLOC_DEBUG ("free: 0x%lx (base = 0x%lx, total_size = %d)\n",
+ (long)mem + MALLOC_ALIGNMENT, (long)mem, size);
- MALLOC_DEBUG ("free: 0x%lx (base = 0x%lx, total_size = %d)\n",
- (long)mem + sizeof (size_t), (long)mem, size);
+ fa = __heap_free (&__malloc_heap, mem, size);
- if (size >= MALLOC_MMAP_THRESHOLD)
- munmap (mem, size);
- else
- __heap_free (&__malloc_heap, mem, size);
+ /* Now we check to see if FA has grown big enough that it should be
+ unmapped. */
+ if (HEAP_FREE_AREA_SIZE (fa) >= MALLOC_UNMAP_THRESHOLD)
+ /* Get rid of it. */
+ unmap_free_area (&__malloc_heap, fa);
+ }
}
#endif
-
-/* The unit in which allocation is done, due to alignment constraints, etc.
- All allocation requests are rounded up to a multiple of this size.
- Must be a power of 2. */
+/* The heap allocates in multiples of, and aligned to, HEAP_GRANULARITY.
+ HEAP_GRANULARITY must be a power of 2. Malloc depends on this being the
+ same as MALLOC_ALIGNMENT. */
#define HEAP_GRANULARITY (sizeof (double))
of memory can be allocated. */
struct heap
{
+ /* A list of memory in the heap available for allocation. */
struct heap_free_area *free_areas;
+
heap_mutex_t lock;
};
-
#define HEAP_INIT { 0, HEAP_MUTEX_INIT }
/* Return the address of the beginning of the frea area FA. FA is
evaulated multiple times. */
#define HEAP_FREE_AREA_START(fa) ((void *)((char *)(fa + 1) - (fa)->size))
+/* Return the size of the frea area FA. */
+#define HEAP_FREE_AREA_SIZE(fa) ((fa)->size)
/* Rounds SZ up to be a multiple of HEAP_GRANULARITY. */
(sizeof (struct heap_free_area) + HEAP_ADJUST_SIZE (1))
+/* Change this to `#if 1' to cause the heap routines to emit debugging info
+ to stderr. */
#if 0
#include <stdio.h>
static void HEAP_DEBUG (struct heap *heap, const char *str)
static int recursed = 0;
if (! recursed)
{
- struct heap_free_area *fa;
+ struct heap_free_area *fa, *prev;
recursed = 1;
fprintf (stderr, " %s: heap @0x%lx:\n", str, (long)heap);
- for (fa = heap->free_areas; fa; fa = fa->next)
- fprintf (stderr,
- " 0x%lx: 0x%lx - 0x%lx (%d)\tN=0x%lx, P=0x%lx\n",
- (long)fa,
- (long)HEAP_FREE_AREA_START (fa),
- (long)HEAP_FREE_AREA_END (fa),
- fa->size,
- (long)fa->prev,
- (long)fa->next);
+ for (prev = 0, fa = heap->free_areas; fa; prev = fa, fa = fa->next)
+ {
+ fprintf (stderr,
+ " 0x%lx: 0x%lx - 0x%lx (%d)\tP=0x%lx, N=0x%lx\n",
+ (long)fa,
+ (long)HEAP_FREE_AREA_START (fa),
+ (long)HEAP_FREE_AREA_END (fa),
+ fa->size,
+ (long)fa->prev,
+ (long)fa->next);
+ if (fa->prev != prev)
+ fprintf (stderr,
+ " PREV POINTER CORRUPTED!!!! P=0x%lx should be 0x%lx\n",
+ (long)fa->prev, (long)prev);
+ }
recursed = 0;
}
}
#endif
+/* Remove the free-area FA from HEAP. */
+extern inline void
+__heap_unlink_free_area (struct heap *heap, struct heap_free_area *fa)
+{
+ if (fa->next)
+ fa->next->prev = fa->prev;
+ if (fa->prev)
+ fa->prev->next = fa->next;
+ else
+ heap->free_areas = fa->next;
+}
+
/* Allocate SIZE bytes from the front of the free-area FA in HEAP, and
return the amount actually allocated (which may be more than SIZE). */
extern inline size_t
/* There's not enough room left over in FA after allocating the block, so
just use the whole thing, removing it from the list of free areas. */
{
- if (fa->next)
- fa->next->prev = fa->prev;
- if (fa->prev)
- fa->prev->next = fa->next;
- else
- heap->free_areas = fa->next;
+ __heap_unlink_free_area (heap, fa);
/* Remember that we've alloced the whole area. */
size = fa_size;
}
allocated, or 0 if we failed. */
extern size_t __heap_alloc_at (struct heap *heap, void *mem, size_t size);
-/* Return the memory area MEM of size SIZE to HEAP. */
-extern void __heap_free (struct heap *heap, void *mem, size_t size);
-
-/* If the memory area MEM, of size SIZE, immediately follows an existing
- free-area in HEAP, use it to extend that free-area, and return true;
- otherwise return false. */
-extern int __heap_append_free (struct heap *heap, void *mem, size_t size);
+/* Return the memory area MEM of size SIZE to HEAP.
+ Returns the heap free area into which the memory was placed. */
+extern struct heap_free_area *__heap_free (struct heap *heap,
+ void *mem, size_t size);
+++ /dev/null
-/*
- * libc/stdlib/malloc/heap_append_free.c -- append memory to a heap free area
- *
- * Copyright (C) 2002 NEC Corporation
- * Copyright (C) 2002 Miles Bader <miles@gnu.org>
- *
- * This file is subject to the terms and conditions of the GNU Lesser
- * General Public License. See the file COPYING.LIB in the main
- * directory of this archive for more details.
- *
- * Written by Miles Bader <miles@gnu.org>
- */
-
-#include <stdlib.h>
-
-#include "heap.h"
-
-
-/* If the block MEM, of size SIZE, immediately follows an existing free-area
- in HEAP, use it to extend that free-area, and return true; otherwise return
- false. */
-int
-__heap_append_free (struct heap *heap, void *mem, size_t size)
-{
- int success = 0;
- struct heap_free_area *fa;
-
- __heap_lock (heap);
-
- HEAP_DEBUG (heap, "before __heap_append_free");
-
- /* Find an adjacent free-list entry. */
- for (fa = heap->free_areas; fa; fa = fa->next)
- if (HEAP_FREE_AREA_END (fa) == mem)
- /* MEM follows FA, extend FA to include it. Since the descriptor for FA
- is located at the end, we must actually write a new descriptor. Note
- that we _don't_ handle the case where the extended FA can be merged
- with a following free area; this is because this function is
- generally only used in cases were we believe that usually won't
- happen (it doesn't cause any incorrectness, and the two blocks can be
- merged by __heap_free later). */
- {
- struct heap_free_area *next_fa = fa->next;
- struct heap_free_area *prev_fa = fa->prev;
- size_t fa_size = fa->size;
- struct heap_free_area *new_fa =
- (struct heap_free_area *)((char *)fa + size);
-
- /* Update surrounding free-areas to point to FA's new address. */
- if (prev_fa)
- prev_fa->next = new_fa;
- else
- heap->free_areas = new_fa;
- if (next_fa)
- next_fa->prev = new_fa;
-
- /* Fill in the moved descriptor. */
- new_fa->prev = prev_fa;
- new_fa->next = next_fa;
- new_fa->size = fa_size + size;
-
- success = 1;
- break;
- }
-
- HEAP_DEBUG (heap, "after __heap_append_free");
-
- __heap_unlock (heap);
-
- return success;
-}
/* Return the memory area MEM of size SIZE to HEAP. */
-void
+struct heap_free_area *
__heap_free (struct heap *heap, void *mem, size_t size)
{
struct heap_free_area *prev_fa, *fa, *new_fa;
if (fa)
fa->prev = new_fa;
+ fa = new_fa;
+
done:
HEAP_DEBUG (heap, "after __heap_free");
__heap_unlock (heap);
+
+ return fa;
}
*/
#include <stdlib.h>
+#include <unistd.h>
#include <sys/mman.h>
#include "malloc.h"
#include "heap.h"
-/* When we give memory to the heap, start this many bytes after the
- beginning of the mmaped block. This is because we must ensure that
- malloc return values are aligned to MALLOC_ALIGNMENT, but since we need
- to use one word _before_ the beginning of that, we actually want the heap
- to return values that are MALLOC_ALIGNMENT aligned - sizeof (size_t).
- Since the heap always allocates in multiples of HEAP_GRANULARITY, we can
- do this by (1) ensuring that HEAP_GRANULARITY is a multiple of
- MALLOC_ALIGNMENT, and (2) making sure that the heap's free areas start
- sizeof(size_t) bytes before our required alignment. */
-#define MALLOC_HEAP_BLOCK_SHIM (MALLOC_ALIGNMENT - sizeof (size_t))
-
-
-/* The heap used for small allocations. */
+/* The malloc heap. */
struct heap __malloc_heap = HEAP_INIT;
\f
MALLOC_DEBUG ("malloc: %d bytes\n", size);
- /* Include an extra word to record the size of the allocated block. */
- size += sizeof (size_t);
-
- if (size >= MALLOC_MMAP_THRESHOLD)
- /* Use mmap for large allocations. */
- {
- /* Make sure we request enough memory to align the result correctly,
- and that SIZE reflects that mmap hands back whole pages. */
- size += MALLOC_ROUND_UP_TO_PAGE_SIZE (MALLOC_ALIGNMENT - sizeof(size_t));
+ /* Include extra space to record the size of the allocated block. */
+ size += MALLOC_ROUND_UP (sizeof (size_t), MALLOC_ALIGNMENT);
- mem = mmap (0, size, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, 0, 0);
- if (mem == MAP_FAILED)
- return 0;
- }
- else
- /* Use the heap for small allocations. */
+ mem = __heap_alloc (&__malloc_heap, &size);
+ if (! mem)
+ /* We couldn't allocate from the heap, so get some more memory
+ from the system, add it to the heap, and try again. */
{
- mem = __heap_alloc (&__malloc_heap, &size);
-
- if (! mem)
- /* We couldn't allocate from the heap, so get some more memory
- from the system, add it to the heap, and try again. */
+ /* If we're trying to allocate a block bigger than the default
+ MALLOC_HEAP_EXTEND_SIZE, make sure we get enough to hold it. */
+ size_t block_size
+ = (size < MALLOC_HEAP_EXTEND_SIZE
+ ? MALLOC_HEAP_EXTEND_SIZE
+ : MALLOC_ROUND_UP_TO_PAGE_SIZE (size));
+ /* Allocate the new heap block. */
+#ifdef MALLOC_USE_SBRK
+ /* Use sbrk we can, as it's faster than mmap, and guarantees
+ contiguous allocation. */
+ void *block = sbrk (block_size);
+#else
+ /* Otherwise, use mmap. */
+ void *block = mmap (0, block_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, 0, 0);
+#endif
+
+ if (block != (void *)-1)
{
- /* If we're trying to allocate a block bigger than the default
- MALLOC_HEAP_EXTEND_SIZE, make sure we get enough to hold it. */
- size_t block_size = (size < MALLOC_HEAP_EXTEND_SIZE
- ? MALLOC_HEAP_EXTEND_SIZE
- : MALLOC_ROUND_UP_TO_PAGE_SIZE (size));
- /* Allocate the new heap block. */
- void *block = mmap (0, block_size,
- PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, 0, 0);
-
- if (block != MAP_FAILED)
+#ifdef MALLOC_USE_SBRK
+ /* Because sbrk can return results of arbitrary
+ alignment, align the result to a MALLOC_ALIGNMENT boundary. */
+ long aligned_block = MALLOC_ROUND_UP ((long)block, MALLOC_ALIGNMENT);
+ if (block != (void *)aligned_block)
+ /* Have to adjust. We should only have to actually do this
+ the first time (after which we will have aligned the brk
+ correctly). */
{
- /* Put BLOCK into the heap. We first try to append BLOCK to
- an existing free area, which is more efficient because it
- doesn't require using a `shim' at the beginning (which
- would prevent merging free-areas); since mmap often returns
- contiguous areas, this is worth it. */
- if (! __heap_append_free (&__malloc_heap, block, block_size))
- /* Couldn't append, just add BLOCK as a new free-area. */
- __heap_free (&__malloc_heap,
- block + MALLOC_HEAP_BLOCK_SHIM,
- block_size - MALLOC_HEAP_BLOCK_SHIM);
-
- /* Try again to allocate. */
- mem = __heap_alloc (&__malloc_heap, &size);
+ /* Move the brk to reflect the alignment; our next allocation
+ should start on exactly the right alignment. */
+ sbrk (aligned_block - (long)block);
+ block = (void *)aligned_block;
}
+#endif /* MALLOC_USE_SBRK */
+
+ MALLOC_DEBUG (" adding memory: 0x%lx - 0x%lx (%d bytes)\n",
+ (long)block, (long)block + block_size, block_size);
+
+ /* Put BLOCK into the heap. */
+ __heap_free (&__malloc_heap, block, block_size);
+
+ /* Try again to allocate. */
+ mem = __heap_alloc (&__malloc_heap, &size);
}
}
/* Record the size of this block just before the returned address. */
{
*(size_t *)mem = size;
- mem = (size_t *)mem + 1;
+ mem += MALLOC_ALIGNMENT;
MALLOC_DEBUG (" malloc: returning 0x%lx (base:0x%lx, total_size:%d)\n",
- (long)mem, (long)mem - sizeof (size_t), size);
+ (long)mem, (long)mem - MALLOC_ALIGNMENT, size);
}
return mem;
satisfy a particularly big request). */
#define MALLOC_HEAP_EXTEND_SIZE MALLOC_PAGE_SIZE
-/* The threshold above which blocks are allocated/freed with mmap/munmap,
- rather than using the heap. */
-#define MALLOC_MMAP_THRESHOLD (8*MALLOC_PAGE_SIZE)
+/* When a heap free-area grows above this size, try to unmap it, releasing
+ the memory back to the system. */
+#define MALLOC_UNMAP_THRESHOLD (8*MALLOC_PAGE_SIZE)
+/* When unmapping a free-area, retain this many bytes if it's the only one,
+ to avoid completely emptying the heap. This is only a heuristic -- the
+ existance of another free area, even if it's smaller than
+ MALLOC_MIN_SIZE, will cause us not to reserve anything. */
+#define MALLOC_MIN_SIZE (2*MALLOC_PAGE_SIZE)
+/* For systems with an MMU, use sbrk to map/unmap memory for the malloc
+ heap, instead of mmap/munmap. This is a tradeoff -- sbrk is faster than
+ mmap/munmap, and guarantees contiguous allocation, but is also less
+ flexible, and causes the heap to only be shrinkable from the end. */
+#ifdef __UCLIBC_HAS_MMU__
+#define MALLOC_USE_SBRK
+#endif
+
+
+/* Change this to `#if 1' to cause malloc to emit debugging info to stderr. */
#if 0
#include <stdio.h>
#define MALLOC_DEBUG(fmt, args...) fprintf (stderr, fmt , ##args)
#endif
+/* Return SZ rounded down to POWER_OF_2_SIZE (which must be power of 2). */
+#define MALLOC_ROUND_DOWN(sz, power_of_2_size) \
+ ((sz) & ~(power_of_2_size - 1))
+/* Return SZ rounded to POWER_OF_2_SIZE (which must be power of 2). */
+#define MALLOC_ROUND_UP(sz, power_of_2_size) \
+ MALLOC_ROUND_DOWN ((sz) + (power_of_2_size - 1), (power_of_2_size))
+
+/* Return SZ rounded down to a multiple MALLOC_PAGE_SIZE. */
+#define MALLOC_ROUND_DOWN_TO_PAGE_SIZE(sz) \
+ MALLOC_ROUND_DOWN (sz, MALLOC_PAGE_SIZE)
/* Return SZ rounded up to a multiple MALLOC_PAGE_SIZE. */
#define MALLOC_ROUND_UP_TO_PAGE_SIZE(sz) \
- (((sz) + (MALLOC_PAGE_SIZE - 1)) & ~(MALLOC_PAGE_SIZE - 1))
+ MALLOC_ROUND_UP (sz, MALLOC_PAGE_SIZE)
-/* The heap used for small allocations. */
+/* The malloc heap. */
extern struct heap __malloc_heap;
return malloc (new_size);
else
{
- void *base_mem = (size_t *)mem - 1;
+ void *base_mem = mem - MALLOC_ALIGNMENT;
size_t size = *(size_t *)base_mem;
MALLOC_DEBUG ("realloc: 0x%lx, %d (base = 0x%lx, total_size = %d)\n",
size_t ext_size = new_size - size;
void *ext_addr = (char *)base_mem + ext_size;
- if (size >= MALLOC_MMAP_THRESHOLD)
- /* Try to extend this block in place using mmap. */
- {
- ext_size += MALLOC_ROUND_UP_TO_PAGE_SIZE (ext_size);
-
- new_mem = mmap (ext_addr, ext_size, PROT_READ | PROT_WRITE,
- MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, 0, 0);
- if (new_mem == MAP_FAILED)
- /* Can't do it. */
- ext_size = 0;
- }
- else
- ext_size = __heap_alloc_at (&__malloc_heap, ext_addr, ext_size);
-
+ ext_size = __heap_alloc_at (&__malloc_heap, ext_addr, ext_size);
if (! ext_size)
/* Our attempts to extend MEM in place failed, just
allocate-and-copy. */