diff options
| author | Daniel Stenberg <daniel@haxx.se> | 2002-05-14 08:19:57 +0000 |
|---|---|---|
| committer | Daniel Stenberg <daniel@haxx.se> | 2002-05-14 08:19:57 +0000 |
| commit | f143fd8e36078141efdf0ef9e590d792930637fb (patch) | |
| tree | 3d7a79599dca5ecb8d1163b33da4379f86f4093a /firmware/malloc/dmalloc.c | |
| parent | bbdeba6d8cb2f2066e22d39c2d5937d3608fd8ed (diff) | |
| download | rockbox-f143fd8e36078141efdf0ef9e590d792930637fb.zip rockbox-f143fd8e36078141efdf0ef9e590d792930637fb.tar.gz rockbox-f143fd8e36078141efdf0ef9e590d792930637fb.tar.bz2 rockbox-f143fd8e36078141efdf0ef9e590d792930637fb.tar.xz | |
Moved the malloc system into the firmware/malloc/ directory, removed the
implementation files from the test/malloc/ directory, leaving only test
files there.
Added headers, corrected a few minor documenational errors.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@571 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/malloc/dmalloc.c')
| -rw-r--r-- | firmware/malloc/dmalloc.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/firmware/malloc/dmalloc.c b/firmware/malloc/dmalloc.c new file mode 100644 index 0000000..cfb2508 --- /dev/null +++ b/firmware/malloc/dmalloc.c @@ -0,0 +1,623 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +/***************************************************************************** + * + * Dynamic small-blocks Memory Allocation + * + * Author: Daniel Stenberg <daniel@haxx.se> + * + * Read THOUGHTS for theories and details on the implementation. + * + *****************************************************************************/ + +#include <stdio.h> +#include <string.h> /* memcpy */ + +#ifdef DEBUG +#include <stdarg.h> +#endif + +#ifdef PSOS +#include <psos.h> +#define SEMAPHORE /* the PSOS routines use semaphore protection */ +#else +#include <stdlib.h> /* makes the PSOS complain on the 'size_t' typedef */ +#endif + +#define BMALLOC /* we use our own big-malloc system */ + +#ifdef BMALLOC +#include "bmalloc.h" +#endif + +/* Each TOP takes care of a chain of BLOCKS */ +struct MemTop { + struct MemBlock *chain; /* pointer to the BLOCK chain */ + long nfree; /* total number of free FRAGMENTS in the chain */ + short nmax; /* total number of FRAGMENTS in this kind of BLOCK */ + size_t fragsize; /* the size of each FRAGMENT */ + +#ifdef SEMAPHORE /* if we're protecting the list with SEMAPHORES */ + long semaphore_id; /* semaphore used to lock this particular list */ +#endif + +}; + +/* Each BLOCK takes care of an amount of FRAGMENTS */ +struct MemBlock { + struct MemTop *top; /* our TOP struct */ + struct MemBlock *next; /* next BLOCK */ + struct MemBlock *prev; /* prev BLOCK */ + + struct MemFrag *first; /* the first free FRAGMENT in this block */ + + short nfree; /* number of free FRAGMENTS in this BLOCK */ +}; + +/* This is the data kept in all _free_ FRAGMENTS */ +struct MemFrag { + struct MemFrag *next; /* next free FRAGMENT */ + struct MemFrag *prev; /* prev free FRAGMENT */ +}; + +/* This is the data kept in all _allocated_ FRAGMENTS and BLOCKS. We add this + to the allocation size first thing in the ALLOC function to make room for + this smoothly. */ + +struct MemInfo { + void *block; + /* which BLOCK is our father, if BLOCK_BIT is set it means this is a + stand-alone, large allocation and then the rest of the bits should be + treated as the size of the block */ +#define BLOCK_BIT 1 +}; + +/* ---------------------------------------------------------------------- */ +/* Defines */ +/* ---------------------------------------------------------------------- */ + +#ifdef DEBUG_VERBOSE +#define MEMINCR(addr,x) memchange(addr, x) +#define MEMDECR(addr,x) memchange(addr,-(x)) +#else +#define MEMINCR(a,x) +#define MEMDECR(a,x) +#endif + +/* The low level functions used to get memory from the OS and to return memory + to the OS, we may also define a stub that does the actual allocation and + free, these are the defined function names used in the dmalloc system + anyway: */ +#ifdef PSOS + +#ifdef DEBUG +#define DMEM_OSALLOCMEM(size,pointer,type) pointer=(type)dbgmalloc(size) +#define DMEM_OSFREEMEM(x) dbgfree(x) +#else +#define DMEM_OSALLOCMEM(size,pointer,type) rn_getseg(0,size,RN_NOWAIT,0,(void **)&pointer) +/* Similar, but this returns the memory */ +#define DMEM_OSFREEMEM(x) rn_retseg(0, x) +#endif + +/* Argument: <id> */ +#define SEMAPHOREOBTAIN(x) sm_p(x, SM_WAIT, 0) +/* Argument: <id> */ +#define SEMAPHORERETURN(x) sm_v(x) +/* Argument: <name> <id-variable name> */ +#define SEMAPHORECREATE(x,y) sm_create(x, 1, SM_FIFO, (ULONG *)&(y)) + +#else +#ifdef BMALLOC /* use our own big-memory-allocation system */ +#define DMEM_OSALLOCMEM(size,pointer,type) pointer=(type)bmalloc(size) +#define DMEM_OSFREEMEM(x) bfree(x) +#elif DEBUG +#define DMEM_OSALLOCMEM(size,pointer,type) pointer=(type)dbgmalloc(size) +#define DMEM_OSFREEMEM(x) dbgfree(x) +#else +#define DMEM_OSALLOCMEM(size,pointer,type) pointer=(type)malloc(size) +#define DMEM_OSFREEMEM(x) free(x) +#endif +#endif + + +/* the largest memory allocation that is made a FRAGMENT: (grab the highest + number from the list below) */ +#define DMEM_LARGESTSIZE 2032 + +/* The total size of a BLOCK used for FRAGMENTS + In order to make this use only *1* even alignment from the big-block- + allocation-system (possible the bmalloc() system also written by me) + we need to subtract the [maximum] struct sizes that could get added all + the way through to the grab from the memory. */ +#define DMEM_BLOCKSIZE 4064 /* (4096 - sizeof(struct MemBlock) - 12) */ + +/* Since the blocksize isn't an even 2^X story anymore, we make a table with + the FRAGMENT sizes and amounts that fills up a BLOCK nicely */ + +/* a little 'bc' script that helps us maximize the usage: + - for 32-bit aligned addresses (SPARC crashes otherwise): + for(i=20; i<2040; i+=4) { a=4064/i; if(a*i >= 4060) { {i;} } } + + I try to approximate a double of each size, starting with ~20. We don't do + ODD sizes since several CPU flavours dump core when accessing such + addresses. We try to do 32-bit aligned to make ALL kinds of CPUs to remain + happy with us. + */ + +const static short qinfo[]= { + 20, 28, 52, 116, 312, 580, 1016, 2032 +}; + +#define MIN(x,y) ((x)<(y)?(x):(y)) + +/* ---------------------------------------------------------------------- */ +/* Globals */ +/* ---------------------------------------------------------------------- */ + +/* keeper of the chain of BLOCKS */ +static struct MemTop top[ sizeof(qinfo)/sizeof(qinfo[0]) ]; + +/* ---------------------------------------------------------------------- */ +/* Start of the real code */ +/* ---------------------------------------------------------------------- */ + +#ifdef DEBUG +/************ + * A few functions that are verbose and tells us about the current status + * of the dmalloc system + ***********/ + +void dmalloc_status(void) +{ + int i; + int used; + int num; + int totalfree=0; + struct MemBlock *block; + for(i=0; i<sizeof(qinfo)/sizeof(qinfo[0]);i++) { + block = top[i].chain; + used = 0; + num = 0; + while(block) { + used += top[i].nmax-block->nfree; + num++; + block = block->next; + } + printf("Q %d (FRAG %4d), USED %4d FREE %4ld (SIZE %4ld) BLOCKS %d\n", + i, top[i].fragsize, used, top[i].nfree, + top[i].nfree*top[i].fragsize, num); + totalfree += top[i].nfree*top[i].fragsize; + } + printf("Total unused memory stolen by dmalloc: %d\n", totalfree); +} +#endif + +#ifdef DEBUG_VERBOSE +static void dmalloc_failed(size_t size) +{ + printf("*** " __FILE__ " Couldn't allocate %d bytes\n", size); + dmalloc_status(); +} +#else +#define dmalloc_failed(x) +#endif + +#ifdef DEBUG_VERBOSE + +#define DBG(x) syslog x + +void syslog(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} + +void memchange(void *a, int x) +{ + static int memory=0; + static int count=0; + static int max=0; + if(memory > max) + max = memory; + memory += x; + DBG(("%d. PTR %p / %d TOTAL %d MAX %d\n", ++count, a, x, memory, max)); +} +#else +#define DBG(x) +#endif + +/**************************************************************************** + * + * FragBlock() + * + * This function makes FRAGMENTS of the BLOCK sent as argument. + * + ***************************************************************************/ + +static void FragBlock(char *memp, int size) +{ + struct MemFrag *frag=(struct MemFrag *)memp; + struct MemFrag *prev=NULL; /* no previous in the first round */ + int count=0; + while((count+size) <= DMEM_BLOCKSIZE) { + frag->next = (struct MemFrag *)((char *)frag + size); + frag->prev = prev; + prev = frag; + (char *)frag += size; + count += size; + } + prev->next = NULL; /* the last one has no next struct */ +} + +/*************************************************************************** + * + * dmalloc_initialize(); + * + * Call before the first dmalloc(). Inits a few memory things. + * + **************************************************************************/ +void dmalloc_initialize(void) +{ + int i; + /* Setup the nmax and fragsize fields of the top structs */ + for(i=0; i< sizeof(qinfo)/sizeof(qinfo[0]); i++) { + top[i].fragsize = qinfo[i]; + top[i].nmax = DMEM_BLOCKSIZE/qinfo[i]; + +#ifdef PSOS + /* for some reason, these aren't nulled from start: */ + top[i].chain = NULL; /* no BLOCKS */ + top[i].nfree = 0; /* no FRAGMENTS */ +#endif +#ifdef SEMAPHORE + { + char name[7]; + sprintf(name, "MEM%d", i); + SEMAPHORECREATE(name, top[i].semaphore_id); + /* doesn't matter if it failed, we continue anyway ;-( */ + } +#endif + } +} + +/**************************************************************************** + * + * fragfromblock() + * + * This should return a fragment from the block and mark it as used + * accordingly. + * + ***************************************************************************/ + +static void *fragfromblock(struct MemBlock *block) +{ + /* make frag point to the first free FRAGMENT */ + struct MemFrag *frag = block->first; + struct MemInfo *mem = (struct MemInfo *)frag; + + /* + * Remove the FRAGMENT from the list and decrease the free counters. + */ + block->first = frag->next; /* new first free FRAGMENT */ + + block->nfree--; /* BLOCK counter */ + block->top->nfree--; /* TOP counter */ + + /* heal the FRAGMENT list */ + if(frag->prev) { + frag->prev->next = frag->next; + } + if(frag->next) { + frag->next->prev = frag->prev; + } + mem->block = block; /* no block bit set here */ + + return ((char *)mem)+sizeof(struct MemInfo); +} + +/*************************************************************************** + * + * dmalloc() + * + * This needs no explanation. A malloc() look-alike. + * + **************************************************************************/ + +void *dmalloc(size_t size) +{ + void *mem; + + DBG(("dmalloc(%d)\n", size)); + + /* First, we make room for the space needed in every allocation */ + size += sizeof(struct MemInfo); + + if(size < DMEM_LARGESTSIZE) { + /* get a FRAGMENT */ + + struct MemBlock *block=NULL; /* SAFE */ + struct MemBlock *newblock=NULL; /* SAFE */ + struct MemTop *memtop=NULL; /* SAFE */ + + /* Determine which queue to use */ + int queue; + for(queue=0; size > qinfo[queue]; queue++) + ; + do { + /* This is the head master of our chain: */ + memtop = &top[queue]; + + DBG(("Top info: CHAIN %p FREE %d MAX %d FRAGSIZE %d\n", + memtop->chain, + memtop->nfree, + memtop->nmax, + memtop->fragsize)); + +#ifdef SEMAPHORE + if(SEMAPHOREOBTAIN(memtop->semaphore_id)) + return NULL; /* failed somehow */ +#endif + + /* get the first BLOCK in the chain */ + block = memtop->chain; + + /* check if we have a free FRAGMENT */ + if(memtop->nfree) { + /* there exists a free FRAGMENT in this chain */ + + /* I WANT THIS LOOP OUT OF HERE! */ + + /* search for the free FRAGMENT */ + while(!block->nfree) + block = block->next; /* check next BLOCK */ + + /* + * Now 'block' is the first BLOCK with a free FRAGMENT + */ + + mem = fragfromblock(block); + + } + else { + /* we do *not* have a free FRAGMENT but need to get us a new BLOCK */ + + DMEM_OSALLOCMEM(DMEM_BLOCKSIZE + sizeof(struct MemBlock), + newblock, + struct MemBlock *); + if(!newblock) { + if(++queue < sizeof(qinfo)/sizeof(qinfo[0])) { + /* There are queues for bigger FRAGMENTS that we should check + before we fail this for real */ +#ifdef DEBUG_VERBOSE + printf("*** " __FILE__ " Trying a bigger Q: %d\n", queue); +#endif + mem = NULL; + } + else { + dmalloc_failed(size- sizeof(struct MemInfo)); + return NULL; /* not enough memory */ + } + } + else { + /* allocation of big BLOCK was successful */ + + MEMINCR(newblock, DMEM_BLOCKSIZE + sizeof(struct MemBlock)); + + memtop->chain = newblock; /* attach this BLOCK to the chain */ + newblock->next = block; /* point to the previous first BLOCK */ + if(block) + block->prev = newblock; /* point back on this new BLOCK */ + newblock->prev = NULL; /* no previous */ + newblock->top = memtop; /* our head master */ + + /* point to the new first FRAGMENT */ + newblock->first = (struct MemFrag *) + ((char *)newblock+sizeof(struct MemBlock)); + + /* create FRAGMENTS of the BLOCK: */ + FragBlock((char *)newblock->first, memtop->fragsize); + + /* fix the nfree counters */ + newblock->nfree = memtop->nmax; + memtop->nfree += memtop->nmax; + + /* get a FRAGMENT from the BLOCK */ + mem = fragfromblock(newblock); + } + } +#ifdef SEMAPHORE + SEMAPHORERETURN(memtop->semaphore_id); /* let it go */ +#endif + } while(NULL == mem); /* if we should retry a larger FRAGMENT */ + } + else { + /* get a stand-alone BLOCK */ + struct MemInfo *meminfo; + + if(size&1) + /* don't leave this with an odd size since we'll use that bit for + information */ + size++; + + DMEM_OSALLOCMEM(size, meminfo, struct MemInfo *); + + if(meminfo) { + MEMINCR(meminfo, size); + meminfo->block = (void *)(size|BLOCK_BIT); + mem = (char *)meminfo + sizeof(struct MemInfo); + } + else { + dmalloc_failed(size); + mem = NULL; + } + } + return (void *)mem; +} + +/*************************************************************************** + * + * dfree() + * + * This needs no explanation. A free() look-alike. + * + **************************************************************************/ + +void dfree(void *memp) +{ + struct MemInfo *meminfo = (struct MemInfo *) + ((char *)memp- sizeof(struct MemInfo)); + + DBG(("dfree(%p)\n", memp)); + + if(!((size_t)meminfo->block&BLOCK_BIT)) { + /* this is a FRAGMENT we have to deal with */ + + struct MemBlock *block=meminfo->block; + struct MemTop *memtop = block->top; + +#ifdef SEMAPHORE + SEMAPHOREOBTAIN(memtop->semaphore_id); +#endif + + /* increase counters */ + block->nfree++; + memtop->nfree++; + + /* is this BLOCK completely empty now? */ + if(block->nfree == memtop->nmax) { + /* yes, return the BLOCK to the system */ + if(block->prev) + block->prev->next = block->next; + else + memtop->chain = block->next; + if(block->next) + block->next->prev = block->prev; + + memtop->nfree -= memtop->nmax; /* total counter subtraction */ + MEMDECR(block, DMEM_BLOCKSIZE + sizeof(struct MemBlock)); + DMEM_OSFREEMEM((void *)block); /* return the whole block */ + } + else { + /* there are still used FRAGMENTS in the BLOCK, link this one + into the chain of free ones */ + struct MemFrag *frag = (struct MemFrag *)meminfo; + frag->prev = NULL; + frag->next = block->first; + if(block->first) + block->first->prev = frag; + block->first = frag; + } +#ifdef SEMAPHORE + SEMAPHORERETURN(memtop->semaphore_id); +#endif + } + else { + /* big stand-alone block, just give it back to the OS: */ + MEMDECR(meminfo->block, (size_t)meminfo->block&~BLOCK_BIT); /* clean BLOCK_BIT */ + DMEM_OSFREEMEM((void *)meminfo); + } +} + +/*************************************************************************** + * + * drealloc() + * + * This needs no explanation. A realloc() look-alike. + * + **************************************************************************/ + +void *drealloc(char *ptr, size_t size) +{ + struct MemInfo *meminfo = (struct MemInfo *) + ((char *)ptr- sizeof(struct MemInfo)); + /* + * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * NOTE: the ->size field of the meminfo will now contain the MemInfo + * struct size too! + * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + */ + void *mem=NULL; /* SAFE */ + size_t prevsize; + + /* NOTE that this is only valid if BLOCK_BIT isn't set: */ + struct MemBlock *block; + + DBG(("drealloc(%p, %d)\n", ptr, size)); + + if(NULL == ptr) + return dmalloc( size ); + + block = meminfo->block; + + if(!((size_t)meminfo->block&BLOCK_BIT) && + (size + sizeof(struct MemInfo) < + (prevsize = block->top->fragsize) )) { + /* This is a FRAGMENT and new size is possible to retain within the same + FRAGMENT */ + if((prevsize > qinfo[0]) && + /* this is not the smallest memory Q */ + (size < (block->top-1)->fragsize)) + /* this fits in a smaller Q */ + ; + else + mem = ptr; /* Just return the same pointer as we got in. */ + } + if(!mem) { + /* This is a stand-alone BLOCK or a realloc that no longer fits within + the same FRAGMENT */ + + if((size_t)meminfo->block&BLOCK_BIT) { + prevsize = ((size_t)meminfo->block&~BLOCK_BIT) - + sizeof(struct MemInfo); + } + else + prevsize -= sizeof(struct MemInfo); + + /* No tricks involved here, just grab a new bite of memory, copy the data + * from the old place and free the old memory again. */ + mem = dmalloc(size); + if(mem) { + memcpy(mem, ptr, MIN(size, prevsize) ); + dfree(ptr); + } + } + return mem; +} + +/*************************************************************************** + * + * dcalloc() + * + * This needs no explanation. A calloc() look-alike. + * + **************************************************************************/ +/* Allocate an array of NMEMB elements each SIZE bytes long. + The entire array is initialized to zeros. */ +void * +dcalloc (size_t nmemb, size_t size) +{ + void *result = dmalloc (nmemb * size); + + if (result != NULL) + memset (result, 0, nmemb * size); + + return result; +} |