Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 24 Jul 1995 03:36:39 -0700 (PDT)
From:      Poul-Henning Kamp <phk>
To:        phk@freefall.cdrom.com (Poul-Henning Kamp)
Cc:        hackers@freebsd.org
Subject:   Re: new malloc.c please test
Message-ID:  <199507241036.DAA14811@freefall.cdrom.com>
In-Reply-To: <199507241032.DAA14618@freefall.cdrom.com> from "Poul-Henning Kamp" at Jul 24, 95 03:32:03 am

next in thread | previous in thread | raw e-mail | index | archive | help
I hate it when this happens,

Here is the right file:

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 *
 * $Id$
 *
 */

#if defined(__i386__) && defined(__FreeBSD__)

#   warning FreeBSD i386 constants hardcoded.

/* If these weren't defined here, they would be calculated on the fly */

#   define malloc_pagesize		4096U
#   define malloc_pageshift		12U
#   define malloc_minsize		16U

/* Keep this many empty pages around before returning pages to the kernel */
#   define NFP	10

/* Save a sbrk(0) call every now and then */
#define curbrk _curbrk
extern char * _curbrk asm ("curbrk");

#endif /* __i386__ && __FreeBSD__ */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <err.h>
#include <sys/types.h>
#include <sys/mman.h>


#ifdef MAIN
#  define SANITY
#  define DEBUG
#endif /* MAIN */

/*
 * This structure describes a page's worth of chunks.
 */

struct pginfo {
    struct pginfo	*next;	/* next on the free list */
    void		*page;	/* Pointer to the page */
    u_short		size;	/* size of this page's chunks */
    u_short		shift;	/* How far to shift for this size chunks */
    u_short		free;	/* How many free chunks */
    u_short		total;	/* How many chunk */
    u_long		bits[1]; /* Which chunks are free */
};

/*
 * How many bits per u_long in the bitmap.
 * Change only if not 8 bits/byte
 */
#define	MALLOC_BITS	(8*sizeof(u_long))

/*
 * Magic values to put in the page_directory
 */
#define MALLOC_NOT_MINE	((struct pginfo*) 0)
#define MALLOC_FREE 	((struct pginfo*) 1)
#define MALLOC_FIRST	((struct pginfo*) 2)
#define MALLOC_FOLLOW	((struct pginfo*) 3)
#define MALLOC_MAGIC	((struct pginfo*) 4)

/*
 * The i386 architecture has some very convenient instructions.
 * We might as well use them.
 */
#ifdef __i386__
#  warning i386 inline assembly used.
#define ffs _ffs
static __inline int
_ffs(unsigned input)
{
	int result;
	asm("bsfl %1,%0" : "=r" (result) : "r" (input));
	return result+1;
}

#define fls _fls
static __inline int
_fls(unsigned input)
{
	int result;
	asm("bsrl %1,%0" : "=r" (result) : "r" (input));
	return result+1;
}

#define set_bit _set_bit
static __inline void
_set_bit(struct pginfo *pi, int bit)
{
	asm("btsl %0,(%1)" :
	: "r" (bit & (MALLOC_BITS-1)), "r" (pi->bits+(bit/MALLOC_BITS)));
}

#define clr_bit _clr_bit
static __inline void
_clr_bit(struct pginfo *pi, int bit)
{
	asm("btcl %0,(%1)" :
	: "r" (bit & (MALLOC_BITS-1)), "r" (pi->bits+(bit/MALLOC_BITS)));
}

#endif __i386__

/*
 * Set to one when malloc_init has been called
 */
static	unsigned	initialized;

/*
 * The size of a page.
 * Must be a integral multiplum of the granularity of mmap(2).
 * Your toes will curl if it isn't a power of two
 */
#define malloc_pagemask	((malloc_pagesize)-1)

/*
 * The size of the largest chunk.
 * Half a page.
 */
#define malloc_maxsize	((malloc_pagesize)>>1)

/*
 * malloc_pagesize == 1 << malloc_pageshift
 */
#ifndef malloc_pageshift
static	unsigned	malloc_pageshift;
#endif /* malloc_pageshift */

/*
 * The smallest allocation we bother about.
 * Must be power of two
 */
#ifndef malloc_minsize
static	unsigned  malloc_minsize;
#endif /* malloc_minsize */

/*
 * The largest chunk we care about.
 * Must be smaller than pagesize
 * Must be power of two
 */
#ifndef malloc_maxsize
static	unsigned  malloc_maxsize;
#endif /* malloc_maxsize */

/*
 * The offset from pagenumber to index into the page directory
 */
static	u_long  malloc_origo;

/*
 * The last index in the page directory we care about
 */
static	u_long  last_index;

/*
 * Pointer to page directory.
 * Allocated "as if with" malloc
 */
static	struct	pginfo **page_dir;

/*
 * How many slots in the page directory
 */
static	unsigned	malloc_ninfo;

/*
 * A small cache of pages so we don't call mmap/munmap too much
 */
#ifdef NFP
static void *freepage[NFP];
#endif /* NFP */

static set_pgdir(void *, struct  pginfo *);

/*
 * Write something to stderr without all the luggage of stdio
 */
static void
wrterror(char *p)
{
	char *q = "malloc(): ";
	write(2,q,strlen(q));
	write(2,p,strlen(p));
	abort();
}

#ifdef DEBUG
void
malloc_dump(FILE *fd)
{
    struct pginfo **pd;
    int i,j;

    pd = page_dir;

    /* find last touched page */
    for(i=malloc_ninfo-1;i>=0;i--)
	if (pd[i])
	    break;

    /* print out all the pages */
    for(j=0;j<=i;j++) {
	fprintf(fd,"%08lx %5d ",(j+malloc_origo) << malloc_pageshift,j);
	if (pd[j] == MALLOC_NOT_MINE) {
	    for(j++;j<=i && pd[j] == MALLOC_NOT_MINE;j++)
		;
	    j--;
	    fprintf(fd,".. %5d not mine\n",	j);
	} else if (pd[j] == MALLOC_FREE) {
	    for(j++;j<=i && pd[j] == MALLOC_FREE;j++)
		;
	    j--;
	    fprintf(fd,".. %5d free\n", j);
	} else if (pd[j] == MALLOC_FIRST) {
	    for(j++;j<=i && pd[j] == MALLOC_FOLLOW;j++)
		;
	    j--;
	    fprintf(fd,".. %5d in use\n", j);
	} else if (pd[j] < MALLOC_MAGIC) {
	    fprintf(fd,"(%d)\n", j);
	} else {
	    fprintf(fd,"%p %d x %d @ %p\n",
		pd[j],pd[j]->free, pd[j]->size, pd[j]->page);
	}
    }

    /* print out various info */
    fprintf(fd,"Minsize\t%d\n",malloc_minsize);
    fprintf(fd,"Maxsize\t%d\n",malloc_maxsize);
    fprintf(fd,"Pagesize\t%d\n",malloc_pagesize);
    fprintf(fd,"Pageshift\t%d\n",malloc_pageshift);
    fprintf(fd,"FirstPage\t%ld\n",malloc_origo);
    fprintf(fd,"LastPage\t%ld %lx\n",last_index,last_index << malloc_pageshift);
    fprintf(fd,"Break\t%ld\n",(u_long)sbrk(0) >> malloc_pageshift);
}
#endif /* DEBUG */

#ifndef curbrk
#define curbrk sbrk(0);
#endif

/*
 * Allocate a number of pages from the OS
 */
static caddr_t
map_pages(void *where, int pages)
{
    caddr_t result;

    if (!where) {
	result = curbrk + malloc_pagemask - 1;
	result = (caddr_t) ((u_long)result & ~malloc_pagemask);
	if (!brk(result + (pages << malloc_pageshift))) {
	    last_index = ((u_long)result >> malloc_pageshift) - malloc_origo;
	    return result;
	}
    } else {
	result = mmap(where, pages << malloc_pageshift,
		PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON, -1, 0);
	if (result != (caddr_t) -1)
	    return result;
    }
    return 0;
}

/*
 * Return pages to the OS if we can
 */
static void
unmap_pages(caddr_t ptr, int pages)
{
    if (munmap(ptr, pages << malloc_pageshift))
	wrterror("botch: munmap failed.  (wild pointer ?)\n");
}

/*
 * Set a bit in the bitmap
 */
#ifndef set_bit
static __inline void
set_bit(struct pginfo *pi, int bit)
{
    pi->bits[bit/MALLOC_BITS] |= 1<<(bit%MALLOC_BITS);
}
#endif /* set_bit */

/*
 * Clear a bit in the bitmap
 */
#ifndef clr_bit
static __inline void
clr_bit(struct pginfo *pi, int bit)
{
    pi->bits[bit/MALLOC_BITS] &= ~(1<<(bit%MALLOC_BITS));
}
#endif /* clr_bit */

#ifdef SANITY
#ifndef tst_bit
/*
 * Test a bit in the bitmap
 */
static __inline int
tst_bit(struct pginfo *pi, int bit)
{
    return pi->bits[bit/MALLOC_BITS] & (1<<(bit%MALLOC_BITS));
}
#endif /* tst_bit */
#endif /* SANITY */

/*
 * Find last bit
 */
#ifndef fls
static __inline int
fls(int size)
{
    int i = 1;
    while (size >>= 1)
	i++;
    return i;
}
#endif /* fls */

/*
 * Extend page directory
 */
static int
extend_page_directory(u_long index)
{
    struct  pginfo **new,**old;
    int i;

    /* Make it this many pages */
    i = index * sizeof *page_dir;
    i /= malloc_pagesize;
    i += 2;

    /* Get new pages, if you used this much mem you don't care :-) */
    new = (struct pginfo**) map_pages(0,i);
    if (!new)
	return 0;

    /* Copy the old stuff */
    memset(new, 0, i * malloc_pagesize);
    memcpy(new, page_dir,
	    malloc_ninfo * sizeof *page_dir);

    /* register the new size */
    malloc_ninfo = i * malloc_pagesize / sizeof *page_dir;

    /* swap the pointers */
    old = page_dir;
    page_dir = new;

    /* Mark the pages */
    set_pgdir(new,MALLOC_FIRST);
    while (--i) {
	new += malloc_pagesize;
	set_pgdir(new,MALLOC_FOLLOW);
    }

    /* Now free the old stuff */
    free(old);
    return 1;
}

/*
 * Set entry in page directory.
 * Extend page directory if need be.
 */
static int
set_pgdir(void *ptr, struct  pginfo *info)
{
    u_long index = ((u_long)ptr >> malloc_pageshift) - malloc_origo;

    if (index >= malloc_ninfo && !extend_page_directory(index))
	return 0;
    page_dir[index] = info;
    return 1;
}

/*
 * Initialize the world
 */
static void
malloc_init ()
{
    int i;

#ifndef malloc_pagesize
    /* determine our pagesize */
    malloc_pagesize = getpagesize();
#endif /* malloc_pagesize */

#ifndef malloc_pageshift
    /* determine how much we shift by to get there */
    for (i = malloc_pagesize; i > 1; i >>= 1)
	malloc_pageshift++;
#endif /* malloc_pageshift */

#ifndef malloc_minsize
    /*
     * find the smallest size allocation we will bother about.
     * this is determined as the smallest allocation that can hold
     * it's own pginfo;
     */
    i = 2;
    for(;;) {
	int j;

	/* Figure out the size of the bits */
	j = malloc_pagesize/i;
	j /= 8;
	if (j < sizeof(u_long))
		j = sizeof (u_long);
	if (sizeof(struct pginfo) + j - sizeof (u_long) <= i)
		break;
	i += i;
    }
    malloc_minsize = i;
#endif /* malloc_minsize */


    /* Allocate one page for the page directory */
    page_dir = (struct pginfo **) map_pages(0,1);
    if (!page_dir)
	wrterror("fatal: my first mmap failed.  (check limits ?)\n");

    /*
     * We need a maximum of malloc_pageshift buckets, steal these from the
     * front of the page_directory;
     */
    malloc_origo = (u_long) page_dir >> malloc_pageshift;
    malloc_origo -= malloc_pageshift;

    /* Clear it */
    memset(page_dir,0,malloc_pagesize);

    /* Find out how much it tells us */
    malloc_ninfo = malloc_pagesize / sizeof *page_dir;

    /* Plug the page directory into itself */
    i = set_pgdir(page_dir,MALLOC_FIRST);
    if (!i)
	wrterror("fatal: couldn't set myself in the page directory\n");

    /* Been here, done that */
    initialized++;
}

/*
 * Allocate a number of complete pages
 */
void *
malloc_pages(size_t size)
{
    void *p,*q;
    int i;
    struct pginfo **pi,**pe;

    /* How many pages ? */
    size += (malloc_pagesize-1);
    size >>= malloc_pageshift;

    /* Look for free pages before asking for more */
    pi = page_dir + malloc_pageshift;
    pe = page_dir + last_index;
    p = 0;
    if (size == 1) {

#ifdef NFP
	for(i=0;i<NFP;i++)
	    if (freepage[i]) {
		p = freepage[i];
		freepage[i] = 0;
		goto found;
	    }
#endif /* NFP */

	/* Optimise this case since it's so common */
	for (; pi < pe; pi++) {
	    if (*pi == MALLOC_FREE) {
		/* Found a page, now find the pointer to it */
		p = (void*)((pi - page_dir + malloc_origo)
		    << malloc_pageshift);
		break;
	    }
	}
    } else {
	for (; pi < pe; pi++) {

	    if (pi[0] != MALLOC_FREE)
		continue;

	    if (pi[size-1] != MALLOC_FREE) {
		/* If the last one isn't OK, just move on, nothing in
		 * between will be any good then */
		pi += (size-1);
		continue;
	    }
	    for(i=1;i<size;i++)
		if (pi[i] != MALLOC_FREE) {
		    pi += i;
		    goto miss;
		}
	    /* Found the pages we need, now find the pointer to them */
	    p = (void*)((pi - page_dir + malloc_origo)
		<< malloc_pageshift);
	    break;
	miss:
	    }
	}

    /* Map the pages again */
    p = map_pages(p,size);

    /* ENOLUCK */
    if (!p)
	return 0;

#ifdef NFP
found:
#endif /* NFP */

    /* Mark the pages in the directory */
    i = set_pgdir(p,MALLOC_FIRST);
    if (!i)
	return 0;

    for (q=p; --size; ) {
	q += malloc_pagesize;
	i = set_pgdir(q,MALLOC_FOLLOW);
	if (!i)
	    return 0;
    }
    return p;
}

/*
 * Allocate a page of fragments
 */

static __inline int
malloc_make_chunks(int bits)
{
    struct  pginfo *bp;
    void *pp;
    int i,k,l;

    /* Allocate a new bucket */
    pp = malloc_pages(malloc_pagesize);
    if (!pp)
	return 0;
    l = sizeof *bp - sizeof(u_long);
    l += sizeof(u_long) *
	(((malloc_pagesize >> bits)+MALLOC_BITS-1) / MALLOC_BITS);
    if ((1<<(bits-1)) <= l) {
	bp = (struct  pginfo *)pp;
	l += (1<<bits);
	l--;
	l >>= bits;
    } else {
	bp = (struct  pginfo *)malloc(l);
	l = 0;
    }
    if (!bp)
	return 0;
    i = set_pgdir(pp,bp);
    if (!i)
	return 0;
    bp->size = (1<<bits);
    bp->shift = bits;
    bp->total = bp->free = malloc_pagesize >> bits;
    bp->next = 0;
    bp->page = pp;

    /* We can safely assume that there is nobody in this chain */
    page_dir[bits] = bp;

    /* set all valid bits in the bits */
    k = bp->total;
    i = 0;
    for(;k-i >= MALLOC_BITS; i += MALLOC_BITS)
	bp->bits[i / MALLOC_BITS] = ~0;
    for(; i < k; i++)
	set_bit(bp,i);

    /* We may have used the first ones already */
    for(i=0;i<l;i++) {
	clr_bit(bp,i);
	bp->free--;
	bp->total--;
    }
    return 1;
}

/*
 * Allocate a fragment
 */
static void *
malloc_bytes(size_t size)
{
    int j;

    /* Find the right bucket */
    if (size < malloc_minsize)
	size = malloc_minsize;
    j = fls((size)-1);
    while(1) {
	struct  pginfo *bp;

	bp = page_dir[j];

	if (bp) {
	    register int k;
	    register u_long *lp = bp->bits;
	    for (; !*lp; lp++)
		;
	    k = ffs(*lp) - 1;
	    *lp ^= 1<<k;
	    bp->free--;
	    if (!bp->free) {
		page_dir[j] = bp->next;
		bp->next = 0;
	    }
	    k += (lp-bp->bits)*MALLOC_BITS;
	    return bp->page + (k << bp->shift);
	}
	if (!malloc_make_chunks(j))
	    return 0;
    }
}

void *
malloc(size_t size)
{
    if (!initialized)
	malloc_init();

    if (size <= malloc_maxsize)
	return malloc_bytes(size);
    return malloc_pages(size);
}

void *
realloc(void *ptr, size_t size)
{
    void *p;
    u_long osize;
    struct pginfo **mp;

    if (!initialized)
	malloc_init();

    if (ptr && !size) {
	free(ptr);
	return 0;
    }

    if (!ptr)
	return malloc(size);

    mp = &page_dir[((u_long)ptr >> malloc_pageshift) - malloc_origo];
    if (*mp == MALLOC_FIRST) {
	osize = malloc_pagesize;
	while (mp[1] == MALLOC_FOLLOW) {
	    osize += malloc_pagesize;
	    mp++;
	}
    } else {
	osize = (*mp)->size;
    }

    p = malloc(size);

    if (p) {

	if (osize < size)
	    memcpy(p,ptr,osize);
	else
	    memcpy(p,ptr,size);
	free(ptr);
    }
    return p;
}

static __inline void
free_pages(void *ptr,u_long page, int index, struct pginfo *info)
{
    int i;
#ifdef SANITY
    if (info == MALLOC_FREE)
	wrterror("sanity: freeing free page.\n");
    if (info != MALLOC_FIRST)
	wrterror("sanity: freeing wrong page.\n");
    if ((u_long)ptr & malloc_pagemask)
	wrterror("sanity: freeing messed up page pointer.\n");
#endif /* SANITY */

    /* Count how many pages it is */
    for (i = 1; page_dir[index+i] == MALLOC_FOLLOW; i++)
	;

#ifdef NFP
    {
    int j;
    for(j=0;j<NFP;j++)
	if (!freepage[j]) {
	    freepage[j] = ptr;
	    page_dir[index++] = MALLOC_FREE;
	    if (!--i)
		return;
	    ptr += malloc_pagesize;
	}
     }
#endif /* NFP */

    /* Hand 'em back to the good 'ol OS */
    unmap_pages(ptr,i);

    /* Now update the directory */
    while(i--)
	page_dir[index++] = MALLOC_FREE;
}

static __inline void
free_bytes(void *ptr,u_long page, int index, struct pginfo *info)
{
    int i;
    struct pginfo **mp;

#ifdef SANITY
    if (info <= MALLOC_MAGIC)
	wrterror("sanity:  freeing something wrong.\n");
    if ((u_long)ptr & (info->size - 1))
	wrterror("sanity:  freeing messed up chunk pointer\n");
#endif /* SANITY */

    i = ((u_long)ptr & malloc_pagemask) >> info->shift;
#ifdef SANITY
    if (tst_bit(info,i))
	    wrterror("sanity: freeing free chunk.\n");
#endif /* SANITY */
    set_bit(info,i);
    info->free++;
    mp = page_dir + info->shift;
    if (info->free == 1) {
	/* Link in front of chain */
	info->next = *mp;
	*mp = info;
	return;
    }
    if (info->free != info->total)
	return;
/*
 * This keeps at least one index-page around for each size.
 * The benefit is decent considering the overhead (7 pages)
 */
    if (!info->next && (page_dir[info->shift] == info))
	return;

    while (*mp != info)
	mp = &((*mp)->next);
    *mp = info->next;
    set_pgdir(info->page,MALLOC_FIRST);
    if((void*)info->page == (void*)info) {
	free(info->page);
    } else {
	free(info->page);
	free(info);
    }
}

void
free(void *ptr)
{
    u_long page;
    struct pginfo *info;
    int index;

    if (!initialized)
	malloc_init();

    page = (u_long)ptr >> malloc_pageshift;
    index = page - malloc_origo;
    info = page_dir[index];

    if (info < MALLOC_MAGIC)
        return free_pages(ptr,page,index,info);
    return free_bytes(ptr,page,index,info);
}

#ifdef MAIN
/* TEST STUFF */

#define NBUCKETS	2000
#define NOPS		500000
void *foo[NBUCKETS];

int
main()
{
    int i,j,k;
    void *bar;

    setbuf(stdout,0);
    setbuf(stderr,0);
    bar = malloc(1);
    malloc_dump(stderr);
    free(bar);
    malloc_dump(stderr);

    for (i = 0 ; i < NOPS ; i++) {
	j = rand() % NBUCKETS;
	if (foo[j]) {
	    free(foo[j]);
	    foo[j] = 0;
	} else {
	    k = rand() % malloc_maxsize;
	    foo[j] = malloc(k);
	    if (!foo[j])
		    printf("%6d M [%4d] %8p %d\n",i,j,foo[j],k);
	}
    }
    for (j = 0 ; j < NBUCKETS ; j++) {
	if (foo[j]) {
	    free(foo[j]);
	    foo[j] = 0;
	}
    }
    malloc_dump(stderr);
    for (i = 0 ; i < NOPS ; i++) {
	j = rand() % NBUCKETS;
	if (foo[j]) {
	    free(foo[j]);
	    foo[j] = 0;
	} else {
	    k = rand() % malloc_maxsize;
	    foo[j] = malloc(k);
	    if (!foo[j])
		    printf("%6d M [%4d] %8p %d\n",i,j,foo[j],k);
	}
    }
    for (j = 0 ; j < NBUCKETS ; j++) {
	if (foo[j]) {
	    free(foo[j]);
	    foo[j] = 0;
	}
    }
    malloc_dump(stderr);
    return 0;
}
#endif /* MAIN */

--
Poul-Henning Kamp           | phk@FreeBSD.ORG       FreeBSD Core-team.
http://www.freebsd.org/~phk | phk@login.dknet.dk    Private mailbox.
whois: [PHK]                | phk@ref.tfs.com       TRW Financial Systems, Inc.
Just that: dried leaves in boiling water ?



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199507241036.DAA14811>