/* * malloc.c * * Very simple linked-list based malloc()/free(). */ #include <syslinux/firmware.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <dprintf.h> #include <minmax.h> #include "malloc.h" #include "thread.h" DECLARE_INIT_SEMAPHORE(__malloc_semaphore, 1); static void *__malloc_from_block(struct free_arena_header *fp, size_t size, malloc_tag_t tag) { size_t fsize; struct free_arena_header *nfp, *na; unsigned int heap = ARENA_HEAP_GET(fp->a.attrs); fsize = ARENA_SIZE_GET(fp->a.attrs); /* We need the 2* to account for the larger requirements of a free block */ if ( fsize >= size+2*sizeof(struct arena_header) ) { /* Bigger block than required -- split block */ nfp = (struct free_arena_header *)((char *)fp + size); na = fp->a.next; ARENA_TYPE_SET(nfp->a.attrs, ARENA_TYPE_FREE); ARENA_HEAP_SET(nfp->a.attrs, heap); ARENA_SIZE_SET(nfp->a.attrs, fsize-size); nfp->a.tag = MALLOC_FREE; #ifdef DEBUG_MALLOC nfp->a.magic = ARENA_MAGIC; #endif ARENA_TYPE_SET(fp->a.attrs, ARENA_TYPE_USED); ARENA_SIZE_SET(fp->a.attrs, size); fp->a.tag = tag; /* Insert into all-block chain */ nfp->a.prev = fp; nfp->a.next = na; na->a.prev = nfp; fp->a.next = nfp; /* Replace current block on free chain */ nfp->next_free = fp->next_free; nfp->prev_free = fp->prev_free; fp->next_free->prev_free = nfp; fp->prev_free->next_free = nfp; } else { /* Allocate the whole block */ ARENA_TYPE_SET(fp->a.attrs, ARENA_TYPE_USED); fp->a.tag = tag; /* Remove from free chain */ fp->next_free->prev_free = fp->prev_free; fp->prev_free->next_free = fp->next_free; } return (void *)(&fp->a + 1); } void *bios_malloc(size_t size, enum heap heap, malloc_tag_t tag) { struct free_arena_header *fp; struct free_arena_header *head = &__core_malloc_head[heap]; void *p = NULL; if (size) { /* Add the obligatory arena header, and round up */ size = (size + 2 * sizeof(struct arena_header) - 1) & ARENA_SIZE_MASK; for ( fp = head->next_free ; fp != head ; fp = fp->next_free ) { if ( ARENA_SIZE_GET(fp->a.attrs) >= size ) { /* Found fit -- allocate out of this block */ p = __malloc_from_block(fp, size, tag); break; } } } return p; } static void *_malloc(size_t size, enum heap heap, malloc_tag_t tag) { void *p; dprintf("_malloc(%zu, %u, %u) @ %p = ", size, heap, tag, __builtin_return_address(0)); sem_down(&__malloc_semaphore, 0); p = firmware->mem->malloc(size, heap, tag); sem_up(&__malloc_semaphore); dprintf("%p\n", p); return p; } __export void *malloc(size_t size) { return _malloc(size, HEAP_MAIN, MALLOC_CORE); } __export void *lmalloc(size_t size) { void *p; p = _malloc(size, HEAP_LOWMEM, MALLOC_CORE); if (!p) errno = ENOMEM; return p; } void *pmapi_lmalloc(size_t size) { return _malloc(size, HEAP_LOWMEM, MALLOC_MODULE); } void *bios_realloc(void *ptr, size_t size) { struct free_arena_header *ah, *nah; struct free_arena_header *head; void *newptr; size_t newsize, oldsize, xsize; if (!ptr) return malloc(size); if (size == 0) { free(ptr); return NULL; } ah = (struct free_arena_header *) ((struct arena_header *)ptr - 1); head = &__core_malloc_head[ARENA_HEAP_GET(ah->a.attrs)]; #ifdef DEBUG_MALLOC if (ah->a.magic != ARENA_MAGIC) dprintf("failed realloc() magic check: %p\n", ptr); #endif /* Actual size of the old block */ //oldsize = ah->a.size; oldsize = ARENA_SIZE_GET(ah->a.attrs); /* Add the obligatory arena header, and round up */ newsize = (size + 2 * sizeof(struct arena_header) - 1) & ARENA_SIZE_MASK; if (oldsize >= newsize && newsize >= (oldsize >> 2) && oldsize - newsize < 4096) { /* This allocation is close enough already. */ return ptr; } else { xsize = oldsize; nah = ah->a.next; if ((char *)nah == (char *)ah + ARENA_SIZE_GET(ah->a.attrs) && ARENA_TYPE_GET(nah->a.attrs) == ARENA_TYPE_FREE && ARENA_SIZE_GET(nah->a.attrs) + oldsize >= newsize) { //nah->a.type == ARENA_TYPE_FREE && //oldsize + nah->a.size >= newsize) { /* Merge in subsequent free block */ ah->a.next = nah->a.next; ah->a.next->a.prev = ah; nah->next_free->prev_free = nah->prev_free; nah->prev_free->next_free = nah->next_free; ARENA_SIZE_SET(ah->a.attrs, ARENA_SIZE_GET(ah->a.attrs) + ARENA_SIZE_GET(nah->a.attrs)); xsize = ARENA_SIZE_GET(ah->a.attrs); } if (xsize >= newsize) { /* We can reallocate in place */ if (xsize >= newsize + 2 * sizeof(struct arena_header)) { /* Residual free block at end */ nah = (struct free_arena_header *)((char *)ah + newsize); ARENA_TYPE_SET(nah->a.attrs, ARENA_TYPE_FREE); ARENA_SIZE_SET(nah->a.attrs, xsize - newsize); ARENA_SIZE_SET(ah->a.attrs, newsize); ARENA_HEAP_SET(nah->a.attrs, ARENA_HEAP_GET(ah->a.attrs)); #ifdef DEBUG_MALLOC nah->a.magic = ARENA_MAGIC; #endif //nah->a.type = ARENA_TYPE_FREE; //nah->a.size = xsize - newsize; //ah->a.size = newsize; /* Insert into block list */ nah->a.next = ah->a.next; ah->a.next = nah; nah->a.next->a.prev = nah; nah->a.prev = ah; /* Insert into free list */ if (newsize > oldsize) { /* Hack: this free block is in the path of a memory object which has already been grown at least once. As such, put it at the *end* of the freelist instead of the beginning; trying to save it for future realloc()s of the same block. */ nah->prev_free = head->prev_free; nah->next_free = head; head->prev_free = nah; nah->prev_free->next_free = nah; } else { nah->next_free = head->next_free; nah->prev_free = head; head->next_free = nah; nah->next_free->prev_free = nah; } } /* otherwise, use up the whole block */ return ptr; } else { /* Last resort: need to allocate a new block and copy */ oldsize -= sizeof(struct arena_header); newptr = malloc(size); if (newptr) { memcpy(newptr, ptr, min(size, oldsize)); free(ptr); } return newptr; } } } __export void *realloc(void *ptr, size_t size) { return firmware->mem->realloc(ptr, size); } __export void *zalloc(size_t size) { void *ptr; ptr = malloc(size); if (ptr) memset(ptr, 0, size); return ptr; }