/* $Id: vq_malloc.c,v 1.10 2004/08/24 08:58:23 janakj Exp $
*
* Copyright (C) 2001-2003 FhG Fokus
*
* This file is part of ser, a free SIP server.
*
* ser is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* For a license to use the ser software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* ser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* History:
* merged from Andrei's qmalloc and many fragments from Regents
* University of California NetBSD malloc used; see
* http://www.ajk.tele.fi/libc/stdlib/malloc.c.html#malloc for more
* details including redistribution policy; this policy asks for
* displaying the copyright:
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
*
*
* About:
* aggressive, wasteful and very quick malloc library built for
* servers that continuously allocate and release chunks of only
* few sizes:
* - free lists are organized by size (which eliminates long list traversal
* thru short free chunks if a long one is asked)
* - quite a few sizes are supported --> this results in more waste
* (unused place in a chunk) however memory can be well reused after
* freeing
* - the last bucket holds unlikely, huge, variable length chunks;
* they are maintained as stack growing from the opposite direction
* of our heap; coalescing is enabled; stack-like first-fit used to
* find free fragments
*
* TODO: possibly, some delayed coalescence wouldn't hurt; also, further
* optimization will certainly speed-up the entire process a lot; using
* knowledge of application as well as trying to make pipeline happy
* (from which I am sadly far away now)
* provides optimization space; trying to use other memory allocators
* to compare would be a great thing to do too;
*
* also, comparing to other memory allocators (like Horde) would not
* be a bad idea: those folks have been doing that for ages and specifically
* Horde has been heavily optimized for multi-processor machines
*
* References:
* - list of malloc implementations:
* http://www.cs.colorado.edu/~zorn/Malloc.html
* - a white-paper: http://g.oswego.edu/dl/html/malloc.html
* - Paul R. Wilson, Mark S. Johnstone, Michael Neely, and David Boles:
* ``Dynamic Storage Allocation: A Survey and Critical Review'' in
* International Workshop on Memory Management, September 1995,
* ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps
* - ptmalloc: http://www.malloc.de/en/
* - GNU C-lib malloc:
* http://www.gnu.org/manual/glibc-2.0.6/html_chapter/libc_3.html
* - delorie malocs: http://www.delorie.com/djgpp/malloc/
*
*/
#ifdef VQ_MALLOC
#include <stdlib.h>
#include "../config.h"
#include "../globals.h"
#include "vq_malloc.h"
#include "../dprint.h"
#include "../globals.h"
#define BIG_BUCKET(_qm) ((_qm)->max_small_bucket+1)
#define IS_BIGBUCKET(_qm, _bucket) ((_bucket)==BIG_BUCKET(_qm))
#ifdef DBG_QM_MALLOC
#define ASSERT(a) \
my_assert(a, __LINE__, __FILE__, __FUNCTION__ )
#else
#define ASSERT(a)
#endif
#ifdef DBG_QM_MALLOC
# define MORE_CORE(_q,_b,_s)\
(more_core( (_q), (_b), (_s), file, func, line ))
#else
# define MORE_CORE(_q,_b,_s) (more_core( (_q), (_b), (_s) ))
#endif
/* dimensioning buckets: define the step function constants for size2bucket */
int s2b_step[] = {8, 16, 32, 64, 128, 256, 512, 1024, 1536, 2048, 2560,
MAX_FIXED_BLOCK, EO_STEP };
void my_assert( int assertation, int line, char *file, char *function )
{
if (assertation) return;
LOG(L_CRIT,"CRIT: assertation failed in %s (%s:%d)\n",
function, file, line);
abort();
}
#ifdef DBG_QM_MALLOC
void vqm_debug_frag(struct vqm_block* qm, struct vqm_frag* f)
{
if (f->check!=ST_CHECK_PATTERN){
LOG(L_CRIT, "BUG: vqm_*: fragm. %p beginning overwritten(%x)!\n",
f, f->check);
vqm_status(qm);
abort();
};
if (memcmp(f->end_check, END_CHECK_PATTERN, END_CHECK_PATTERN_LEN)!=0) {
LOG(L_CRIT, "BUG: vqm_*: fragm. %p end overwritten(%.*s)!\n",
f, END_CHECK_PATTERN_LEN, f->end_check );
vqm_status(qm);
abort();
}
}
#endif
/* takes demanded size without overhead as input, returns bucket number
and changed the demanded size to size really used including all
possible overhead
*/
unsigned char size2bucket( struct vqm_block* qm, int *size )
{
unsigned char b;
unsigned int real_size;
unsigned int exceeds;
real_size = *size+ VQM_OVERHEAD;
#ifdef DBG_QM_MALLOC
real_size+=END_CHECK_PATTERN_LEN;
#endif
real_size+=((exceeds = (real_size % 8 )) ? 8 - exceeds : 0);
ASSERT( !(real_size%8) );
/* "small" chunk sizes <=1k translated using a table */
if ( real_size < MAX_FIXED_BLOCK ) {
b = qm->s2b[ real_size ];
*size = qm->s2s[ real_size ];
/* there might be various allocations slightly 1>1k, I still
don't want to be too aggressive and increase useless
allocations in small steps
*/
} else {
b = BIG_BUCKET(qm);
*size = MAX_FIXED_BLOCK +
(real_size-MAX_FIXED_BLOCK+BLOCK_STEP)
/ BLOCK_STEP * BLOCK_STEP;
}
/*size must be a multiple of 8*/
ASSERT( !(*size%8) );
return b;
}
/* init malloc and return a qm_block */
struct vqm_block* vqm_malloc_init(char* address, unsigned int size)
{
char* start;
struct vqm_block* qm;
unsigned int init_overhead;
unsigned char b; /* bucket iterator */
unsigned int s; /* size iterator */
char *end;
/* make address and size multiple of 8*/
start=(char*)( ((unsigned int)address%8)?((unsigned int)address+8)/8*8:
(unsigned int)address);
if (size<start-address) return 0;
size-=(start-address);
if (size <8) return 0;
size=(size%8)?(size-8)/8*8:size;
init_overhead=sizeof(struct vqm_block);
if (size < init_overhead)
{
/* not enough mem to create our control structures !!!*/
return 0;
}
end = start + size;
qm=(struct vqm_block*)start;
memset(qm, 0, sizeof(struct vqm_block));
size-=init_overhead;
/* definition of the size2bucket table */
for (s=0, b=0; s<MAX_FIXED_BLOCK ; s++) {
while (s>s2b_step[b]) b++;
if (b>MAX_BUCKET) {
LOG(L_CRIT, "CRIT: vqm_malloc_init: attempt to install too"
" many buckets, s2b_step > MAX_BUCKET\n");
return 0;
}
qm->s2b[s] = b;
qm->s2s[s] = s2b_step[b];
}
qm->max_small_bucket = b;
/* from where we will draw memory */
qm->core = (char *) ( start + sizeof( struct vqm_block ) );
qm->free_core = size;
/* remember for bound checking */
qm->init_core = qm->core;
qm->core_end = end;
/* allocate big chunks from end */
qm->big_chunks = end;
return qm;
}
struct vqm_frag *more_core( struct vqm_block* qm,
unsigned char bucket, unsigned int size
#ifdef DBG_QM_MALLOC
, char *file, char *func, unsigned int line
#endif
)
{
struct vqm_frag *new_chunk;
struct vqm_frag_end *end;
if (qm->free_core<size) return 0;
/* update core */
if (IS_BIGBUCKET(qm, bucket)) {
qm->big_chunks-=size;
new_chunk = (struct vqm_frag *) qm->big_chunks ;
} else {
new_chunk = (struct vqm_frag *) qm->core;
qm->core+=size;
}
qm->free_core-=size;
/* initialize the new fragment */
new_chunk->u.inuse.bucket = bucket;
new_chunk->size = size;
end=FRAG_END( new_chunk );
end->size=size;
return new_chunk;
}
static inline void vqm_detach_free( struct vqm_block* qm,
struct vqm_frag* frag)
{
struct vqm_frag *prev, *next;
prev=FRAG_END(frag)->prv_free;
next=frag->u.nxt_free;
if (prev) prev->u.nxt_free=next;
else qm->next_free[BIG_BUCKET(qm)]=next;
if (next) FRAG_END(next)->prv_free=prev;
}
#ifdef DBG_QM_MALLOC
void* vqm_malloc(struct vqm_block* qm, unsigned int size,
char* file, char* func, unsigned int line)
#else
void* vqm_malloc(struct vqm_block* qm, unsigned int size)
#endif
{
struct vqm_frag *new_chunk, *f;
unsigned char bucket;
#ifdef DBG_QM_MALLOC
unsigned int demanded_size;
DBG("vqm_malloc(%p, %d) called from %s: %s(%d)\n", qm, size, file,
func, line);
demanded_size = size;
#endif
new_chunk=0;
/* what's the bucket? what's the total size incl. overhead? */
bucket = size2bucket( qm, &size );
if (IS_BIGBUCKET(qm, bucket)) { /* the kilo-bucket uses first-fit */
#ifdef DBG_QM_MALLOC
DBG("vqm_malloc: processing a big fragment\n");
#endif
for (f=qm->next_free[bucket] ; f; f=f->u.nxt_free )
if (f->size>=size) { /* first-fit */
new_chunk=f;
VQM_DEBUG_FRAG(qm, f);
vqm_detach_free(qm,f);
break;
}
} else if ( (new_chunk=qm->next_free[ bucket ]) ) { /*fixed size bucket*/
VQM_DEBUG_FRAG(qm, new_chunk);
/*detach it from the head of bucket's free list*/
qm->next_free[ bucket ] = new_chunk->u.nxt_free;
}
if (!new_chunk) { /* no chunk can be reused; slice one from the core */
new_chunk=MORE_CORE( qm, bucket, size );
if (!new_chunk) {
#ifdef DBG_QM_MALLOC
LOG(L_DBG, "vqm_malloc(%p, %d) called from %s: %s(%d)\n",
qm, size, file, func, line);
#else
LOG(L_DBG, "vqm_malloc(%p, %d) called from %s: %s(%d)\n",
qm, size);
#endif
return 0;
}
}
new_chunk->u.inuse.magic = FR_USED;
new_chunk->u.inuse.bucket=bucket;
#ifdef DBG_QM_MALLOC
new_chunk->file=file;
new_chunk->func=func;
new_chunk->line=line;
new_chunk->demanded_size=demanded_size;
qm->usage[ bucket ]++;
DBG("vqm_malloc( %p, %d ) returns address %p in bucket %d, real-size %d\n",
qm, demanded_size, (char*)new_chunk+sizeof(struct vqm_frag),
bucket, size );
new_chunk->end_check=(char*)new_chunk+
sizeof(struct vqm_frag)+demanded_size;
memcpy( new_chunk->end_check, END_CHECK_PATTERN, END_CHECK_PATTERN_LEN );
new_chunk->check=ST_CHECK_PATTERN;
#endif
return (char*)new_chunk+sizeof(struct vqm_frag);
}
#ifdef DBG_QM_MALLOC
void vqm_free(struct vqm_block* qm, void* p, char* file, char* func,
unsigned int line)
#else
void vqm_free(struct vqm_block* qm, void* p)
#endif
{
struct vqm_frag *f, *next, *prev, *first_big;
unsigned char b;
#ifdef DBG_QM_MALLOC
DBG("vqm_free(%p, %p), called from %s: %s(%d)\n",
qm, p, file, func, line);
if (p>(void *)qm->core_end || p<(void*)qm->init_core){
LOG(L_CRIT, "BUG: vqm_free: bad pointer %p (out of memory block!) - "
"aborting\n", p);
abort();
}
#endif
if (p==0) {
DBG("WARNING:vqm_free: free(0) called\n");
return;
}
f=(struct vqm_frag*) ((char*)p-sizeof(struct vqm_frag));
b=f->u.inuse.bucket;
#ifdef DBG_QM_MALLOC
VQM_DEBUG_FRAG(qm, f);
if ( ! FRAG_ISUSED(f) ) {
LOG(L_CRIT, "BUG: vqm_free: freeing already freed pointer,"
" first freed: %s: %s(%d) - aborting\n",
f->file, f->func, f->line);
abort();
}
if ( b>MAX_BUCKET ) {
LOG(L_CRIT, "BUG: vqm_free: fragment with too high bucket nr: "
"%d, allocated: %s: %s(%d) - aborting\n",
b, f->file, f->func, f->line);
abort();
}
DBG("vqm_free: freeing %d bucket block alloc'ed from %s: %s(%d)\n",
f->u.inuse.bucket, f->file, f->func, f->line);
f->file=file; f->func=func; f->line=line;
qm->usage[ f->u.inuse.bucket ]--;
#endif
if (IS_BIGBUCKET(qm,b)) {
next=FRAG_NEXT(f);
if ((char *)next +sizeof( struct vqm_frag) < qm->core_end) {
VQM_DEBUG_FRAG(qm, next);
if (! FRAG_ISUSED(next)) { /* coalesce with next fragment */
DBG("vqm_free: coalesced with next\n");
vqm_detach_free(qm, next);
f->size+=next->size;
FRAG_END(f)->size=f->size;
}
}
first_big = qm->next_free[b];
if (first_big && f>first_big) {
prev=FRAG_PREV(f);
VQM_DEBUG_FRAG(qm, prev);
if (!FRAG_ISUSED(prev)) { /* coalesce with prev fragment */
DBG("vqm_free: coalesced with prev\n");
vqm_detach_free(qm, prev );
prev->size+=f->size;
f=prev;
FRAG_END(f)->size=f->size;
}
}
if ((char *)f==qm->big_chunks) { /* release unused core */
DBG("vqm_free: big chunk released\n");
qm->free_core+=f->size;
qm->big_chunks+=f->size;
return;
}
first_big = qm->next_free[b];
/* fix reverse link (used only for BIG_BUCKET */
if (first_big) FRAG_END(first_big)->prv_free=f;
FRAG_END(f)->prv_free=0;
} else first_big = qm->next_free[b];
f->u.nxt_free = first_big; /* also clobbers magic */
qm->next_free[b] = f;
}
void dump_frag( struct vqm_frag* f, int i )
{
LOG(memlog, " %3d. address=%p real size=%d bucket=%d\n", i,
(char*)f+sizeof(struct vqm_frag), f->size, f->u.inuse.bucket);
#ifdef DBG_QM_MALLOC
LOG(memlog, " demanded size=%d\n", f->demanded_size );
LOG(memlog, " alloc'd from %s: %s(%d)\n",
f->file, f->func, f->line);
LOG(memlog, " start check=%x, end check= %.*s\n",
f->check, END_CHECK_PATTERN_LEN, f->end_check );
#endif
}
void vqm_status(struct vqm_block* qm)
{
struct vqm_frag* f;
unsigned int i,on_list;
LOG(memlog, "vqm_status (%p):\n", qm);
if (!qm) return;
LOG(memlog, " heap size= %d, available: %d\n",
qm->core_end-qm->init_core, qm->free_core );
LOG(memlog, "dumping unfreed fragments:\n");
for (f=(struct vqm_frag*)qm->init_core, i=0;(char*)f<(char*)qm->core;
f=FRAG_NEXT(f) ,i++) if ( FRAG_ISUSED(f) ) dump_frag(f, i);
LOG(memlog, "dumping unfreed big fragments:\n");
for (f=(struct vqm_frag*)qm->big_chunks,i=0;(char*)f<(char*)qm->core_end;
f=FRAG_NEXT(f) ,i++) if ( FRAG_ISUSED(f) ) dump_frag( f, i );
#ifdef DBG_QM_MALLOC
DBG("dumping bucket statistics:\n");
for (i=0; i<=BIG_BUCKET(qm); i++) {
for(on_list=0, f=qm->next_free[i]; f; f=f->u.nxt_free ) on_list++;
LOG(L_DBG, " %3d. bucket: in use: %ld, on free list: %d\n",
i, qm->usage[i], on_list );
}
#endif
LOG(memlog, "-----------------------------\n");
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1