/** BEGIN COPYRIGHT BLOCK * This Program 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; version 2 of the License. * * This Program 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. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /* * Generic pool handling routines. * * Hopefully these reduce the number of malloc/free calls. * * * Thread warning: * This implementation is thread safe. However, simultaneous * mallocs/frees to the same "pool" are not safe. If you wish to * use this module across multiple threads, you should define * POOL_LOCKING which will make the malloc pools safe. * * Mike Belshe * 11-20-95 * */ #include "netsite.h" #include "base/systems.h" #include "base/systhr.h" #ifdef MALLOC_POOLS #include "base/pool.h" #include "base/ereport.h" #include "base/util.h" #include "base/crit.h" #include "base/dbtbase.h" #ifdef DEBUG #define POOL_ZERO_DEBUG #endif #undef POOL_LOCKING #define BLOCK_SIZE (32 * 1024) #define MAX_FREELIST_SIZE (BLOCK_SIZE * 32) /* WORD SIZE 8 sets us up for 8 byte alignment. */ #define WORD_SIZE 8 #undef ALIGN #define ALIGN(x) ( (x + WORD_SIZE-1) & (~(WORD_SIZE-1)) ) /* block_t * When the user allocates space, a BLOCK_SIZE (or larger) block is created in * the pool. This block is used until all the space is eaten within it. * When all the space is gone, a new block is created. * */ typedef struct block_t { char *data; /* the real alloc'd space */ char *start; /* first free byte in block */ char *end; /* ptr to end of block */ struct block_t *next; /* ptr to next block */ } block_t; /* pool_t * A pool is a collection of blocks. The blocks consist of multiple * allocations of memory, but a single allocation cannot be freed by * itself. Once the memory is allocated it is allocated until the * entire pool is freed. */ typedef struct pool_t { #ifdef DEBUG_CACHES time_t time_created; #endif #ifdef POOL_LOCKING CRITICAL lock; /* lock for modifying the pool */ #endif block_t *curr_block; /* current block being used */ block_t *used_blocks; /* blocks that are all used up */ long size; /* size of memory in pool */ struct pool_t *next; /* known_pools list */ } pool_t; /* known_pools * Primarily for debugging, keep a list of all active malloc pools. */ static pool_t *known_pools = NULL; static CRITICAL known_pools_lock = NULL; static unsigned long pool_blocks_created = 0; static unsigned long pool_blocks_freed = 0; /* freelist * Internally we maintain a list of free blocks which we try to pull from * whenever possible. This list will never have more than MAX_FREELIST_SIZE * bytes within it. */ static CRITICAL freelist_lock = NULL; static block_t *freelist = NULL; static unsigned long freelist_size = 0; static unsigned long freelist_max = MAX_FREELIST_SIZE; static int pool_disable = 0; int pool_internal_init() { if (pool_disable == 0) { if (known_pools_lock == NULL) { known_pools_lock = crit_init(); freelist_lock = crit_init(); } } return 0; } static block_t * _create_block(int size) { block_t *newblock = NULL; long bytes = ALIGN(size); block_t *free_ptr, *last_free_ptr = NULL; /* check freelist for large enough block first */ crit_enter(freelist_lock); free_ptr = freelist; while(free_ptr && ((free_ptr->end - free_ptr->data) < bytes)) { last_free_ptr = free_ptr; free_ptr = free_ptr->next; } if (free_ptr) { newblock = free_ptr; if (last_free_ptr) last_free_ptr->next = free_ptr->next; else freelist = free_ptr->next; freelist_size -= (newblock->end - newblock->data); crit_exit(freelist_lock); bytes = free_ptr->end - free_ptr->data; } else { pool_blocks_created++; crit_exit(freelist_lock); if (((newblock = (block_t *)PERM_MALLOC(sizeof(block_t))) == NULL) || ((newblock->data = (char *)PERM_MALLOC(bytes)) == NULL)) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateBlockOutOfMemory_)); if (newblock) PERM_FREE(newblock); return NULL; } } newblock->start = newblock->data; newblock->end = newblock->data + bytes; newblock->next = NULL; return newblock; } /* Caller must hold lock for the pool */ static void _free_block(block_t *block) { #ifdef POOL_ZERO_DEBUG memset(block->data, 0xa, block->end-block->data); #endif /* POOL_ZERO_DEBUG */ if ((unsigned long)(freelist_size + block->end - block->data) > freelist_max) { /* Just have to delete the whole block! */ crit_enter(freelist_lock); pool_blocks_freed++; crit_exit(freelist_lock); PERM_FREE(block->data); #ifdef POOL_ZERO_DEBUG memset(block, 0xa, sizeof(block)); #endif /* POOL_ZERO_DEBUG */ PERM_FREE(block); return; } crit_enter(freelist_lock); freelist_size += (block->end - block->data); block->start = block->data; block->next = freelist; freelist = block; crit_exit(freelist_lock); } /* ptr_in_pool() * Checks to see if the given pointer is in the given pool. * If true, returns a ptr to the block_t containing the ptr; * otherwise returns NULL */ block_t * _ptr_in_pool(pool_t *pool, void *ptr) { block_t *block_ptr = NULL; /* try to find a block which contains this ptr */ if ( ((char *)ptr < (char *)pool->curr_block->end) && ((char *)ptr >= (char *)pool->curr_block->data) ) block_ptr = pool->curr_block; else for( block_ptr = pool->used_blocks; block_ptr && (((char *)ptr >= (char *)block_ptr->end) && ((char *)ptr < (char *)block_ptr->data)); block_ptr = block_ptr->next); return block_ptr; } NSAPI_PUBLIC pool_handle_t * pool_create() { pool_t *newpool; if (pool_disable) return NULL; newpool = (pool_t *)PERM_MALLOC(sizeof(pool_t)); if (newpool) { /* Have to initialize now, as pools get created sometimes * before pool_init can be called... */ if (known_pools_lock == NULL) { known_pools_lock = crit_init(); freelist_lock = crit_init(); } if ( (newpool->curr_block =_create_block(BLOCK_SIZE)) == NULL) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_)); PERM_FREE(newpool); return NULL; } newpool->used_blocks = NULL; newpool->size = 0; newpool->next = NULL; #ifdef POOL_LOCKING newpool->lock = crit_init(); #endif #ifdef DEBUG_CACHES newpool->time_created = time(NULL); #endif /* Add to known pools list */ crit_enter(known_pools_lock); newpool->next = known_pools; known_pools = newpool; crit_exit(known_pools_lock); } else ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolCreateOutOfMemory_1)); return (pool_handle_t *)newpool; } NSAPI_PUBLIC void pool_destroy(pool_handle_t *pool_handle) { pool_t *pool = (pool_t *)pool_handle; block_t *tmp_blk; pool_t *last, *search; if (pool_disable) return; crit_enter(known_pools_lock); #ifdef POOL_LOCKING crit_enter(pool->lock); #endif if (pool->curr_block) _free_block(pool->curr_block); while(pool->used_blocks) { tmp_blk = pool->used_blocks; pool->used_blocks = pool->used_blocks->next; _free_block(tmp_blk); } /* Remove from the known pools list */ for (last = NULL, search = known_pools; search; last = search, search = search->next) if (search == pool) break; if (search) { if(last) last->next = search->next; else known_pools = search->next; } #ifdef POOL_LOCKING crit_exit(pool->lock); crit_terminate(pool->lock); #endif crit_exit(known_pools_lock); #ifdef POOL_ZERO_DEBUG memset(pool, 0xa, sizeof(pool)); #endif /* POOL_ZERO_DEBUG */ PERM_FREE(pool); return; } NSAPI_PUBLIC void * pool_malloc(pool_handle_t *pool_handle, size_t size) { pool_t *pool = (pool_t *)pool_handle; long reqsize, blocksize; char *ptr; if (pool == NULL || pool_disable) { return PERM_MALLOC(size); } #ifdef DEBUG if (size == 0) return NULL; #endif #ifdef POOL_LOCKING crit_enter(pool->lock); #endif reqsize = ALIGN(size); ptr = pool->curr_block->start; pool->curr_block->start += reqsize; /* does this fit into the last allocated block? */ if (pool->curr_block->start > pool->curr_block->end) { /* Did not fit; time to allocate a new block */ pool->curr_block->start -= reqsize; /* keep structs in tact */ pool->curr_block->next = pool->used_blocks; pool->used_blocks = pool->curr_block; /* Allocate a chunk of memory which is a multiple of BLOCK_SIZE * bytes */ blocksize = ( (size + BLOCK_SIZE-1) / BLOCK_SIZE ) * BLOCK_SIZE; if ( (pool->curr_block = _create_block(blocksize)) == NULL) { ereport(LOG_CATASTROPHE, XP_GetAdminStr(DBT_poolMallocOutOfMemory_)); #ifdef POOL_LOCKING crit_exit(pool->lock); #endif return NULL; } ptr = pool->curr_block->start; reqsize = ALIGN(size); pool->curr_block->start += reqsize; } pool->size += reqsize; #ifdef POOL_LOCKING crit_exit(pool->lock); #endif return ptr; } void _pool_free_error() { ereport(LOG_WARN, XP_GetAdminStr(DBT_freeUsedWherePermFreeShouldHaveB_)); return; } NSAPI_PUBLIC void pool_free(pool_handle_t *pool_handle, void *ptr) { if (pool_handle == NULL || pool_disable) { PERM_FREE(ptr); return; } #ifdef DEBUG /* Just to be nice, check to see if the ptr was allocated in a pool. * If not, issue a warning and do a REAL free just to make sure that * we don't leak memory. */ if ( !_ptr_in_pool((pool_t *)pool_handle, ptr) ) { _pool_free_error(); PERM_FREE(ptr); } #endif return; } NSAPI_PUBLIC void * pool_calloc(pool_handle_t *pool_handle, size_t nelem, size_t elsize) { void *ptr; if (pool_handle == NULL || pool_disable) return PERM_CALLOC(elsize * nelem); ptr = pool_malloc(pool_handle, elsize * nelem); if (ptr) memset(ptr, 0, elsize * nelem); return ptr; } NSAPI_PUBLIC void * pool_realloc(pool_handle_t *pool_handle, void *ptr, size_t size) { pool_t *pool = (pool_t *)pool_handle; void *newptr; block_t *block_ptr; size_t oldsize; if (pool_handle == NULL || pool_disable) return PERM_REALLOC(ptr, size); if ( (newptr = pool_malloc(pool_handle, size)) == NULL) return NULL; /* With our structure we don't know exactly where the end * of the original block is. But we do know an upper bound * which is a valid ptr. Search the outstanding blocks * for the block which contains this ptr, and copy... */ #ifdef POOL_LOCKING crit_enter(pool->lock); #endif if ( !(block_ptr = _ptr_in_pool(pool, ptr)) ) { /* User is trying to realloc nonmalloc'd space! */ return newptr; } oldsize = block_ptr->end - (char *)ptr ; if (oldsize > size) oldsize = size; memmove((char *)newptr, (char *)ptr, oldsize); #ifdef POOL_LOCKING crit_exit(pool->lock); #endif return newptr; } NSAPI_PUBLIC char * pool_strdup(pool_handle_t *pool_handle, const char *orig_str) { char *new_str; int len = strlen(orig_str); if (pool_handle == NULL || pool_disable) return PERM_STRDUP(orig_str); new_str = (char *)pool_malloc(pool_handle, len+1); if (new_str) memcpy(new_str, orig_str, len+1); return new_str; } NSAPI_PUBLIC long pool_space(pool_handle_t *pool_handle) { pool_t *pool = (pool_t *)pool_handle; return pool->size; } NSAPI_PUBLIC int pool_enabled() { #ifndef THREAD_ANY /* we don't have USE_NSPR defined so systhread_getdata is undef'ed */ return 0; #else if (pool_disable || (getThreadMallocKey() == -1) ) return 0; if (!systhread_getdata(getThreadMallocKey())) return 0; return 1; #endif } /* pool_service_debug() * NSAPI service routine to print state information about the existing * pools. Hopefully useful in debugging. * */ #define MAX_DEBUG_LINE 1024 #ifdef DEBUG_CACHES /* XXXrobm causes entanglement in install and admserv cgis */ NSAPI_PUBLIC int pool_service_debug(pblock *pb, Session *sn, Request *rq) { char tmp_buf[MAX_DEBUG_LINE]; char cbuf[DEF_CTIMEBUF]; int len; pool_t *pool_ptr; block_t *block_ptr; int pool_cnt, block_cnt; param_free(pblock_remove("content-type", rq->srvhdrs)); pblock_nvinsert("content-type", "text/html", rq->srvhdrs); protocol_status(sn, rq, PROTOCOL_OK, NULL); protocol_start_response(sn, rq); len = util_sprintf(tmp_buf, "

Memory pool status report

\n"); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Note: The 0 block in each pool is \ the currently used block

\n"); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Freelist size: %d/%d

", freelist_size, freelist_max); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Pool disabled: %d

", pool_disable); net_write(sn->csd, tmp_buf, len); len = util_sprintf(tmp_buf, "Blocks created: %d

Blocks freed: %d", pool_blocks_created, pool_blocks_freed); net_write(sn->csd, tmp_buf, len); /* Create an HTML table */ len = util_sprintf(tmp_buf, "