/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* wizard:/space/4.3reno/usr/src/lib/libc/stdlib/malloc.c
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that: (1) source distributions retain this entire copyright
* notice and comment, and (2) distributions including binaries display
* the following acknowledgement: ``This product includes software
* developed by the University of California, Berkeley and its contributors''
* in the documentation or other materials provided with the distribution
* and in all advertising materials mentioning features or use of this
* software. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
* malloc.c (Caltech) 2/21/82
* Chris Kingsley, kingsley@cit-20.
*
* This is a very 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^n-4 bytes long (ILP32)
* or 2^n-8 bytes long (LP64).
*/
/*LINTLIBRARY*/
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
/*
* The overhead on a block is at least 4 bytes. When free, this space
* contains a pointer to the next free block, and the bottom two bits must
* be zero. When in use, the first byte is set to MAGIC, and the second
* byte is the size index. The remaining bytes are for alignment.
* The order of elements is critical: ov_magic must overlay the low order
* bits of ov_next, and ov_magic can not be a valid ov_next bit pattern.
* Overhead is 4 bytes for ILP32, 8 bytes for LP64
*/
union overhead {
union overhead *ov_next; /* when free */
struct {
#if defined(_LITTLE_ENDIAN)
uchar_t ovu_magic; /* magic number */
uchar_t ovu_index; /* bucket # */
uchar_t ovu_pad[sizeof (union overhead *) - 2];
#elif defined(_BIG_ENDIAN)
uchar_t ovu_pad[sizeof (union overhead *) - 2];
uchar_t ovu_index; /* bucket # */
uchar_t ovu_magic; /* magic number */
#else
#error "Endianness is not defined"
#endif
} ovu;
};
#define ov_magic ovu.ovu_magic
#define ov_index ovu.ovu_index
#define MAGIC 0xef /* magic # on accounting info */
/*
* nextf[i] is the pointer to the next free block of size 2^(i+EXP).
* The smallest allocatable block is 8 bytes (ILP32) or 16 bytes (LP64).
* The overhead information precedes the data area returned to the user.
*/
#ifdef _LP64
#define EXP 4
#define NBUCKETS 60
#else
#define EXP 3
#define NBUCKETS 29
#endif
static union overhead *nextf[NBUCKETS];
static int pagesz; /* page size */
static long sbrk_adjust; /* in case sbrk() does alignment */
static int pagebucket; /* page size bucket */
static void morecore(int);
static int findbucket(union overhead *, int);
void *
malloc(size_t nbytes)
{
union overhead *op;
int bucket;
ssize_t n;
size_t amt;
/*
* First time malloc is called, setup page size and
* align break pointer so all data will be page aligned.
*/
if (pagesz == 0) {
pagesz = getpagesize();
op = sbrk(0);
n = pagesz - sizeof (*op) - ((uintptr_t)op & (pagesz - 1));
if (n < 0)
n += pagesz;
if (n) {
if (sbrk(n) == (void *)-1)
return (NULL);
/*
* We were careful to arrange that
* sbrk(0) + sizeof (union overhead)
* should end up on a page boundary.
* If the underlying sbrk() performs alignment
* then this is false. We compute the adjustment.
*/
op = sbrk(0);
sbrk_adjust = (uintptr_t)(op + 1) & (pagesz - 1);
} else {
sbrk_adjust = 0;
}
bucket = 0;
amt = (1UL << EXP);
while (pagesz > amt) {
amt <<= 1;
bucket++;
}
pagebucket = bucket;
}
/*
* Convert amount of memory requested into closest block size
* stored in hash buckets which satisfies request.
* Account for space used per block for accounting.
*/
if (nbytes <= (n = pagesz - sizeof (*op))) {
amt = (1UL << EXP); /* size of first bucket */
bucket = 0;
n = -(ssize_t)(sizeof (*op));
} else {
amt = pagesz;
bucket = pagebucket;
}
while (nbytes > amt + n) {
amt <<= 1;
if (amt == 0)
return (NULL);
bucket++;
}
/*
* If nothing in hash bucket right now,
* request more memory from the system.
*/
if ((op = nextf[bucket]) == NULL) {
morecore(bucket);
if ((op = nextf[bucket]) == NULL)
return (NULL);
}
/* remove from linked list */
nextf[bucket] = op->ov_next;
op->ov_magic = MAGIC;
op->ov_index = (uchar_t)bucket;
return (op + 1);
}
/*
* Allocate more memory to the indicated bucket.
*/
static void
morecore(int bucket)
{
union overhead *op;
size_t sz; /* size of desired block */
ssize_t amt; /* amount to allocate */
long nblks; /* how many blocks we get */
sz = 1UL << (bucket + EXP);
if (sz == 0)
return;
if (sz < pagesz) {
amt = pagesz;
nblks = amt / sz;
} else {
amt = sz + pagesz;
nblks = 1;
}
if (amt <= 0)
return;
if (amt > LONG_MAX) {
intptr_t delta;
/*
* the value required is too big for sbrk() to deal with
* in one go, so use sbrk() at most 2 times instead.
*/
op = sbrk(0);
delta = LONG_MAX;
while (delta > 0) {
if (sbrk(delta) == (void *)-1) {
if (op != sbrk(0))
(void) sbrk(-LONG_MAX);
return;
}
amt -= LONG_MAX;
delta = amt;
}
}
else
op = sbrk(amt);
/* no more room! */
if (op == (union overhead *)-1)
return;
/* LINTED improper alignment */
op = (union overhead *)((caddr_t)op - sbrk_adjust);
/*
* Add new memory allocated to that on
* free list for this hash bucket.
*/
nextf[bucket] = op;
while (--nblks > 0) {
/* LINTED improper alignment */
op->ov_next = (union overhead *)((caddr_t)op + sz);
/* LINTED improper alignment */
op = (union overhead *)((caddr_t)op + sz);
}
}
void
free(void *cp)
{
int size;
union overhead *op;
if (cp == NULL)
return;
/* LINTED improper alignment */
op = (union overhead *)((caddr_t)cp - sizeof (union overhead));
if (op->ov_magic != MAGIC)
return; /* previously freed? */
size = op->ov_index;
op->ov_next = nextf[size]; /* also clobbers ov_magic */
nextf[size] = op;
}
/*
* 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: 1st we make one pass thru the lists
* checking only the first block in each; if that fails we search
* ``realloc_srchlen'' blocks in each list for a match (the variable
* is extern so the caller can modify it). If that fails we just copy
* however many bytes was given to realloc() and hope it's not huge.
*/
int realloc_srchlen = 4; /* 4 should be plenty, -1 =>'s whole list */
void *
realloc(void *cp, size_t nbytes)
{
size_t onb;
int i;
union overhead *op;
char *res;
int was_alloced = 0;
if (cp == NULL)
return (malloc(nbytes));
/* LINTED improper alignment */
op = (union overhead *)((caddr_t)cp - sizeof (union overhead));
if (op->ov_magic == MAGIC) {
was_alloced++;
i = op->ov_index;
} else {
/*
* Already free, doing "compaction".
*
* Search for the old block of memory on the
* free list. First, check the most common
* case (last element free'd), then (this failing)
* the last ``realloc_srchlen'' items free'd.
* If all lookups fail, then just malloc() the
* space and copy the size of the new space.
*/
if ((i = findbucket(op, 1)) < 0 &&
(i = findbucket(op, realloc_srchlen)) < 0) {
if ((res = malloc(nbytes)) != NULL)
(void) memmove(res, cp, nbytes);
return (res);
}
}
onb = 1UL << (i + EXP);
if (onb < pagesz)
onb -= sizeof (*op);
else
onb += pagesz - sizeof (*op);
/* avoid the copy if same size block */
if (was_alloced) {
size_t sz = 0;
if (i) {
sz = 1UL << (i + EXP - 1);
if (sz < pagesz)
sz -= sizeof (*op);
else
sz += pagesz - sizeof (*op);
}
if (nbytes <= onb && nbytes > sz) {
return (cp);
} else
free(cp);
}
if ((res = malloc(nbytes)) == NULL)
return (NULL);
if (cp != res) /* common optimization if "compacting" */
(void) memmove(res, cp, (nbytes < onb) ? nbytes : onb);
return (res);
}
/*
* Search ``srchlen'' elements of each free list for a block whose
* header starts at ``freep''. If srchlen is -1 search the whole list.
* Return bucket number, or -1 if not found.
*/
static int
findbucket(union overhead *freep, int srchlen)
{
union overhead *p;
int i, j;
for (i = 0; i < NBUCKETS; i++) {
j = 0;
for (p = nextf[i]; p && j != srchlen; p = p->ov_next) {
if (p == freep)
return (i);
j++;
}
}
return (-1);
}