#include #include #include "hurdmalloc.h" /* XXX see that file */ #include #define vm_allocate __vm_allocate #define vm_page_size __vm_page_size /* * Mach Operating System * Copyright (c) 1991,1990,1989 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * (pre-GNU) HISTORY * * Revision 2.7 91/05/14 17:57:34 mrt * Correcting copyright * * Revision 2.6 91/02/14 14:20:26 mrt * Added new Mach copyright * [91/02/13 12:41:21 mrt] * * Revision 2.5 90/11/05 14:37:33 rpd * Added malloc_fork* code. * [90/11/02 rwd] * * Add spin_lock_t. * [90/10/31 rwd] * * Revision 2.4 90/08/07 14:31:28 rpd * Removed RCS keyword nonsense. * * Revision 2.3 90/06/02 15:14:00 rpd * Converted to new IPC. * [90/03/20 20:56:57 rpd] * * Revision 2.2 89/12/08 19:53:59 rwd * Removed conditionals. * [89/10/23 rwd] * * Revision 2.1 89/08/03 17:09:46 rwd * Created. * * * 13-Sep-88 Eric Cooper (ecc) at Carnegie Mellon University * Changed realloc() to copy min(old size, new size) bytes. * Bug found by Mike Kupfer at Olivetti. */ /* * File: malloc.c * Author: Eric Cooper, Carnegie Mellon University * Date: July, 1988 * * Memory allocator for use with multiple threads. */ #include #include #define MCHECK /* * Structure of memory block header. * When free, next points to next block on free list. * When allocated, fl points to free list. * Size of header is 4 bytes, so minimum usable block size is 8 bytes. */ #define CHECK_BUSY 0x8a3c743e #define CHECK_FREE 0x66688b92 #ifdef MCHECK typedef struct header { long check; union { struct header *next; struct free_list *fl; } u; } *header_t; #define HEADER_SIZE sizeof (struct header) #define HEADER_NEXT(h) ((h)->u.next) #define HEADER_FREE(h) ((h)->u.fl) #define HEADER_CHECK(h) ((h)->check) #define MIN_SIZE 16 #define LOG2_MIN_SIZE 4 #else /* ! MCHECK */ typedef union header { union header *next; struct free_list *fl; } *header_t; #define HEADER_SIZE sizeof (union header) #define HEADER_NEXT(h) ((h)->next) #define HEADER_FREE(h) ((h)->fl) #define MIN_SIZE 8 /* minimum block size */ #define LOG2_MIN_SIZE 3 #endif /* MCHECK */ typedef struct free_list { spin_lock_t lock; /* spin lock for mutual exclusion */ header_t head; /* head of free list for this size */ #ifdef DEBUG int in_use; /* # mallocs - # frees */ #endif /* DEBUG */ } *free_list_t; /* * Free list with index i contains blocks of size 2 ^ (i + LOG2_MIN_SIZE) * including header. Smallest block size is MIN_SIZE, with MIN_SIZE - * HEADER_SIZE bytes available to user. Size argument to malloc is a signed * integer for sanity checking, so largest block size is 2^31. */ #define NBUCKETS 29 static struct free_list malloc_free_list[NBUCKETS]; /* Initialization just sets everything to zero, but might be necessary on a machine where spin_lock_init does otherwise, and is necessary when running an executable that was written by something like Emacs's unexec. It preserves the values of data variables like malloc_free_list, but does not save the vm_allocate'd space allocated by this malloc. */ static void malloc_init (void) { int i; for (i = 0; i < NBUCKETS; ++i) { spin_lock_init (&malloc_free_list[i].lock); malloc_free_list[i].head = NULL; #ifdef DEBUG malloc_free_list[i].in_use = 0; #endif } /* This not only suppresses a `defined but not used' warning, but it is ABSOLUTELY NECESSARY to avoid the hyperclever compiler from "optimizing out" the entire function! */ (void) &malloc_init; } static void more_memory(int size, free_list_t fl) { int amount; int n; vm_address_t where; header_t h; kern_return_t r; if (size <= vm_page_size) { amount = vm_page_size; n = vm_page_size / size; /* We lose vm_page_size - n*size bytes here. */ } else { amount = size; n = 1; } r = vm_allocate(mach_task_self(), &where, (vm_size_t) amount, TRUE); assert_perror (r); h = (header_t) where; do { HEADER_NEXT (h) = fl->head; #ifdef MCHECK HEADER_CHECK (h) = CHECK_FREE; #endif fl->head = h; h = (header_t) ((char *) h + size); } while (--n != 0); } /* Declaration changed to standard one for GNU. */ void * malloc (size_t size) { int i, n; free_list_t fl; header_t h; if ((int) size < 0) /* sanity check */ return 0; size += HEADER_SIZE; /* * Find smallest power-of-two block size * big enough to hold requested size plus header. */ i = 0; n = MIN_SIZE; while (n < size) { i += 1; n <<= 1; } ASSERT(i < NBUCKETS); fl = &malloc_free_list[i]; spin_lock(&fl->lock); h = fl->head; if (h == 0) { /* * Free list is empty; * allocate more blocks. */ more_memory(n, fl); h = fl->head; if (h == 0) { /* * Allocation failed. */ spin_unlock(&fl->lock); return 0; } } /* * Pop block from free list. */ fl->head = HEADER_NEXT (h); #ifdef MCHECK assert (HEADER_CHECK (h) == CHECK_FREE); HEADER_CHECK (h) = CHECK_BUSY; #endif #ifdef DEBUG fl->in_use += 1; #endif /* DEBUG */ spin_unlock(&fl->lock); /* * Store free list pointer in block header * so we can figure out where it goes * at free() time. */ HEADER_FREE (h) = fl; /* * Return pointer past the block header. */ return ((char *) h) + HEADER_SIZE; } /* Declaration changed to standard one for GNU. */ void free (void *base) { header_t h; free_list_t fl; int i; if (base == 0) return; /* * Find free list for block. */ h = (header_t) (base - HEADER_SIZE); #ifdef MCHECK assert (HEADER_CHECK (h) == CHECK_BUSY); #endif fl = HEADER_FREE (h); i = fl - malloc_free_list; /* * Sanity checks. */ if (i < 0 || i >= NBUCKETS) { ASSERT(0 <= i && i < NBUCKETS); return; } if (fl != &malloc_free_list[i]) { ASSERT(fl == &malloc_free_list[i]); return; } /* * Push block on free list. */ spin_lock(&fl->lock); HEADER_NEXT (h) = fl->head; #ifdef MCHECK HEADER_CHECK (h) = CHECK_FREE; #endif fl->head = h; #ifdef DEBUG fl->in_use -= 1; #endif /* DEBUG */ spin_unlock(&fl->lock); return; } /* Declaration changed to standard one for GNU. */ void * realloc (void *old_base, size_t new_size) { header_t h; free_list_t fl; int i; unsigned int old_size; char *new_base; if (old_base == 0) return malloc (new_size); /* * Find size of old block. */ h = (header_t) (old_base - HEADER_SIZE); #ifdef MCHECK assert (HEADER_CHECK (h) == CHECK_BUSY); #endif fl = HEADER_FREE (h); i = fl - malloc_free_list; /* * Sanity checks. */ if (i < 0 || i >= NBUCKETS) { ASSERT(0 <= i && i < NBUCKETS); return 0; } if (fl != &malloc_free_list[i]) { ASSERT(fl == &malloc_free_list[i]); return 0; } /* * Free list with index i contains blocks of size * 2 ^ (i + * LOG2_MIN_SIZE) including header. */ old_size = (1 << (i + LOG2_MIN_SIZE)) - HEADER_SIZE; if (new_size <= old_size && new_size > (((old_size + HEADER_SIZE) >> 1) - HEADER_SIZE)) /* The new size still fits in the same block, and wouldn't fit in the next smaller block! */ return old_base; /* * Allocate new block, copy old bytes, and free old block. */ new_base = malloc(new_size); if (new_base) memcpy (new_base, old_base, (int) (old_size < new_size ? old_size : new_size)); if (new_base || new_size == 0) /* Free OLD_BASE, but only if the malloc didn't fail. */ free (old_base); return new_base; } #ifdef DEBUG void print_malloc_free_list (void) { int i, size; free_list_t fl; int n; header_t h; int total_used = 0; int total_free = 0; fprintf(stderr, " Size In Use Free Total\n"); for (i = 0, size = MIN_SIZE, fl = malloc_free_list; i < NBUCKETS; i += 1, size <<= 1, fl += 1) { spin_lock(&fl->lock); if (fl->in_use != 0 || fl->head != 0) { total_used += fl->in_use * size; for (n = 0, h = fl->head; h != 0; h = HEADER_NEXT (h), n += 1) ; total_free += n * size; fprintf(stderr, "%10d %10d %10d %10d\n", size, fl->in_use, n, fl->in_use + n); } spin_unlock(&fl->lock); } fprintf(stderr, " all sizes %10d %10d %10d\n", total_used, total_free, total_used + total_free); } #endif /* DEBUG */ static void malloc_fork_prepare(void) /* * Prepare the malloc module for a fork by insuring that no thread is in a * malloc critical section. */ { int i; for (i = 0; i < NBUCKETS; i++) { spin_lock(&malloc_free_list[i].lock); } } static void malloc_fork_parent(void) /* * Called in the parent process after a fork() to resume normal operation. */ { int i; for (i = NBUCKETS-1; i >= 0; i--) { spin_unlock(&malloc_free_list[i].lock); } } static void malloc_fork_child(void) /* * Called in the child process after a fork() to resume normal operation. */ { int i; for (i = NBUCKETS-1; i >= 0; i--) { spin_unlock(&malloc_free_list[i].lock); } } text_set_element (_hurd_fork_prepare_hook, malloc_fork_prepare); text_set_element (_hurd_fork_parent_hook, malloc_fork_parent); text_set_element (_hurd_fork_child_hook, malloc_fork_child); text_set_element (_hurd_preinit_hook, malloc_init);