/* $NetBSD: malloc.c,v 1.2 1997/04/13 10:50:30 mrg Exp $ */ /* * malloc/free/realloc memory management routines. * * This is a very simple, but fast storage allocator. It allocates blocks * of a small number of different sizes, and keeps free lists of each size. * Blocks that don't exactly fit are passed up to the next larger size. * In this implementation, the available sizes are (2^(i+4))-8 bytes long. * This is designed for use in a single-threaded virtual memory environment. * * This version is derived from: malloc.c (Caltech) 2/21/82, Chris Kingsley, * kingsley@cit-20, and a similar version of Larry Wall, used by perl. * * Rewritten by Eric Wassenaar, Nikhef-H, * * Not only varies the vendor-supplied implementation of this package greatly * for different platforms, there are also many subtle semantic differences, * especially for anomalous conditions. This makes it risky to use a separate * package, in case other library routines depend on those special features. * Nevertheless, we assume that external routines under normal circumstances * just need the basic malloc/free/realloc functionality. * * To avoid possible conflicts, included in this package are some auxiliary * functions: memalign, calloc/cfree, valloc/vfree, because they are closely * related to and built upon the basic functions. * * Not supported are special functions: mallopt/mallinfo/mstats, and those * which are found on specific platforms only: mallocblksize/recalloc (sgi), * malloc_debug/malloc_verify (sun), malloc_size/malloc_error (next). * * The simplicity of this package imposes the following limitations: * - Memory once allocated is never returned to the system, but is put on * free lists for subsequent use. The process size grows monotonously. * - Allocating zero-size data blocks is not an error. * - In case realloc fails, the old block may no longer be allocated. * - The strictest alignment for memalign is the page size. */ #ifndef lint static char Version[] = "@(#)malloc.c e07@nikhef.nl (Eric Wassenaar) 961019"; #endif #if defined(apollo) && defined(lint) #define __attribute(x) #endif #include #include #include #include #ifdef lint #define EXTERN #else #define EXTERN extern #endif EXTERN int errno; /* * Portability definitions. These are probably too primitive, but the * semantics on various platforms are too chaotic to do it correctly. */ #if defined(SYSV) || defined(SVR4) #define SYSV_MALLOC #define SYSV_MEMSET #endif #ifdef SYSV_MALLOC typedef void ptr_t; typedef u_int siz_t; typedef void free_t; #define free_return(x) return #else typedef char ptr_t; typedef u_int siz_t; typedef int free_t; #define free_return(x) return(x) #endif #ifdef SYSV_MEMSET #define bzero(a,n) (void) memset(a,'\0',n) #define bcopy(a,b,n) (void) memcpy(b,a,n) #endif /* * The page size used to request blocks of system memory. */ #ifndef PAGESIZE #ifdef NBPG #ifdef CLSIZE #define PAGESIZE (NBPG*CLSIZE) #else #define PAGESIZE NBPG #endif #else /*not NBPG*/ #ifdef NBPC #define PAGESIZE NBPC #else #define PAGESIZE 4096 /* some reasonable default */ #endif #endif /*NBPG*/ #endif static int pagesize = 0; /* page size after initialization */ /* * The overhead on a block is 8 bytes on traditional 32-bit platforms. * When free, this space contains a pointer to the next free block, * and the lower 3 bits of this pointer must be zero. * When in use, the first field is set to OVMAGIC with the lower 3 bits * nonzero, and the second field is the hash bucket index. * The overhead information precedes the data area returned to the user. * When special alignment is required, a fragment is created within the * data block, preceded by a special fragment header containing a magic * number FRMAGIC and the align offset from the overlapping block. */ typedef union overhead { struct { union overhead *ovfree_next; /* next chunk on free list */ } ov_free; struct { int ovused_magic; /* magic number */ int ovused_index; /* bucket index */ } ov_used; struct { int ovfrag_fmagic; /* fragment magic number */ int ovfrag_offset; /* fragment align offset */ } ov_frag; /* align on double word boundary */ double ov_align; } OVHDR; #define ov_next ov_free.ovfree_next /* next on free list */ #define ov_magic ov_used.ovused_magic /* magic number */ #define ov_index ov_used.ovused_index /* bucket index */ #define ov_fmagic ov_frag.ovfrag_fmagic /* fragment magic number */ #define ov_offset ov_frag.ovfrag_offset /* fragment align offset */ #define OVMAGIC 0xef /* overhead magic number */ #define FRMAGIC 0x5555 /* fragment magic number */ #define OVHDRSZ sizeof(OVHDR) /* size of overhead data */ /* * freelist[i] is the pointer to the next free chunk of size 2^(i+4). * The smallest allocatable chunk is 16 bytes, containing a user data * block of 8 bytes (assuming 8 bytes overhead on 32-bit platforms). */ #define MINCHUNK 4 /* minimum chunk size 2^(0+4) */ #define MAXCHUNK 30 /* maximum chunk size 2^(26+4) */ #define NBUCKETS 27 /* MAXCHUNK - MINCHUNK + 1 */ static OVHDR *freelist[NBUCKETS]; /* hash list of free chunks */ static int malloced[NBUCKETS]; /* number of allocated chunks */ #define chunk_size(i) (1 << ((i) + MINCHUNK)) #define data_size(i) (chunk_size(i) - OVHDRSZ) #define MINDATA ((1 << MINCHUNK) - OVHDRSZ) #define MAXDATA ((1 << MAXCHUNK) - OVHDRSZ) #define get_offset(a,b) ((unsigned long)(a) & ((b) - 1)) #define word_offset(a) get_offset(a, OVHDRSZ) #define page_offset(a) get_offset(a, PAGESIZE) #define aligned(a,b) (get_offset(a,b) == 0) #define word_aligned(a) aligned(a, OVHDRSZ) #define page_aligned(a) aligned(a, PAGESIZE) #define auto_aligned(a) aligned(a, a) #define valid_index(i) ((i) >= 0 && (i) < NBUCKETS) #define valid_offset(i) ((i) > OVHDRSZ && auto_aligned(i) && (i) <= PAGESIZE) /* * Definition of modules. */ #define PROTO(TYPES) () ptr_t *malloc PROTO((siz_t)); ptr_t *memalign PROTO((siz_t, siz_t)); free_t free PROTO((ptr_t *)); ptr_t *realloc PROTO((ptr_t *, siz_t)); ptr_t *calloc PROTO((siz_t, siz_t)); free_t cfree PROTO((ptr_t *)); ptr_t *valloc PROTO((siz_t)); free_t vfree PROTO((ptr_t *)); extern ptr_t *sbrk PROTO((int)); /* ** MALLOC -- Allocate more memory ** ------------------------------ */ ptr_t * malloc(size) siz_t size; /* amount of memory to allocate */ { register OVHDR *op; /* chunk pointer */ register int bucket; /* hash bucket index */ register int bucketsize; /* size of hash bucket chunk */ register int memsize; /* amount of memory to expand */ /* * First time malloc is called, do some sanity checks, setup page size, * and align the break pointer so all chunk data will be page aligned. * Note. Cannot issue debugging print statements during initialization. */ if (pagesize == 0) { if (page_offset(PAGESIZE) || word_offset(OVHDRSZ)) { errno = EINVAL; return(NULL); } op = (OVHDR *)sbrk(0); if (op == NULL || (char *)op == (char *)-1) { errno = ENOMEM; return(NULL); } memsize = page_offset(op); if (memsize > 0) { memsize = PAGESIZE - memsize; op = (OVHDR *)sbrk(memsize); if (op == NULL || (char *)op == (char *)-1) { errno = ENOMEM; return(NULL); } } /* initialization complete */ pagesize = PAGESIZE; } /* * Convert amount of memory requested into closest chunk size * stored in hash buckets which satisfies request. */ bucket = 0; bucketsize = chunk_size(bucket); while (bucketsize < OVHDRSZ || size > (bucketsize - OVHDRSZ)) { bucket++; bucketsize <<= 1; if (bucket >= NBUCKETS) { errno = EINVAL; return(NULL); } } /* * If nothing in hash bucket right now, request more memory from the system. * Add new memory allocated to that on free list for this hash bucket. * System memory is expanded by increments of whole pages. For small chunk * sizes, the page is subdivided into a list of free chunks. */ if (freelist[bucket] == NULL) { memsize = (bucketsize < PAGESIZE) ? PAGESIZE : bucketsize; op = (OVHDR *)sbrk(memsize); if (op == NULL || (char *)op == (char *)-1) { errno = ENOMEM; return(NULL); } freelist[bucket] = op; while (memsize > bucketsize) { memsize -= bucketsize; op->ov_next = (OVHDR *)((char *)op + bucketsize); op = op->ov_next; } op->ov_next = NULL; } /* * Memory is available. */ /* remove from linked list */ op = freelist[bucket]; freelist[bucket] = op->ov_next; malloced[bucket]++; /* mark this chunk in use */ op->ov_magic = OVMAGIC; op->ov_index = bucket; /* return pointer to user data block */ return((ptr_t *)((char *)op + OVHDRSZ)); } /* ** MEMALIGN -- Allocate memory with alignment constraints ** ------------------------------------------------------ */ ptr_t * memalign(align, size) siz_t align; /* required memory alignment */ siz_t size; /* amount of memory to allocate */ { register OVHDR *op; /* chunk pointer */ register ptr_t *newbuf; /* new block of user data */ register int offset = 0; /* fragment offset for alignment */ /* * The alignment must be a power of two, and no bigger than the page size. */ if ((align == 0) || !auto_aligned(align) || (align > PAGESIZE)) { errno = EINVAL; return(NULL); } /* * For ordinary small alignment sizes, we can use the plain malloc. */ if (align > OVHDRSZ) offset = align - OVHDRSZ; newbuf = malloc(size + offset); if (newbuf == NULL) return(NULL); /* * Otherwise we have to create a more strictly aligned fragment. */ if (offset > 0) { /* locate the proper alignment boundary within the block */ newbuf = (ptr_t *)((char *)newbuf + offset); /* mark this block as a special fragment */ op = (OVHDR *)((char *)newbuf - OVHDRSZ); op->ov_fmagic = FRMAGIC; op->ov_offset = OVHDRSZ + offset; } return(newbuf); } /* ** DEALLOC -- Put unneeded memory on the free list ** ----------------------------------------------- */ static int dealloc(oldbuf) ptr_t *oldbuf; /* old block of user data */ { register OVHDR *op; /* chunk pointer */ register int bucket; /* hash bucket index */ register int offset; /* fragment offset for alignment */ /* if no old block, or if not yet initialized */ if (oldbuf == NULL || pagesize == 0) { errno = EINVAL; return(0); } /* avoid bogus block addresses */ if (!word_aligned(oldbuf)) { errno = EINVAL; return(0); } /* move to the header for this chunk */ op = (OVHDR *)((char *)oldbuf - OVHDRSZ); /* adjust in case this is an aligned fragment */ if ((op->ov_fmagic == FRMAGIC) && valid_offset(op->ov_offset)) { offset = op->ov_offset - OVHDRSZ; op = (OVHDR *)((char *)op - offset); } /* check whether this chunk was really allocated */ if ((op->ov_magic != OVMAGIC) || !valid_index(op->ov_index)) { errno = EINVAL; return(0); } /* put back on the free list for this bucket */ bucket = op->ov_index; op->ov_next = freelist[bucket]; freelist[bucket] = op; malloced[bucket]--; return(1); } /* ** FREE -- Put unneeded memory on the free list ** -------------------------------------------- ** ** In this implementation, unneeded memory is never returned to ** the system, but is put on a free memory hash list instead. ** Subsequent malloc requests will first examine the free lists ** to see whether a request can be satisfied. ** As a consequence of this strategy, the process size will grow ** monotonously, up to the largest amount needed at any moment. ** ** On some platforms, this routine does not return a status code. ** The status will be stored in a global variable ``free_status'' ** which can be examined if desired. */ int free_status = 0; /* return code of last free */ free_t free(oldbuf) ptr_t *oldbuf; /* old block of user data */ { free_status = dealloc(oldbuf); free_return(free_status); } /* ** FINDBUCKET -- Locate a chunk of memory on the free list ** ------------------------------------------------------- ** ** When a program attempts "storage compaction" as mentioned in the ** old malloc man page, it realloc's an already freed block. Usually ** this is the last block it freed; occasionally it might be farther ** back. We have to search all the free lists for the block in order ** to determine its bucket: first we make one pass thru the lists ** checking only the first block in each; if that fails we search ** the entire lists for a match. If still not found it is an error. */ static int findbucket(oldop) OVHDR *oldop; /* old chunk to search for */ { register OVHDR *op; /* chunk pointer */ register int bucket; /* hash bucket index */ for (bucket = 0; bucket < NBUCKETS; bucket++) { if (freelist[bucket] == oldop) return(bucket); } for (bucket = 0; bucket < NBUCKETS; bucket++) { for (op = freelist[bucket]; op != NULL; op = op->ov_next) { if (op == oldop) return(bucket); } } /* not found */ return(-1); } /* ** REALLOC -- Rellocate already allocated memory ** --------------------------------------------- */ ptr_t * realloc(oldbuf, size) ptr_t *oldbuf; /* old block of user data */ siz_t size; /* amount of memory to allocate */ { register OVHDR *op; /* chunk pointer */ register ptr_t *newbuf; /* new block of user data */ register int bucket; /* hash bucket index */ register int offset = 0; /* fragment offset for alignment */ siz_t minsize, maxsize; /* size limits for this bucket */ int allocated = 0; /* set if old chunk was allocated */ /* * Do plain malloc if no old block, or if not yet initialized. * Otherwise, get the header, and check for special conditions. */ if (oldbuf == NULL || pagesize == 0) { newbuf = malloc(size); return(newbuf); } /* avoid bogus block addresses */ if (!word_aligned(oldbuf)) { errno = EINVAL; return(NULL); } /* move to the header for this chunk */ op = (OVHDR *)((char *)oldbuf - OVHDRSZ); /* adjust in case this is an aligned fragment */ if ((op->ov_fmagic == FRMAGIC) && valid_offset(op->ov_offset)) { offset = op->ov_offset - OVHDRSZ; op = (OVHDR *)((char *)op - offset); } /* * Check whether this chunk is allocated at this moment. * If not, try to locate it on the free list hash buckets. */ if ((op->ov_magic == OVMAGIC) && valid_index(op->ov_index)) { allocated = 1; bucket = op->ov_index; } else { bucket = findbucket(op); if (bucket < 0) { errno = EINVAL; return(NULL); } } /* * If the new size block fits into the same already allocated chunk, * we can just use it again, avoiding a malloc and a bcopy. Otherwise, * we can safely put it on the free list (the contents are preserved). */ /* make sure the alignment offset is consistent with the bucket */ if ((offset > 0) && (bucket == 0 || offset > data_size(bucket-1))) { errno = EINVAL; return(NULL); } /* maximum data block size of this chunk */ maxsize = data_size(bucket) - offset; if (allocated) { /* maximum data block size in the preceding hash bucket */ minsize = (bucket > 0) ? data_size(bucket-1) - offset : 0; /* re-use same block if within bounds */ if (size <= maxsize && size > minsize) return(oldbuf); /* no longer useable */ (void) dealloc(oldbuf); } /* * A new chunk must be allocated, possibly with alignment restrictions. */ newbuf = malloc(size + offset); if (newbuf == NULL) return(NULL); if (offset > 0) { /* locate the proper alignment boundary within the block */ newbuf = (ptr_t *)((char *)newbuf + offset); /* mark this block as a special fragment */ op = (OVHDR *)((char *)newbuf - OVHDRSZ); op->ov_fmagic = FRMAGIC; op->ov_offset = OVHDRSZ + offset; } /* * Copy the contents of the old user data block into the new block. * In case we shrink, only copy the requested amount of user data. * If we expand, copy the maximum possible amount from the old block. * Note that the exact amount of valid old user data is not known. */ if (oldbuf != newbuf) bcopy(oldbuf, newbuf, (size < maxsize) ? size : maxsize); return(newbuf); } /* ** CALLOC -- Allocate memory for number of elements, and clear it ** -------------------------------------------------------------- ** ** This is a wrapper for malloc to request memory for a number ** of consecutive elements of certain length. ** As a side effect, the entire memory block obtained is cleared. */ ptr_t * calloc(count, length) siz_t count; /* number of elements */ siz_t length; /* size per element */ { register ptr_t *newbuf; /* new block of user data */ register siz_t size; /* amount of memory to allocate */ size = count * length; newbuf = malloc(size); if (newbuf == NULL) return(NULL); bzero(newbuf, size); return(newbuf); } /* ** CFREE -- Put unneeded memory on the free list ** --------------------------------------------- */ free_t cfree(oldbuf) ptr_t *oldbuf; /* old block of user data */ { free_status = dealloc(oldbuf); free_return(free_status); } /* ** VALLOC -- Allocate memory on a page boundary ** -------------------------------------------- ** ** This is a wrapper for memalign to request memory that is ** aligned on a page boundary. In this implementation, this ** is the strictest alignment possible. */ ptr_t * valloc(size) siz_t size; /* amount of memory to allocate */ { register ptr_t *newbuf; /* new block of user data */ siz_t align = PAGESIZE; /* alignment on page boundary */ newbuf = memalign(align, size); return(newbuf); } /* ** VFREE -- Put unneeded memory on the free list ** --------------------------------------------- */ free_t vfree(oldbuf) ptr_t *oldbuf; /* old block of user data */ { free_status = dealloc(oldbuf); free_return(free_status); }