/* * ircd-ratbox: A slightly useful ircd. * balloc.c: A block allocator. * * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center * Copyright (C) 1996-2002 Hybrid Development Team * Copyright (C) 2002-2005 ircd-ratbox development team * * File: blalloc.c * Owner: Wohali (Joan Touzet) * * Modified 2001/11/29 for mmap() support by Aaron Sethman * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 * * $Id: balloc.c 388 2005-12-07 16:34:40Z nenolod $ */ /* * About the block allocator * * Basically we have three ways of getting memory off of the operating * system. Below are this list of methods and the order of preference. * * 1. mmap() anonymous pages with the MMAP_ANON flag. * 2. mmap() via the /dev/zero trick. * 3. malloc() * * The advantages of 1 and 2 are this. We can munmap() the pages which will * return the pages back to the operating system, thus reducing the size * of the process as the memory is unused. malloc() on many systems just keeps * a heap of memory to itself, which never gets given back to the OS, except on * exit. This of course is bad, if say we have an event that causes us to allocate * say, 200MB of memory, while our normal memory consumption would be 15MB. In the * malloc() case, the amount of memory allocated to our process never goes down, as * malloc() has it locked up in its heap. With the mmap() method, we can munmap() * the block and return it back to the OS, thus causing our memory consumption to go * down after we no longer need it. * * Of course it is up to the caller to make sure BlockHeapGarbageCollect() gets * called periodically to do this cleanup, otherwise you'll keep the memory in the * process. * * */ #include "stdinc.h" #include "libcharybdis.h" #define WE_ARE_MEMORY_C #include "setup.h" #include "balloc.h" #ifndef NOBALLOC #include "ircd_defs.h" /* DEBUG_BLOCK_ALLOCATOR */ #include "ircd.h" #include "memory.h" #include "irc_string.h" #include "tools.h" #include "s_log.h" #include "client.h" #include "event.h" #ifdef HAVE_MMAP /* We've got mmap() that is good */ #include /* HP-UX sucks */ #ifdef MAP_ANONYMOUS #ifndef MAP_ANON #define MAP_ANON MAP_ANONYMOUS #endif #endif #endif static int newblock(BlockHeap * bh); static int BlockHeapGarbageCollect(BlockHeap *); static void block_heap_gc(void *unused); static dlink_list heap_lists; #if defined(HAVE_MMAP) && !defined(MAP_ANON) static int zero_fd = -1; #endif #define blockheap_fail(x) _blockheap_fail(x, __FILE__, __LINE__) static void _blockheap_fail(const char *reason, const char *file, int line) { libcharybdis_log("Blockheap failure: %s (%s:%d)", reason, file, line); abort(); } /* * static inline void free_block(void *ptr, size_t size) * * Inputs: The block and its size * Output: None * Side Effects: Returns memory for the block back to the OS */ static inline void free_block(void *ptr, size_t size) { #ifdef HAVE_MMAP munmap(ptr, size); #else free(ptr); #endif } #ifdef DEBUG_BALLOC /* Check the list length the very slow way */ static unsigned long slow_list_length(dlink_list *list) { dlink_node *ptr; unsigned long count = 0; for (ptr = list->head; ptr; ptr = ptr->next) { count++; if(count > list->length * 2) { blockheap_fail("count > list->length * 2 - I give up"); } } return count; } static void bh_sanity_check_block(BlockHeap *bh, Block *block) { unsigned long s_used, s_free; s_used = slow_list_length(&block->used_list); s_free = slow_list_length(&block->free_list); if(s_used != dlink_list_length(&block->used_list)) blockheap_fail("used link count doesn't match head count"); if(s_free != dlink_list_length(&block->free_list)) blockheap_fail("free link count doesn't match head count"); if(dlink_list_length(&block->used_list) + dlink_list_length(&block->free_list) != bh->elemsPerBlock) blockheap_fail("used_list + free_list != elemsPerBlock"); } #if 0 /* See how confused we are */ static void bh_sanity_check(BlockHeap *bh) { Block *walker; unsigned long real_alloc = 0; unsigned long s_used, s_free; unsigned long blockcount = 0; unsigned long allocated; if(bh == NULL) blockheap_fail("Trying to sanity check a NULL block"); allocated = bh->blocksAllocated * bh->elemsPerBlock; for(walker = bh->base; walker != NULL; walker = walker->next) { blockcount++; s_used = slow_list_length(&walker->used_list); s_free = slow_list_length(&walker->free_list); if(s_used != dlink_list_length(&walker->used_list)) blockheap_fail("used link count doesn't match head count"); if(s_free != dlink_list_length(&walker->free_list)) blockheap_fail("free link count doesn't match head count"); if(dlink_list_length(&walker->used_list) + dlink_list_length(&walker->free_list) != bh->elemsPerBlock) blockheap_fail("used_list + free_list != elemsPerBlock"); real_alloc += dlink_list_length(&walker->used_list); real_alloc += dlink_list_length(&walker->free_list); } if(allocated != real_alloc) blockheap_fail("block allocations don't match heap"); if(bh->blocksAllocated != blockcount) blockheap_fail("blocksAllocated don't match blockcount"); } static void bh_sanity_check_all(void *unused) { dlink_node *ptr; DLINK_FOREACH(ptr, heap_lists.head) { bh_sanity_check(ptr->data); } } #endif #endif /* * void initBlockHeap(void) * * Inputs: None * Outputs: None * Side Effects: Initializes the block heap */ void initBlockHeap(void) { #if defined(HAVE_MMAP) && !defined(MAP_ANON) zero_fd = open("/dev/zero", O_RDWR); if(zero_fd < 0) blockheap_fail("Failed opening /dev/zero"); comm_socket(zero_fd, FD_FILE, "Anonymous mmap()"); #endif eventAddIsh("block_heap_gc", block_heap_gc, NULL, 30); } /* * static inline void *get_block(size_t size) * * Input: Size of block to allocate * Output: Pointer to new block * Side Effects: None */ static inline void * get_block(size_t size) { void *ptr; #ifdef HAVE_MMAP #ifdef MAP_ANON ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); #else ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, zero_fd, 0); #endif if(ptr == MAP_FAILED) { ptr = NULL; } #else ptr = malloc(size); #endif return (ptr); } static void block_heap_gc(void *unused) { dlink_node *ptr; DLINK_FOREACH(ptr, heap_lists.head) { BlockHeapGarbageCollect(ptr->data); } } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* newblock */ /* Description: */ /* Allocates a new block for addition to a blockheap */ /* Parameters: */ /* bh (IN): Pointer to parent blockheap. */ /* Returns: */ /* 0 if successful, 1 if not */ /* ************************************************************************ */ static int newblock(BlockHeap * bh) { MemBlock *newblk; Block *b; unsigned long i; void *offset; /* Setup the initial data structure. */ b = (Block *) calloc(1, sizeof(Block)); if(b == NULL) { return (1); } b->free_list.head = b->free_list.tail = NULL; b->used_list.head = b->used_list.tail = NULL; b->next = bh->base; b->alloc_size = (bh->elemsPerBlock + 1) * (bh->elemSize + sizeof(MemBlock)); b->elems = get_block(b->alloc_size); if(b->elems == NULL) { return (1); } offset = b->elems; /* Setup our blocks now */ for (i = 0; i < bh->elemsPerBlock; i++) { void *data; newblk = (void *) offset; newblk->block = b; #ifdef DEBUG_BALLOC newblk->magic = BALLOC_MAGIC; #endif data = (void *) ((size_t) offset + sizeof(MemBlock)); newblk->block = b; dlinkAdd(data, &newblk->self, &b->free_list); offset = (unsigned char *) ((unsigned char *) offset + bh->elemSize + sizeof(MemBlock)); } ++bh->blocksAllocated; bh->freeElems += bh->elemsPerBlock; bh->base = b; return (0); } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* BlockHeapCreate */ /* Description: */ /* Creates a new blockheap from which smaller blocks can be allocated. */ /* Intended to be used instead of multiple calls to malloc() when */ /* performance is an issue. */ /* Parameters: */ /* elemsize (IN): Size of the basic element to be stored */ /* elemsperblock (IN): Number of elements to be stored in a single block */ /* of memory. When the blockheap runs out of free memory, it will */ /* allocate elemsize * elemsperblock more. */ /* Returns: */ /* Pointer to new BlockHeap, or NULL if unsuccessful */ /* ************************************************************************ */ BlockHeap * BlockHeapCreate(size_t elemsize, int elemsperblock) { BlockHeap *bh; s_assert(elemsize > 0 && elemsperblock > 0); /* Catch idiotic requests up front */ if((elemsize <= 0) || (elemsperblock <= 0)) { blockheap_fail("Attempting to BlockHeapCreate idiotic sizes"); } /* Allocate our new BlockHeap */ bh = (BlockHeap *) calloc(1, sizeof(BlockHeap)); if(bh == NULL) { blockheap_fail("Attempt to calloc() failed"); outofmemory(); /* die.. out of memory */ } if((elemsize % sizeof(void *)) != 0) { /* Pad to even pointer boundary */ elemsize += sizeof(void *); elemsize &= ~(sizeof(void *) - 1); } bh->elemSize = elemsize; bh->elemsPerBlock = elemsperblock; bh->blocksAllocated = 0; bh->freeElems = 0; bh->base = NULL; /* Be sure our malloc was successful */ if(newblock(bh)) { if(bh != NULL) free(bh); libcharybdis_restart("Aiee! -- newblock() failed!!!"); } if(bh == NULL) { blockheap_fail("bh == NULL when it shouldn't be"); } dlinkAdd(bh, &bh->hlist, &heap_lists); return (bh); } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* BlockHeapAlloc */ /* Description: */ /* Returns a pointer to a struct within our BlockHeap that's free for */ /* the taking. */ /* Parameters: */ /* bh (IN): Pointer to the Blockheap. */ /* Returns: */ /* Pointer to a structure (void *), or NULL if unsuccessful. */ /* ************************************************************************ */ void * BlockHeapAlloc(BlockHeap * bh) { Block *walker; dlink_node *new_node; s_assert(bh != NULL); if(bh == NULL) { blockheap_fail("Cannot allocate if bh == NULL"); } if(bh->freeElems == 0) { /* Allocate new block and assign */ /* newblock returns 1 if unsuccessful, 0 if not */ if(newblock(bh)) { /* That didn't work..try to garbage collect */ BlockHeapGarbageCollect(bh); if(bh->freeElems == 0) { libcharybdis_restart("newblock() failed and garbage collection didn't help"); } } } for (walker = bh->base; walker != NULL; walker = walker->next) { if(dlink_list_length(&walker->free_list) > 0) { #ifdef DEBUG_BALLOC bh_sanity_check_block(bh, walker); #endif bh->freeElems--; new_node = walker->free_list.head; dlinkMoveNode(new_node, &walker->free_list, &walker->used_list); s_assert(new_node->data != NULL); if(new_node->data == NULL) blockheap_fail("new_node->data is NULL and that shouldn't happen!!!"); memset(new_node->data, 0, bh->elemSize); #ifdef DEBUG_BALLOC do { struct MemBlock *memblock = (void *) ((size_t) new_node->data - sizeof(MemBlock)); if(memblock->magic == BALLOC_FREE_MAGIC) memblock->magic = BALLOC_MAGIC; } while(0); bh_sanity_check_block(bh, walker); #endif return (new_node->data); } } blockheap_fail("BlockHeapAlloc failed, giving up"); return NULL; } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* BlockHeapFree */ /* Description: */ /* Returns an element to the free pool, does not free() */ /* Parameters: */ /* bh (IN): Pointer to BlockHeap containing element */ /* ptr (in): Pointer to element to be "freed" */ /* Returns: */ /* 0 if successful, 1 if element not contained within BlockHeap. */ /* ************************************************************************ */ int BlockHeapFree(BlockHeap * bh, void *ptr) { Block *block; struct MemBlock *memblock; s_assert(bh != NULL); s_assert(ptr != NULL); if(bh == NULL) { libcharybdis_restart("balloc.c:BlockHeapFree() bh == NULL"); return (1); } if(ptr == NULL) { libcharybdis_restart("balloc.BlockHeapFree() ptr == NULL"); return (1); } memblock = (void *) ((size_t) ptr - sizeof(MemBlock)); #ifdef DEBUG_BALLOC if(memblock->magic == BALLOC_FREE_MAGIC) { blockheap_fail("double free of a block"); outofmemory(); } else if(memblock->magic != BALLOC_MAGIC) { blockheap_fail("memblock->magic != BALLOC_MAGIC"); outofmemory(); } #endif s_assert(memblock->block != NULL); if(memblock->block == NULL) { blockheap_fail("memblock->block == NULL, not a valid block?"); outofmemory(); } block = memblock->block; #ifdef DEBUG_BALLOC bh_sanity_check_block(bh, block); #endif bh->freeElems++; mem_frob(ptr, bh->elemSize); dlinkMoveNode(&memblock->self, &block->used_list, &block->free_list); #ifdef DEBUG_BALLOC bh_sanity_check_block(bh, block); #endif return (0); } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* BlockHeapGarbageCollect */ /* Description: */ /* Performs garbage collection on the block heap. Any blocks that are */ /* completely unallocated are removed from the heap. Garbage collection */ /* will never remove the root node of the heap. */ /* Parameters: */ /* bh (IN): Pointer to the BlockHeap to be cleaned up */ /* Returns: */ /* 0 if successful, 1 if bh == NULL */ /* ************************************************************************ */ static int BlockHeapGarbageCollect(BlockHeap * bh) { Block *walker, *last; if(bh == NULL) { return (1); } if(bh->freeElems < bh->elemsPerBlock || bh->blocksAllocated == 1) { /* There couldn't possibly be an entire free block. Return. */ return (0); } last = NULL; walker = bh->base; while (walker != NULL) { if((dlink_list_length(&walker->free_list) == bh->elemsPerBlock) != 0) { free_block(walker->elems, walker->alloc_size); if(last != NULL) { last->next = walker->next; if(walker != NULL) free(walker); walker = last->next; } else { bh->base = walker->next; if(walker != NULL) free(walker); walker = bh->base; } bh->blocksAllocated--; bh->freeElems -= bh->elemsPerBlock; } else { last = walker; walker = walker->next; } } return (0); } /* ************************************************************************ */ /* FUNCTION DOCUMENTATION: */ /* BlockHeapDestroy */ /* Description: */ /* Completely free()s a BlockHeap. Use for cleanup. */ /* Parameters: */ /* bh (IN): Pointer to the BlockHeap to be destroyed. */ /* Returns: */ /* 0 if successful, 1 if bh == NULL */ /* ************************************************************************ */ int BlockHeapDestroy(BlockHeap * bh) { Block *walker, *next; if(bh == NULL) { return (1); } for (walker = bh->base; walker != NULL; walker = next) { next = walker->next; free_block(walker->elems, walker->alloc_size); if(walker != NULL) free(walker); } dlinkDelete(&bh->hlist, &heap_lists); free(bh); return (0); } void BlockHeapUsage(BlockHeap * bh, size_t * bused, size_t * bfree, size_t * bmemusage) { size_t used; size_t freem; size_t memusage; if(bh == NULL) { return; } freem = bh->freeElems; used = (bh->blocksAllocated * bh->elemsPerBlock) - bh->freeElems; memusage = used * (bh->elemSize + sizeof(MemBlock)); if(bused != NULL) *bused = used; if(bfree != NULL) *bfree = freem; if(bmemusage != NULL) *bmemusage = memusage; } #endif /* NOBALLOC */