1 /*
2  *  ircd-ratbox: A slightly useful ircd.
3  *  balloc.c: A block allocator.
4  *
5  * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6  * Copyright (C) 1996-2002 Hybrid Development Team
7  * Copyright (C) 2002-2012 ircd-ratbox development team
8  *
9  *  Below are the orignal headers from the old blalloc.c
10  *
11  *  File:   blalloc.c
12  *  Owner:  Wohali (Joan Touzet)
13  *
14  *  Modified 2001/11/29 for mmap() support by Aaron Sethman <androsyn@ratbox.org>
15  *
16  *  This program is free software; you can redistribute it and/or modify
17  *  it under the terms of the GNU General Public License as published by
18  *  the Free Software Foundation; either version 2 of the License, or
19  *  (at your option) any later version.
20  *
21  *  This program is distributed in the hope that it will be useful,
22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  *  GNU General Public License for more details.
25  *
26  *  You should have received a copy of the GNU General Public License
27  *  along with this program; if not, write to the Free Software
28  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
29  *  USA
30  *
31  *  $Id: balloc.c 27377 2012-03-16 06:29:42Z dubkat $
32  */
33 
34 /*
35  * About the block allocator
36  *
37  * Basically we have three ways of getting memory off of the operating
38  * system. Below are this list of methods and the order of preference.
39  *
40  * 1. mmap() anonymous pages with the MMAP_ANON flag.
41  * 2. mmap() via the /dev/zero trick.
42  * 3. HeapCreate/HeapAlloc (on win32)
43  * 4. malloc()
44  *
45  * The advantages of 1 and 2 are this.  We can munmap() the pages which will
46  * return the pages back to the operating system, thus reducing the size
47  * of the process as the memory is unused.  malloc() on many systems just keeps
48  * a heap of memory to itself, which never gets given back to the OS, except on
49  * exit.  This of course is bad, if say we have an event that causes us to allocate
50  * say, 200MB of memory, while our normal memory consumption would be 15MB.  In the
51  * malloc() case, the amount of memory allocated to our process never goes down, as
52  * malloc() has it locked up in its heap.  With the mmap() method, we can munmap()
53  * the block and return it back to the OS, thus causing our memory consumption to go
54  * down after we no longer need it.
55  *
56  *
57  *
58  */
59 #include <libratbox_config.h>
60 #include <ratbox_lib.h>
61 
62 #ifndef NOBALLOC
63 #ifdef HAVE_MMAP		/* We've got mmap() that is good */
64 #include <sys/mman.h>
65 /* HP-UX sucks */
66 #ifdef MAP_ANONYMOUS
67 #ifndef MAP_ANON
68 #define MAP_ANON MAP_ANONYMOUS
69 #endif
70 #endif
71 #endif
72 #endif
73 
74 static uintptr_t offset_pad;
75 
76 /* status information for an allocated block in heap */
77 struct rb_heap_block
78 {
79 	size_t alloc_size;
80 	rb_dlink_node node;
81 	unsigned long free_count;
82 	void *elems;		/* Points to allocated memory */
83 };
84 typedef struct rb_heap_block rb_heap_block;
85 
86 /* information for the root node of the heap */
87 struct rb_bh
88 {
89 	rb_dlink_node hlist;
90 	size_t elemSize;	/* Size of each element to be stored */
91 	unsigned long elemsPerBlock;	/* Number of elements per block */
92 	rb_dlink_list block_list;
93 	rb_dlink_list free_list;
94 	char *desc;
95 };
96 
97 #ifndef NOBALLOC
98 static int newblock(rb_bh *bh);
99 static void rb_bh_gc_event(void *unused);
100 #endif /* !NOBALLOC */
101 static rb_dlink_list *heap_lists;
102 
103 #if defined(WIN32)
104 static HANDLE block_heap;
105 #endif
106 
107 #define rb_bh_fail(x) _rb_bh_fail(x, __FILE__, __LINE__)
108 
109 static void
_rb_bh_fail(const char * reason,const char * file,int line)110 _rb_bh_fail(const char *reason, const char *file, int line)
111 {
112 	rb_lib_log("rb_heap_blockheap failure: %s (%s:%d)", reason, file, line);
113 	abort();
114 }
115 
116 #ifndef NOBALLOC
117 /*
118  * static inline void free_block(void *ptr, size_t size)
119  *
120  * Inputs: The block and its size
121  * Output: None
122  * Side Effects: Returns memory for the block back to the OS
123  */
124 static inline void
free_block(void * ptr,size_t size)125 free_block(void *ptr, size_t size)
126 {
127 #ifdef HAVE_MMAP
128 	munmap(ptr, size);
129 #else
130 #ifdef _WIN32
131 	HeapFree(block_heap, 0, ptr);
132 #else
133 	free(ptr);
134 #endif
135 #endif
136 }
137 #endif /* !NOBALLOC */
138 
139 /*
140  * void rb_init_bh(void)
141  *
142  * Inputs: None
143  * Outputs: None
144  * Side Effects: Initializes the block heap
145  */
146 
147 void
rb_init_bh(void)148 rb_init_bh(void)
149 {
150 	heap_lists = rb_malloc(sizeof(rb_dlink_list));
151 	offset_pad = sizeof(void *);
152 	/* XXX if you get SIGBUS when trying to use a long long..here is where you need to
153 	 * fix your shit
154 	 */
155 #ifdef __sparc__
156 	if((offset_pad % __alignof__(long long)) != 0)
157 	{
158 		offset_pad += __alignof__(long long);
159 		offset_pad &= ~(__alignof__(long long) - 1);
160 	}
161 #endif
162 
163 #ifndef NOBALLOC
164 #ifdef _WIN32
165 	block_heap = HeapCreate(HEAP_NO_SERIALIZE, 0, 0);
166 #endif
167 	rb_event_addish("rb_bh_gc_event", rb_bh_gc_event, NULL, 300);
168 #endif /* !NOBALLOC */
169 }
170 
171 #ifndef NOBALLOC
172 /*
173  * static inline void *get_block(size_t size)
174  *
175  * Input: Size of block to allocate
176  * Output: Pointer to new block
177  * Side Effects: None
178  */
179 static inline void *
get_block(size_t size)180 get_block(size_t size)
181 {
182 	void *ptr;
183 #ifdef HAVE_MMAP
184 #ifdef MAP_ANON
185 	ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
186 #else
187 	int zero_fd;
188 	zero_fd = open("/dev/zero", O_RDWR);
189 	if(zero_fd < 0)
190 		rb_bh_fail("Failed opening /dev/zero");
191 	ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, zero_fd, 0);
192 	close(zero_fd);
193 #endif /* MAP_ANON */
194 	if(ptr == MAP_FAILED)
195 		ptr = NULL;
196 #else
197 #ifdef _WIN32
198 	ptr = HeapAlloc(block_heap, 0, size);
199 #else
200 	ptr = malloc(size);
201 #endif
202 #endif
203 	return (ptr);
204 }
205 
206 
207 static void
rb_bh_gc_event(void * unused)208 rb_bh_gc_event(void *unused)
209 {
210 	rb_dlink_node *ptr;
211 	RB_DLINK_FOREACH(ptr, heap_lists->head)
212 	{
213 		rb_bh_gc(ptr->data);
214 	}
215 }
216 
217 /* ************************************************************************ */
218 /* FUNCTION DOCUMENTATION:                                                  */
219 /*    newblock                                                              */
220 /* Description:                                                             */
221 /*    Allocates a new block for addition to a blockheap                     */
222 /* Parameters:                                                              */
223 /*    bh (IN): Pointer to parent blockheap.                                 */
224 /* Returns:                                                                 */
225 /*    0 if successful, 1 if not                                             */
226 /* ************************************************************************ */
227 
228 static int
newblock(rb_bh * bh)229 newblock(rb_bh *bh)
230 {
231 	rb_heap_block *b;
232 	unsigned long i;
233 	uintptr_t offset;
234 	rb_dlink_node *node;
235 	/* Setup the initial data structure. */
236 	b = rb_malloc(sizeof(rb_heap_block));
237 
238 	b->alloc_size = bh->elemsPerBlock * bh->elemSize;
239 
240 	b->elems = get_block(b->alloc_size);
241 	if(rb_unlikely(b->elems == NULL))
242 	{
243 		return (1);
244 	}
245 	offset = (uintptr_t)b->elems;
246 	/* Setup our blocks now */
247 	for(i = 0; i < bh->elemsPerBlock; i++, offset += bh->elemSize)
248 	{
249 		*((void **)offset) = b;
250 		node = (void *)(offset + offset_pad);
251 		rb_dlinkAdd((void *)offset, node, &bh->free_list);
252 	}
253 	rb_dlinkAdd(b, &b->node, &bh->block_list);
254 	b->free_count = bh->elemsPerBlock;
255 	return (0);
256 }
257 #endif /* !NOBALLOC */
258 
259 /* ************************************************************************ */
260 /* FUNCTION DOCUMENTATION:                                                  */
261 /*    rb_bh_create                                                       */
262 /* Description:                                                             */
263 /*   Creates a new blockheap from which smaller blocks can be allocated.    */
264 /*   Intended to be used instead of multiple calls to malloc() when         */
265 /*   performance is an issue.                                               */
266 /* Parameters:                                                              */
267 /*   elemsize (IN):  Size of the basic element to be stored                 */
268 /*   elemsperblock (IN):  Number of elements to be stored in a single block */
269 /*         of memory.  When the blockheap runs out of free memory, it will  */
270 /*         allocate elemsize * elemsperblock more.                          */
271 /* Returns:                                                                 */
272 /*   Pointer to new rb_bh, or NULL if unsuccessful                      */
273 /* ************************************************************************ */
274 rb_bh *
rb_bh_create(size_t elemsize,int elemsperblock,const char * desc)275 rb_bh_create(size_t elemsize, int elemsperblock, const char *desc)
276 {
277 	rb_bh *bh;
278 	lrb_assert(elemsize > 0 && elemsperblock > 0);
279 	lrb_assert(elemsize >= sizeof(rb_dlink_node));
280 
281 	/* Catch idiotic requests up front */
282 	if((elemsize == 0) || (elemsperblock <= 0))
283 	{
284 		rb_bh_fail("Attempting to rb_bh_create idiotic sizes");
285 	}
286 
287 	if(elemsize < sizeof(rb_dlink_node))
288 		rb_bh_fail("Attempt to rb_bh_create smaller than sizeof(rb_dlink_node)");
289 
290 	/* Allocate our new rb_bh */
291 	bh = rb_malloc(sizeof(rb_bh));
292 #ifndef NOBALLOC
293 	elemsize += offset_pad;
294 	if((elemsize % sizeof(void *)) != 0)
295 	{
296 		/* Pad to even pointer boundary */
297 		elemsize += sizeof(void *);
298 		elemsize &= ~(sizeof(void *) - 1);
299 	}
300 #endif
301 
302 	bh->elemSize = elemsize;
303 	bh->elemsPerBlock = elemsperblock;
304 	if(desc != NULL)
305 		bh->desc = rb_strdup(desc);
306 
307 #ifndef NOBALLOC
308 	/* Be sure our malloc was successful */
309 	if(newblock(bh))
310 	{
311 		if(bh != NULL)
312 			free(bh);
313 		rb_lib_log("newblock() failed");
314 		rb_outofmemory();	/* die.. out of memory */
315 	}
316 #endif /* !NOBALLOC */
317 
318 	if(bh == NULL)
319 	{
320 		rb_bh_fail("bh == NULL when it shouldn't be");
321 	}
322 	rb_dlinkAdd(bh, &bh->hlist, heap_lists);
323 	return (bh);
324 }
325 
326 /* ************************************************************************ */
327 /* FUNCTION DOCUMENTATION:                                                  */
328 /*    rb_bh_alloc                                                        */
329 /* Description:                                                             */
330 /*    Returns a pointer to a struct within our rb_bh that's free for    */
331 /*    the taking.                                                           */
332 /* Parameters:                                                              */
333 /*    bh (IN):  Pointer to the Blockheap.                                   */
334 /* Returns:                                                                 */
335 /*    Pointer to a structure (void *), or NULL if unsuccessful.             */
336 /* ************************************************************************ */
337 
338 void *
rb_bh_alloc(rb_bh * bh)339 rb_bh_alloc(rb_bh *bh)
340 {
341 #ifndef NOBALLOC
342 	rb_dlink_node *new_node;
343 	rb_heap_block **block;
344 	void *ptr;
345 #endif
346 	lrb_assert(bh != NULL);
347 	if(rb_unlikely(bh == NULL))
348 	{
349 		rb_bh_fail("Cannot allocate if bh == NULL");
350 	}
351 
352 #ifdef NOBALLOC
353 	return (rb_malloc(bh->elemSize));
354 #else
355 	if(bh->free_list.head == NULL)
356 	{
357 		/* Allocate new block and assign */
358 		/* newblock returns 1 if unsuccessful, 0 if not */
359 
360 		if(rb_unlikely(newblock(bh)))
361 		{
362 			rb_lib_log("newblock() failed");
363 			rb_outofmemory();	/* Well that didn't work either...bail */
364 		}
365 		if(bh->free_list.head == NULL)
366 		{
367 			rb_lib_log("out of memory after newblock()...");
368 			rb_outofmemory();
369 		}
370 	}
371 
372 	new_node = bh->free_list.head;
373 	block = (rb_heap_block **) new_node->data;
374 	ptr = (void *)((uintptr_t)new_node->data + (uintptr_t)offset_pad);
375 	rb_dlinkDelete(new_node, &bh->free_list);
376 	(*block)->free_count--;
377 	memset(ptr, 0, bh->elemSize - offset_pad);
378 	return (ptr);
379 #endif
380 }
381 
382 
383 /* ************************************************************************ */
384 /* FUNCTION DOCUMENTATION:                                                  */
385 /*    rb_bh_free                                                          */
386 /* Description:                                                             */
387 /*    Returns an element to the free pool, does not free()                  */
388 /* Parameters:                                                              */
389 /*    bh (IN): Pointer to rb_bh containing element                        */
390 /*    ptr (in):  Pointer to element to be "freed"                           */
391 /* Returns:                                                                 */
392 /*    0 if successful, 1 if element not contained within rb_bh.           */
393 /* ************************************************************************ */
394 int
rb_bh_free(rb_bh * bh,void * ptr)395 rb_bh_free(rb_bh *bh, void *ptr)
396 {
397 #ifndef NOBALLOC
398 	rb_heap_block *block;
399 	void *data;
400 #endif
401 	lrb_assert(bh != NULL);
402 	lrb_assert(ptr != NULL);
403 
404 	if(rb_unlikely(bh == NULL))
405 	{
406 		rb_lib_log("balloc.c:rb_bhFree() bh == NULL");
407 		return (1);
408 	}
409 
410 	if(rb_unlikely(ptr == NULL))
411 	{
412 		rb_lib_log("balloc.rb_bhFree() ptr == NULL");
413 		return (1);
414 	}
415 
416 #ifdef NOBALLOC
417 	rb_free(ptr);
418 #else
419 	data = (void *)((uintptr_t)ptr - (uintptr_t)offset_pad);
420 	block = *(rb_heap_block **) data;
421 	/* XXX */
422 	if(rb_unlikely
423 	   (!((uintptr_t)ptr >= (uintptr_t)block->elems
424 	      && (uintptr_t)ptr < (uintptr_t)block->elems + (uintptr_t)block->alloc_size)))
425 	{
426 		rb_bh_fail("rb_bh_free() bogus pointer");
427 	}
428 	block->free_count++;
429 
430 	rb_dlinkAdd(data, (rb_dlink_node *)ptr, &bh->free_list);
431 #endif /* !NOBALLOC */
432 	return (0);
433 }
434 
435 
436 /* ************************************************************************ */
437 /* FUNCTION DOCUMENTATION:                                                  */
438 /*    rb_bhDestroy                                                      */
439 /* Description:                                                             */
440 /*    Completely free()s a rb_bh.  Use for cleanup.                     */
441 /* Parameters:                                                              */
442 /*    bh (IN):  Pointer to the rb_bh to be destroyed.                   */
443 /* Returns:                                                                 */
444 /*   0 if successful, 1 if bh == NULL                                       */
445 /* ************************************************************************ */
446 int
rb_bh_destroy(rb_bh * bh)447 rb_bh_destroy(rb_bh *bh)
448 {
449 #ifndef NOBALLOC
450 	rb_dlink_node *ptr, *next;
451 	rb_heap_block *b;
452 #endif
453 	if(bh == NULL)
454 		return (1);
455 
456 #ifndef NOBALLOC
457 	RB_DLINK_FOREACH_SAFE(ptr, next, bh->block_list.head)
458 	{
459 		b = ptr->data;
460 		free_block(b->elems, b->alloc_size);
461 		rb_free(b);
462 	}
463 #endif /* !NOBALLOC */
464 
465 	rb_dlinkDelete(&bh->hlist, heap_lists);
466 	rb_free(bh->desc);
467 	rb_free(bh);
468 
469 	return (0);
470 }
471 
472 void
rb_bh_usage(rb_bh * bh,size_t * bused,size_t * bfree,size_t * bmemusage,const char ** desc)473 rb_bh_usage(rb_bh *bh, size_t *bused, size_t *bfree, size_t *bmemusage, const char **desc)
474 {
475 #ifndef NOBALLOC
476 	size_t used, freem, memusage;
477 
478 	if(bh == NULL)
479 	{
480 		return;
481 	}
482 
483 	freem = rb_dlink_list_length(&bh->free_list);
484 	used = (rb_dlink_list_length(&bh->block_list) * bh->elemsPerBlock) - freem;
485 	memusage = used * bh->elemSize;
486 	if(bused != NULL)
487 		*bused = used;
488 	if(bfree != NULL)
489 		*bfree = freem;
490 	if(bmemusage != NULL)
491 		*bmemusage = memusage;
492 	if(desc != NULL)
493 		*desc = bh->desc;
494 #else
495 	static char *noballoc = "no blockheap";
496 	*bused = 0;
497 	*bfree = 0;
498 	*bmemusage = 0;
499 	*desc = noballoc;
500 #endif
501 }
502 
503 void
rb_bh_usage_all(rb_bh_usage_cb * cb,void * data)504 rb_bh_usage_all(rb_bh_usage_cb *cb, void *data)
505 {
506 	rb_dlink_node *ptr;
507 	rb_bh *bh;
508 	size_t used, freem, memusage, heapalloc;
509 	static const char *unnamed = "(unnamed_heap)";
510 	const char *desc = unnamed;
511 
512 	if(cb == NULL)
513 		return;
514 
515 	RB_DLINK_FOREACH(ptr, heap_lists->head)
516 	{
517 		bh = (rb_bh *)ptr->data;
518 		freem = rb_dlink_list_length(&bh->free_list);
519 		used = (rb_dlink_list_length(&bh->block_list) * bh->elemsPerBlock) - freem;
520 		memusage = used * bh->elemSize;
521 		heapalloc = (freem + used) * bh->elemSize;
522 		if(bh->desc != NULL)
523 			desc = bh->desc;
524 		cb(used, freem, memusage, heapalloc, desc, data);
525 	}
526 	return;
527 }
528 
529 void
rb_bh_total_usage(size_t * total_alloc,size_t * total_used)530 rb_bh_total_usage(size_t *total_alloc, size_t *total_used)
531 {
532 	rb_dlink_node *ptr;
533 	size_t total_memory = 0, used_memory = 0, used, freem;
534 	rb_bh *bh;
535 
536 	RB_DLINK_FOREACH(ptr, heap_lists->head)
537 	{
538 		bh = (rb_bh *)ptr->data;
539 		freem = rb_dlink_list_length(&bh->free_list);
540 		used = (rb_dlink_list_length(&bh->block_list) * bh->elemsPerBlock) - freem;
541 		used_memory += used * bh->elemSize;
542 		total_memory += (freem + used) * bh->elemSize;
543 	}
544 
545 	if(total_alloc != NULL)
546 		*total_alloc = total_memory;
547 	if(total_used != NULL)
548 		*total_used = used_memory;
549 }
550 
551 #ifndef NOBALLOC
552 int
rb_bh_gc(rb_bh * bh)553 rb_bh_gc(rb_bh *bh)
554 {
555 	rb_heap_block *b;
556 	rb_dlink_node *ptr, *next;
557 	unsigned long i;
558 	uintptr_t offset;
559 
560 	if(bh == NULL)
561 	{
562 		/* somebody is smoking some craq..(probably lee, but don't tell him that) */
563 		return (1);
564 	}
565 
566 	if((rb_dlink_list_length(&bh->free_list) < bh->elemsPerBlock)
567 	   || rb_dlink_list_length(&bh->block_list) == 1)
568 	{
569 		/* There couldn't possibly be an entire free block.  Return. */
570 		return (0);
571 	}
572 
573 	RB_DLINK_FOREACH_SAFE(ptr, next, bh->block_list.head)
574 	{
575 		b = ptr->data;
576 		if(rb_dlink_list_length(&bh->block_list) == 1)
577 			return (0);
578 
579 		if(b->free_count == bh->elemsPerBlock)
580 		{
581 			/* i'm seriously going to hell for this.. */
582 
583 			offset = (uintptr_t)b->elems;
584 			for(i = 0; i < bh->elemsPerBlock; i++, offset += (uintptr_t)bh->elemSize)
585 			{
586 				rb_dlinkDelete((rb_dlink_node *)(offset + offset_pad),
587 					       &bh->free_list);
588 			}
589 			rb_dlinkDelete(&b->node, &bh->block_list);
590 			free_block(b->elems, b->alloc_size);
591 			rb_free(b);
592 		}
593 
594 	}
595 	return (0);
596 }
597 #endif /* !NOBALLOC */
598