1 #include <stdlib.h>
2 #include <string.h>
3 #include "set-hooks.h"
4 
5 #include "hurdmalloc.h"		/* XXX see that file */
6 
7 #include <mach.h>
8 #include <mach/spin-lock.h>
9 #define vm_allocate __vm_allocate
10 #define vm_page_size __vm_page_size
11 
12 /*
13  * Mach Operating System
14  * Copyright (c) 1991,1990,1989 Carnegie Mellon University
15  * All Rights Reserved.
16  *
17  * Permission to use, copy, modify and distribute this software and its
18  * documentation is hereby granted, provided that both the copyright
19  * notice and this permission notice appear in all copies of the
20  * software, derivative works or modified versions, and any portions
21  * thereof, and that both notices appear in supporting documentation.
22  *
23  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
24  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
25  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
26  *
27  * Carnegie Mellon requests users of this software to return to
28  *
29  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
30  *  School of Computer Science
31  *  Carnegie Mellon University
32  *  Pittsburgh PA 15213-3890
33  *
34  * any improvements or extensions that they make and grant Carnegie Mellon
35  * the rights to redistribute these changes.
36  */
37 /*
38  * (pre-GNU) HISTORY
39  *
40  * Revision 2.7  91/05/14  17:57:34  mrt
41  * 	Correcting copyright
42  *
43  * Revision 2.6  91/02/14  14:20:26  mrt
44  * 	Added new Mach copyright
45  * 	[91/02/13  12:41:21  mrt]
46  *
47  * Revision 2.5  90/11/05  14:37:33  rpd
48  * 	Added malloc_fork* code.
49  * 	[90/11/02            rwd]
50  *
51  * 	Add spin_lock_t.
52  * 	[90/10/31            rwd]
53  *
54  * Revision 2.4  90/08/07  14:31:28  rpd
55  * 	Removed RCS keyword nonsense.
56  *
57  * Revision 2.3  90/06/02  15:14:00  rpd
58  * 	Converted to new IPC.
59  * 	[90/03/20  20:56:57  rpd]
60  *
61  * Revision 2.2  89/12/08  19:53:59  rwd
62  * 	Removed conditionals.
63  * 	[89/10/23            rwd]
64  *
65  * Revision 2.1  89/08/03  17:09:46  rwd
66  * Created.
67  *
68  *
69  * 13-Sep-88  Eric Cooper (ecc) at Carnegie Mellon University
70  *	Changed realloc() to copy min(old size, new size) bytes.
71  *	Bug found by Mike Kupfer at Olivetti.
72  */
73 /*
74  * 	File: 	malloc.c
75  *	Author: Eric Cooper, Carnegie Mellon University
76  *	Date:	July, 1988
77  *
78  * 	Memory allocator for use with multiple threads.
79  */
80 
81 
82 #include <assert.h>
83 
84 #define MCHECK
85 
86 /*
87  * Structure of memory block header.
88  * When free, next points to next block on free list.
89  * When allocated, fl points to free list.
90  * Size of header is 4 bytes, so minimum usable block size is 8 bytes.
91  */
92 
93 #define CHECK_BUSY  0x8a3c743e
94 #define CHECK_FREE  0x66688b92
95 
96 #ifdef MCHECK
97 
98 typedef struct header {
99   long check;
100   union {
101     struct header *next;
102     struct free_list *fl;
103   } u;
104 } *header_t;
105 
106 #define HEADER_SIZE sizeof (struct header)
107 #define HEADER_NEXT(h) ((h)->u.next)
108 #define HEADER_FREE(h) ((h)->u.fl)
109 #define HEADER_CHECK(h) ((h)->check)
110 #define MIN_SIZE	16
111 #define LOG2_MIN_SIZE	4
112 
113 #else /* ! MCHECK */
114 
115 typedef union header {
116 	union header *next;
117 	struct free_list *fl;
118 } *header_t;
119 
120 #define HEADER_SIZE sizeof (union header)
121 #define HEADER_NEXT(h) ((h)->next)
122 #define HEADER_FREE(h) ((h)->fl)
123 #define MIN_SIZE	8	/* minimum block size */
124 #define LOG2_MIN_SIZE	3
125 
126 #endif /* MCHECK */
127 
128 typedef struct free_list {
129 	spin_lock_t lock;	/* spin lock for mutual exclusion */
130 	header_t head;		/* head of free list for this size */
131 #ifdef	DEBUG
132 	int in_use;		/* # mallocs - # frees */
133 #endif	/* DEBUG */
134 } *free_list_t;
135 
136 /*
137  * Free list with index i contains blocks of size 2 ^ (i + LOG2_MIN_SIZE)
138  * including header.  Smallest block size is MIN_SIZE, with MIN_SIZE -
139  * HEADER_SIZE bytes available to user.  Size argument to malloc is a signed
140  * integer for sanity checking, so largest block size is 2^31.
141  */
142 #define NBUCKETS	29
143 
144 static struct free_list malloc_free_list[NBUCKETS];
145 
146 /* Initialization just sets everything to zero, but might be necessary on a
147    machine where spin_lock_init does otherwise, and is necessary when
148    running an executable that was written by something like Emacs's unexec.
149    It preserves the values of data variables like malloc_free_list, but
150    does not save the vm_allocate'd space allocated by this malloc.  */
151 
152 static void attribute_used_retain
malloc_init(void)153 malloc_init (void)
154 {
155   int i;
156   for (i = 0; i < NBUCKETS; ++i)
157     {
158       spin_lock_init (&malloc_free_list[i].lock);
159       malloc_free_list[i].head = NULL;
160 #ifdef DEBUG
161       malloc_free_list[i].in_use = 0;
162 #endif
163     }
164 }
165 
166 static void
more_memory(int size,free_list_t fl)167 more_memory(int size, free_list_t fl)
168 {
169 	int amount;
170 	int n;
171 	vm_address_t where;
172 	header_t h;
173 	kern_return_t r;
174 
175 	if (size <= vm_page_size) {
176 		amount = vm_page_size;
177 		n = vm_page_size / size;
178 		/* We lose vm_page_size - n*size bytes here.  */
179 	} else {
180 		amount = size;
181 		n = 1;
182 	}
183 
184 	r = vm_allocate(mach_task_self(), &where, (vm_size_t) amount, TRUE);
185 	assert_perror (r);
186 
187 	h = (header_t) where;
188 	do {
189 		HEADER_NEXT (h) = fl->head;
190 #ifdef MCHECK
191 		HEADER_CHECK (h) = CHECK_FREE;
192 #endif
193 		fl->head = h;
194 		h = (header_t) ((char *) h + size);
195 	} while (--n != 0);
196 }
197 
198 /* Declaration changed to standard one for GNU.  */
199 void *
malloc(size_t size)200 malloc (size_t size)
201 {
202 	int i, n;
203 	free_list_t fl;
204 	header_t h;
205 
206 	if ((int) size < 0)		/* sanity check */
207 		return 0;
208 	size += HEADER_SIZE;
209 	/*
210 	 * Find smallest power-of-two block size
211 	 * big enough to hold requested size plus header.
212 	 */
213 	i = 0;
214 	n = MIN_SIZE;
215 	while (n < size) {
216 		i += 1;
217 		n <<= 1;
218 	}
219 	assert(i < NBUCKETS);
220 	fl = &malloc_free_list[i];
221 	spin_lock(&fl->lock);
222 	h = fl->head;
223 	if (h == 0) {
224 		/*
225 		 * Free list is empty;
226 		 * allocate more blocks.
227 		 */
228 		more_memory(n, fl);
229 		h = fl->head;
230 		if (h == 0) {
231 			/*
232 			 * Allocation failed.
233 			 */
234 			spin_unlock(&fl->lock);
235 			return 0;
236 		}
237 	}
238 	/*
239 	 * Pop block from free list.
240 	 */
241 	fl->head = HEADER_NEXT (h);
242 
243 #ifdef MCHECK
244 	assert (HEADER_CHECK (h) == CHECK_FREE);
245 	HEADER_CHECK (h) = CHECK_BUSY;
246 #endif
247 
248 #ifdef	DEBUG
249 	fl->in_use += 1;
250 #endif	/* DEBUG */
251 	spin_unlock(&fl->lock);
252 	/*
253 	 * Store free list pointer in block header
254 	 * so we can figure out where it goes
255 	 * at free() time.
256 	 */
257 	HEADER_FREE (h) = fl;
258 	/*
259 	 * Return pointer past the block header.
260 	 */
261 	return ((char *) h) + HEADER_SIZE;
262 }
263 
264 /* Declaration changed to standard one for GNU.  */
265 void
free(void * base)266 free (void *base)
267 {
268 	header_t h;
269 	free_list_t fl;
270 	int i;
271 
272 	if (base == 0)
273 		return;
274 	/*
275 	 * Find free list for block.
276 	 */
277 	h = (header_t) (base - HEADER_SIZE);
278 
279 #ifdef MCHECK
280 	assert (HEADER_CHECK (h) == CHECK_BUSY);
281 #endif
282 
283 	fl = HEADER_FREE (h);
284 	i = fl - malloc_free_list;
285 	/*
286 	 * Sanity checks.
287 	 */
288 	if (i < 0 || i >= NBUCKETS) {
289 		assert(0 <= i && i < NBUCKETS);
290 		return;
291 	}
292 	if (fl != &malloc_free_list[i]) {
293 		assert(fl == &malloc_free_list[i]);
294 		return;
295 	}
296 	/*
297 	 * Push block on free list.
298 	 */
299 	spin_lock(&fl->lock);
300 	HEADER_NEXT (h) = fl->head;
301 #ifdef MCHECK
302 	HEADER_CHECK (h) = CHECK_FREE;
303 #endif
304 	fl->head = h;
305 #ifdef	DEBUG
306 	fl->in_use -= 1;
307 #endif	/* DEBUG */
308 	spin_unlock(&fl->lock);
309 	return;
310 }
311 
312 /* Declaration changed to standard one for GNU.  */
313 void *
realloc(void * old_base,size_t new_size)314 realloc (void *old_base, size_t new_size)
315 {
316 	header_t h;
317 	free_list_t fl;
318 	int i;
319 	unsigned int old_size;
320 	char *new_base;
321 
322 	if (old_base == 0)
323 	  return malloc (new_size);
324 
325 	/*
326 	 * Find size of old block.
327 	 */
328 	h = (header_t) (old_base - HEADER_SIZE);
329 #ifdef MCHECK
330 	assert (HEADER_CHECK (h) == CHECK_BUSY);
331 #endif
332 	fl = HEADER_FREE (h);
333 	i = fl - malloc_free_list;
334 	/*
335 	 * Sanity checks.
336 	 */
337 	if (i < 0 || i >= NBUCKETS) {
338 		assert(0 <= i && i < NBUCKETS);
339 		return 0;
340 	}
341 	if (fl != &malloc_free_list[i]) {
342 		assert(fl == &malloc_free_list[i]);
343 		return 0;
344 	}
345 	/*
346 	 * Free list with index i contains blocks of size
347 	 * 2 ^ (i + * LOG2_MIN_SIZE) including header.
348 	 */
349 	old_size = (1 << (i + LOG2_MIN_SIZE)) - HEADER_SIZE;
350 
351 	if (new_size <= old_size
352 	    && new_size > (((old_size + HEADER_SIZE) >> 1) - HEADER_SIZE))
353 	  /* The new size still fits in the same block, and wouldn't fit in
354 	     the next smaller block!  */
355 	  return old_base;
356 
357 	/*
358 	 * Allocate new block, copy old bytes, and free old block.
359 	 */
360 	new_base = malloc(new_size);
361 	if (new_base)
362 	  memcpy (new_base, old_base,
363 		  (int) (old_size < new_size ? old_size : new_size));
364 
365 	if (new_base || new_size == 0)
366 	  /* Free OLD_BASE, but only if the malloc didn't fail.  */
367 	  free (old_base);
368 
369 	return new_base;
370 }
371 
372 #ifdef	DEBUG
373 void
print_malloc_free_list(void)374 print_malloc_free_list (void)
375 {
376 	int i, size;
377 	free_list_t fl;
378 	int n;
379 	header_t h;
380 	int total_used = 0;
381 	int total_free = 0;
382 
383 	fprintf(stderr, "      Size     In Use       Free      Total\n");
384 	for (i = 0, size = MIN_SIZE, fl = malloc_free_list;
385 	     i < NBUCKETS;
386 	     i += 1, size <<= 1, fl += 1) {
387 		spin_lock(&fl->lock);
388 		if (fl->in_use != 0 || fl->head != 0) {
389 			total_used += fl->in_use * size;
390 			for (n = 0, h = fl->head; h != 0; h = HEADER_NEXT (h), n += 1)
391 				;
392 			total_free += n * size;
393 			fprintf(stderr, "%10d %10d %10d %10d\n",
394 				size, fl->in_use, n, fl->in_use + n);
395 		}
396 		spin_unlock(&fl->lock);
397 	}
398 	fprintf(stderr, " all sizes %10d %10d %10d\n",
399 		total_used, total_free, total_used + total_free);
400 }
401 #endif	/* DEBUG */
402 
403 void
_hurd_malloc_fork_prepare(void)404 _hurd_malloc_fork_prepare(void)
405 /*
406  * Prepare the malloc module for a fork by insuring that no thread is in a
407  * malloc critical section.
408  */
409 {
410     int i;
411 
412     for (i = 0; i < NBUCKETS; i++) {
413 	spin_lock(&malloc_free_list[i].lock);
414     }
415 }
416 
417 void
_hurd_malloc_fork_parent(void)418 _hurd_malloc_fork_parent(void)
419 /*
420  * Called in the parent process after a fork() to resume normal operation.
421  */
422 {
423     int i;
424 
425     for (i = NBUCKETS-1; i >= 0; i--) {
426 	spin_unlock(&malloc_free_list[i].lock);
427     }
428 }
429 
430 void
_hurd_malloc_fork_child(void)431 _hurd_malloc_fork_child(void)
432 /*
433  * Called in the child process after a fork() to resume normal operation.
434  */
435 {
436     int i;
437 
438     for (i = NBUCKETS-1; i >= 0; i--) {
439 	spin_unlock(&malloc_free_list[i].lock);
440     }
441 }
442 
443 
444 SET_RELHOOK (_hurd_preinit_hook, malloc_init);
445