mirror of
https://github.com/matrix-construct/construct
synced 2024-11-16 23:10:54 +01:00
212380e3f4
+ branches/release-2.1 -> 2.2 base + 3.0 -> branches/cxxconversion + backport some immediate 3.0 functionality for 2.2 + other stuff
652 lines
18 KiB
C
652 lines
18 KiB
C
/*
|
|
* 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 <androsyn@ratbox.org>
|
|
*
|
|
* 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 <sys/mman.h>
|
|
/* 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 */
|
|
|