2 * ircd-ratbox: A slightly useful ircd.
3 * balloc.c: A block allocator.
5 * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6 * Copyright (C) 1996-2002 Hybrid Development Team
7 * Copyright (C) 2002-2005 ircd-ratbox development team
10 * Owner: Wohali (Joan Touzet)
12 * Modified 2001/11/29 for mmap() support by Aaron Sethman <androsyn@ratbox.org>
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 * $Id: balloc.c 181 2006-12-18 02:56:07Z beu $
33 * About the block allocator
35 * Basically we have three ways of getting memory off of the operating
36 * system. Below are this list of methods and the order of preference.
38 * 1. mmap() anonymous pages with the MMAP_ANON flag.
39 * 2. mmap() via the /dev/zero trick.
42 * The advantages of 1 and 2 are this. We can munmap() the pages which will
43 * return the pages back to the operating system, thus reducing the size
44 * of the process as the memory is unused. malloc() on many systems just keeps
45 * a heap of memory to itself, which never gets given back to the OS, except on
46 * exit. This of course is bad, if say we have an event that causes us to allocate
47 * say, 200MB of memory, while our normal memory consumption would be 15MB. In the
48 * malloc() case, the amount of memory allocated to our process never goes down, as
49 * malloc() has it locked up in its heap. With the mmap() method, we can munmap()
50 * the block and return it back to the OS, thus causing our memory consumption to go
51 * down after we no longer need it.
53 * Of course it is up to the caller to make sure BlockHeapGarbageCollect() gets
54 * called periodically to do this cleanup, otherwise you'll keep the memory in the
63 #define WE_ARE_MEMORY_C
68 #include "ircd_defs.h" /* DEBUG_BLOCK_ALLOCATOR */
71 #include "irc_string.h"
77 #ifdef HAVE_MMAP /* We've got mmap() that is good */
82 #define MAP_ANON MAP_ANONYMOUS
87 static int newblock(BlockHeap
* bh
);
88 static int BlockHeapGarbageCollect(BlockHeap
*);
89 static void block_heap_gc(void *unused
);
90 static dlink_list heap_lists
;
92 #if defined(HAVE_MMAP) && !defined(MAP_ANON)
93 static int zero_fd
= -1;
96 #define blockheap_fail(x) _blockheap_fail(x, __FILE__, __LINE__)
99 _blockheap_fail(const char *reason
, const char *file
, int line
)
101 libseven_log("Blockheap failure: %s (%s:%d)", reason
, file
, line
);
107 * static inline void free_block(void *ptr, size_t size)
109 * Inputs: The block and its size
111 * Side Effects: Returns memory for the block back to the OS
114 free_block(void *ptr
, size_t size
)
124 /* Check the list length the very slow way */
126 slow_list_length(dlink_list
*list
)
129 unsigned long count
= 0;
131 for (ptr
= list
->head
; ptr
; ptr
= ptr
->next
)
134 if(count
> list
->length
* 2)
136 blockheap_fail("count > list->length * 2 - I give up");
143 bh_sanity_check_block(BlockHeap
*bh
, Block
*block
)
145 unsigned long s_used
, s_free
;
146 s_used
= slow_list_length(&block
->used_list
);
147 s_free
= slow_list_length(&block
->free_list
);
148 if(s_used
!= dlink_list_length(&block
->used_list
))
149 blockheap_fail("used link count doesn't match head count");
150 if(s_free
!= dlink_list_length(&block
->free_list
))
151 blockheap_fail("free link count doesn't match head count");
153 if(dlink_list_length(&block
->used_list
) + dlink_list_length(&block
->free_list
) != bh
->elemsPerBlock
)
154 blockheap_fail("used_list + free_list != elemsPerBlock");
158 /* See how confused we are */
160 bh_sanity_check(BlockHeap
*bh
)
163 unsigned long real_alloc
= 0;
164 unsigned long s_used
, s_free
;
165 unsigned long blockcount
= 0;
166 unsigned long allocated
;
168 blockheap_fail("Trying to sanity check a NULL block");
170 allocated
= bh
->blocksAllocated
* bh
->elemsPerBlock
;
172 for(walker
= bh
->base
; walker
!= NULL
; walker
= walker
->next
)
175 s_used
= slow_list_length(&walker
->used_list
);
176 s_free
= slow_list_length(&walker
->free_list
);
178 if(s_used
!= dlink_list_length(&walker
->used_list
))
179 blockheap_fail("used link count doesn't match head count");
180 if(s_free
!= dlink_list_length(&walker
->free_list
))
181 blockheap_fail("free link count doesn't match head count");
183 if(dlink_list_length(&walker
->used_list
) + dlink_list_length(&walker
->free_list
) != bh
->elemsPerBlock
)
184 blockheap_fail("used_list + free_list != elemsPerBlock");
186 real_alloc
+= dlink_list_length(&walker
->used_list
);
187 real_alloc
+= dlink_list_length(&walker
->free_list
);
190 if(allocated
!= real_alloc
)
191 blockheap_fail("block allocations don't match heap");
193 if(bh
->blocksAllocated
!= blockcount
)
194 blockheap_fail("blocksAllocated don't match blockcount");
200 bh_sanity_check_all(void *unused
)
203 DLINK_FOREACH(ptr
, heap_lists
.head
)
205 bh_sanity_check(ptr
->data
);
215 * void initBlockHeap(void)
219 * Side Effects: Initializes the block heap
225 #if defined(HAVE_MMAP) && !defined(MAP_ANON)
226 zero_fd
= open("/dev/zero", O_RDWR
);
229 blockheap_fail("Failed opening /dev/zero");
230 comm_socket(zero_fd
, FD_FILE
, "Anonymous mmap()");
232 eventAddIsh("block_heap_gc", block_heap_gc
, NULL
, 30);
236 * static inline void *get_block(size_t size)
238 * Input: Size of block to allocate
239 * Output: Pointer to new block
243 get_block(size_t size
)
248 ptr
= mmap(NULL
, size
, PROT_READ
| PROT_WRITE
, MAP_PRIVATE
| MAP_ANON
, -1, 0);
250 ptr
= mmap(NULL
, size
, PROT_READ
| PROT_WRITE
, MAP_PRIVATE
, zero_fd
, 0);
252 if(ptr
== MAP_FAILED
)
264 block_heap_gc(void *unused
)
267 DLINK_FOREACH(ptr
, heap_lists
.head
)
269 BlockHeapGarbageCollect(ptr
->data
);
273 /* ************************************************************************ */
274 /* FUNCTION DOCUMENTATION: */
277 /* Allocates a new block for addition to a blockheap */
279 /* bh (IN): Pointer to parent blockheap. */
281 /* 0 if successful, 1 if not */
282 /* ************************************************************************ */
285 newblock(BlockHeap
* bh
)
292 /* Setup the initial data structure. */
293 b
= (Block
*) calloc(1, sizeof(Block
));
298 b
->free_list
.head
= b
->free_list
.tail
= NULL
;
299 b
->used_list
.head
= b
->used_list
.tail
= NULL
;
302 b
->alloc_size
= (bh
->elemsPerBlock
+ 1) * (bh
->elemSize
+ sizeof(MemBlock
));
304 b
->elems
= get_block(b
->alloc_size
);
310 /* Setup our blocks now */
311 for (i
= 0; i
< bh
->elemsPerBlock
; i
++)
314 newblk
= (void *) offset
;
317 newblk
->magic
= BALLOC_MAGIC
;
319 data
= (void *) ((size_t) offset
+ sizeof(MemBlock
));
321 dlinkAdd(data
, &newblk
->self
, &b
->free_list
);
322 offset
= (unsigned char *) ((unsigned char *) offset
+
323 bh
->elemSize
+ sizeof(MemBlock
));
326 ++bh
->blocksAllocated
;
327 bh
->freeElems
+= bh
->elemsPerBlock
;
334 /* ************************************************************************ */
335 /* FUNCTION DOCUMENTATION: */
336 /* BlockHeapCreate */
338 /* Creates a new blockheap from which smaller blocks can be allocated. */
339 /* Intended to be used instead of multiple calls to malloc() when */
340 /* performance is an issue. */
342 /* elemsize (IN): Size of the basic element to be stored */
343 /* elemsperblock (IN): Number of elements to be stored in a single block */
344 /* of memory. When the blockheap runs out of free memory, it will */
345 /* allocate elemsize * elemsperblock more. */
347 /* Pointer to new BlockHeap, or NULL if unsuccessful */
348 /* ************************************************************************ */
350 BlockHeapCreate(size_t elemsize
, int elemsperblock
)
353 s_assert(elemsize
> 0 && elemsperblock
> 0);
355 /* Catch idiotic requests up front */
356 if((elemsize
<= 0) || (elemsperblock
<= 0))
358 blockheap_fail("Attempting to BlockHeapCreate idiotic sizes");
361 /* Allocate our new BlockHeap */
362 bh
= (BlockHeap
*) calloc(1, sizeof(BlockHeap
));
365 blockheap_fail("Attempt to calloc() failed");
366 outofmemory(); /* die.. out of memory */
369 if((elemsize
% sizeof(void *)) != 0)
371 /* Pad to even pointer boundary */
372 elemsize
+= sizeof(void *);
373 elemsize
&= ~(sizeof(void *) - 1);
376 bh
->elemSize
= elemsize
;
377 bh
->elemsPerBlock
= elemsperblock
;
378 bh
->blocksAllocated
= 0;
382 /* Be sure our malloc was successful */
387 libseven_restart("Aiee! -- newblock() failed!!!");
392 blockheap_fail("bh == NULL when it shouldn't be");
394 dlinkAdd(bh
, &bh
->hlist
, &heap_lists
);
398 /* ************************************************************************ */
399 /* FUNCTION DOCUMENTATION: */
402 /* Returns a pointer to a struct within our BlockHeap that's free for */
405 /* bh (IN): Pointer to the Blockheap. */
407 /* Pointer to a structure (void *), or NULL if unsuccessful. */
408 /* ************************************************************************ */
411 BlockHeapAlloc(BlockHeap
* bh
)
414 dlink_node
*new_node
;
416 s_assert(bh
!= NULL
);
419 blockheap_fail("Cannot allocate if bh == NULL");
422 if(bh
->freeElems
== 0)
424 /* Allocate new block and assign */
425 /* newblock returns 1 if unsuccessful, 0 if not */
429 /* That didn't work..try to garbage collect */
430 BlockHeapGarbageCollect(bh
);
431 if(bh
->freeElems
== 0)
433 libseven_restart("newblock() failed and garbage collection didn't help");
438 for (walker
= bh
->base
; walker
!= NULL
; walker
= walker
->next
)
440 if(dlink_list_length(&walker
->free_list
) > 0)
443 bh_sanity_check_block(bh
, walker
);
446 new_node
= walker
->free_list
.head
;
447 dlinkMoveNode(new_node
, &walker
->free_list
, &walker
->used_list
);
448 s_assert(new_node
->data
!= NULL
);
449 if(new_node
->data
== NULL
)
450 blockheap_fail("new_node->data is NULL and that shouldn't happen!!!");
451 memset(new_node
->data
, 0, bh
->elemSize
);
455 struct MemBlock
*memblock
= (void *) ((size_t) new_node
->data
- sizeof(MemBlock
));
456 if(memblock
->magic
== BALLOC_FREE_MAGIC
)
457 memblock
->magic
= BALLOC_MAGIC
;
460 bh_sanity_check_block(bh
, walker
);
462 return (new_node
->data
);
465 blockheap_fail("BlockHeapAlloc failed, giving up");
470 /* ************************************************************************ */
471 /* FUNCTION DOCUMENTATION: */
474 /* Returns an element to the free pool, does not free() */
476 /* bh (IN): Pointer to BlockHeap containing element */
477 /* ptr (in): Pointer to element to be "freed" */
479 /* 0 if successful, 1 if element not contained within BlockHeap. */
480 /* ************************************************************************ */
482 BlockHeapFree(BlockHeap
* bh
, void *ptr
)
485 struct MemBlock
*memblock
;
487 s_assert(bh
!= NULL
);
488 s_assert(ptr
!= NULL
);
492 libseven_restart("balloc.c:BlockHeapFree() bh == NULL");
498 libseven_restart("balloc.BlockHeapFree() ptr == NULL");
502 memblock
= (void *) ((size_t) ptr
- sizeof(MemBlock
));
504 if(memblock
->magic
== BALLOC_FREE_MAGIC
)
506 blockheap_fail("double free of a block");
509 if(memblock
->magic
!= BALLOC_MAGIC
)
511 blockheap_fail("memblock->magic != BALLOC_MAGIC");
515 s_assert(memblock
->block
!= NULL
);
516 if(memblock
->block
== NULL
)
518 blockheap_fail("memblock->block == NULL, not a valid block?");
522 block
= memblock
->block
;
524 bh_sanity_check_block(bh
, block
);
527 mem_frob(ptr
, bh
->elemSize
);
528 dlinkMoveNode(&memblock
->self
, &block
->used_list
, &block
->free_list
);
530 bh_sanity_check_block(bh
, block
);
535 /* ************************************************************************ */
536 /* FUNCTION DOCUMENTATION: */
537 /* BlockHeapGarbageCollect */
539 /* Performs garbage collection on the block heap. Any blocks that are */
540 /* completely unallocated are removed from the heap. Garbage collection */
541 /* will never remove the root node of the heap. */
543 /* bh (IN): Pointer to the BlockHeap to be cleaned up */
545 /* 0 if successful, 1 if bh == NULL */
546 /* ************************************************************************ */
548 BlockHeapGarbageCollect(BlockHeap
* bh
)
550 Block
*walker
, *last
;
556 if(bh
->freeElems
< bh
->elemsPerBlock
|| bh
->blocksAllocated
== 1)
558 /* There couldn't possibly be an entire free block. Return. */
565 while (walker
!= NULL
)
567 if((dlink_list_length(&walker
->free_list
) == bh
->elemsPerBlock
) != 0)
569 free_block(walker
->elems
, walker
->alloc_size
);
572 last
->next
= walker
->next
;
579 bh
->base
= walker
->next
;
584 bh
->blocksAllocated
--;
585 bh
->freeElems
-= bh
->elemsPerBlock
;
590 walker
= walker
->next
;
596 /* ************************************************************************ */
597 /* FUNCTION DOCUMENTATION: */
598 /* BlockHeapDestroy */
600 /* Completely free()s a BlockHeap. Use for cleanup. */
602 /* bh (IN): Pointer to the BlockHeap to be destroyed. */
604 /* 0 if successful, 1 if bh == NULL */
605 /* ************************************************************************ */
607 BlockHeapDestroy(BlockHeap
* bh
)
609 Block
*walker
, *next
;
616 for (walker
= bh
->base
; walker
!= NULL
; walker
= next
)
619 free_block(walker
->elems
, walker
->alloc_size
);
623 dlinkDelete(&bh
->hlist
, &heap_lists
);
629 BlockHeapUsage(BlockHeap
* bh
, size_t * bused
, size_t * bfree
, size_t * bmemusage
)
639 freem
= bh
->freeElems
;
640 used
= (bh
->blocksAllocated
* bh
->elemsPerBlock
) - bh
->freeElems
;
641 memusage
= used
* (bh
->elemSize
+ sizeof(MemBlock
));
647 if(bmemusage
!= NULL
)
648 *bmemusage
= memusage
;
651 #endif /* NOBALLOC */