2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
25 #include <vm/vm_kern.h>
26 #include <kern/queue.h>
29 #include <libsa/malloc.h>
34 /*********************************************************************
35 * I'm not sure this is really necessary....
36 *********************************************************************/
37 static inline size_t round_to_long(size_t size
) {
38 return (size
+ sizeof(long int)) & ~(sizeof(long int) - 1);
42 typedef struct queue_entry queue_entry
;
44 /*********************************************************************
45 * Structure for an allocation region. Each one is created using
46 * kmem_alloc(), and the whole list of these is destroyed by calling
47 * malloc_reset(). Client blocks are allocated from a linked list of these
48 * regions, on a first-fit basis, and are never freed.
49 *********************************************************************/
50 typedef struct malloc_region
{
51 queue_entry links
; // Uses queue.h for linked list
52 vm_size_t region_size
; // total size w/ this bookeeping info
54 queue_entry block_list
; // list of allocated blocks; uses queue.h
56 vm_size_t free_size
; // size of unused area
57 void * free_address
; // points at the unused area
59 char buffer
[0]; // beginning of useable area
63 /*********************************************************************
64 * Structure for a client memory block. Contains linked-list pointers,
65 * a size field giving the TOTAL size of the block, including this
66 * header, and the address of the client's block. The client block
67 * field is guaranteed to lie on a 16-byte boundary.
68 *********************************************************************/
69 typedef struct malloc_block
{
70 queue_entry links
; // Uses queue.h for linked list
71 malloc_region
* region
;
74 #endif /* CLIENT_DEBUG */
75 size_t block_size
; // total size w/ all bookeeping info
77 // the client's memory block
78 char buffer
[0] __attribute__((aligned(16)));
82 /*********************************************************************
85 * malloc_create_region()
86 * size - The size in bytes of the region. This is rounded up
87 * to a multiple of the VM page size.
88 * Returns a pointer to the new region.
90 * malloc_free_region()
91 * region - The region to free.
92 * Returns whatever vm_deallocate() returns.
94 * malloc_create_block_in_region()
95 * region - The region to alloate a block from.
96 * size - The total size, including the header, of the block to
98 * Returns a pointer to the block, or NULL on failure.
100 * malloc_find_block()
101 * address - The address of the client buffer to find a block for.
102 * block (out) - The block header for the address.
103 * region (out) - The region the block was found in, or NULL.
104 *********************************************************************/
105 static malloc_region
* malloc_create_region(vm_size_t size
);
106 static kern_return_t
malloc_free_region(malloc_region
* region
);
107 static malloc_block
* malloc_create_block_in_region(
108 malloc_region
* region
,
110 static void malloc_find_block(
112 malloc_block
** block
,
113 malloc_region
** region
);
114 static void malloc_get_free_block(
116 malloc_block
** block
,
117 malloc_region
** region
);
120 /*********************************************************************
121 * Pointers to the linked list of VM-allocated regions, and a high
122 * water mark used in testing/debugging.
123 *********************************************************************/
124 static queue_entry malloc_region_list
= {
125 &malloc_region_list
, // the "next" field
126 &malloc_region_list
// the "prev" field
129 static queue_entry sorted_free_block_list
= {
130 &sorted_free_block_list
,
131 &sorted_free_block_list
135 static size_t malloc_hiwater_mark
= 0;
136 static long int num_regions
= 0;
138 static size_t current_block_total
= 0;
139 static double peak_usage
= 0.0;
140 static double min_usage
= 100.0;
141 #endif /* CLIENT_DEBUG */
144 /*********************************************************************
146 *********************************************************************/
148 void * malloc(size_t size
) {
150 malloc_region
* cur_region
= NULL
;
151 malloc_region
* use_region
= NULL
;
152 malloc_block
* client_block
= NULL
;
153 void * client_buffer
= NULL
;
155 /* Add the size of the block header to the request size.
157 need_size
= round_to_long(size
+ sizeof(malloc_block
));
160 /* See if there's a previously-freed block that we can reuse.
162 malloc_get_free_block(need_size
,
163 &client_block
, &use_region
);
166 /* If we found a free block that we can reuse, then reuse it.
168 if (client_block
!= NULL
) {
170 /* Remove the found block from the list of free blocks
171 * and tack it onto the list of allocated blocks.
173 queue_remove(&sorted_free_block_list
, client_block
, malloc_block
*, links
);
174 queue_enter(&use_region
->block_list
, client_block
, malloc_block
*, links
);
176 client_buffer
= client_block
->buffer
;
177 // Don't return here! There's bookkeeping done below.
181 /* Didn't find a freed block to reuse. */
183 /* Look for a region with enough unused space to carve out a new block.
185 queue_iterate(&malloc_region_list
, cur_region
, malloc_region
*, links
) {
186 if (use_region
== NULL
&& cur_region
->free_size
>= need_size
) {
187 use_region
= cur_region
;
193 /* If we haven't found a region with room, create a new one and
194 * put it at the end of the list of regions.
196 if (use_region
== NULL
) {
197 use_region
= malloc_create_region(need_size
);
198 if (use_region
== NULL
) {
204 /* Create a new block in the found/created region.
206 client_block
= malloc_create_block_in_region(use_region
, need_size
);
207 if (client_block
!= NULL
) {
208 client_buffer
= client_block
->buffer
;
209 // Don't return here! There's bookkeeping done below.
214 if (client_block
!= NULL
) {
215 size_t region_usage
= malloc_region_usage();
216 double current_usage
;
218 current_block_total
+= client_block
->block_size
;
219 if (region_usage
> 0) {
220 current_usage
= (double)current_block_total
/ (double)malloc_region_usage();
221 if (current_usage
> peak_usage
) {
222 peak_usage
= current_usage
;
225 if (current_usage
< min_usage
) {
226 min_usage
= current_usage
;
230 client_block
->request_size
= size
;
232 #endif /* CLIENT_DEBUG */
234 return client_buffer
;
239 /*********************************************************************
242 * Moves a block from the allocated list to the free list. Neither
243 * list is kept sorted!
244 *********************************************************************/
246 void free(void * address
) {
247 malloc_region
* found_region
= NULL
;
248 malloc_block
* found_block
= NULL
;
249 malloc_block
* cur_block
= NULL
;
251 /* Find the block and region for the given address.
253 malloc_find_block(address
, &found_block
, &found_region
);
255 if (found_block
== NULL
) {
261 /* Remove the found block from the list of allocated blocks
262 * and tack it onto the list of free blocks.
264 queue_remove(&found_region
->block_list
, found_block
, malloc_block
*, links
);
266 found_block
->links
.next
= NULL
;
267 queue_iterate(&sorted_free_block_list
, cur_block
, malloc_block
*, links
) {
268 if (cur_block
->block_size
> found_block
->block_size
) {
269 queue_insert_before(&sorted_free_block_list
, found_block
, cur_block
,
270 malloc_block
*, links
);
276 /* If the "next" link is still NULL, then either the list is empty or the
277 * freed block has to go on the end, so just tack it on.
279 if (found_block
->links
.next
== NULL
) {
280 queue_enter(&sorted_free_block_list
, found_block
, malloc_block
*, links
);
285 current_block_total
-= found_block
->block_size
;
286 #endif /* CLIENT_DEBUG */
293 /*********************************************************************
296 * Walks through the list of VM-allocated regions, destroying them
297 * all. Any subsequent access by clients to allocated data will cause
298 * a segmentation fault.
299 *********************************************************************/
301 void malloc_reset(void) {
302 malloc_region
* cur_region
;
304 while (! queue_empty(&malloc_region_list
)) {
305 kern_return_t kern_result
;
306 queue_remove_first(&malloc_region_list
, cur_region
,
307 malloc_region
*, links
);
308 kern_result
= malloc_free_region(cur_region
);
309 if (kern_result
!= KERN_SUCCESS
) {
310 // what sort of error checking can we even do here?
311 // printf("malloc_free_region() failed.\n");
318 } /* malloc_reset() */
321 /*********************************************************************
324 * This function simply allocates a new block and copies the existing
325 * data into it. Nothing too clever here, as cleanup and efficient
326 * memory usage are not important in this allocator package.
327 *********************************************************************/
329 void * realloc(void * address
, size_t new_client_size
) {
330 malloc_region
* found_region
= NULL
;
331 malloc_block
* found_block
= NULL
;
333 size_t new_block_size
;
334 size_t copy_bytecount
;
337 malloc_find_block(address
, &found_block
, &found_region
);
340 /* If we couldn't find the requested block,
341 * the caller is in error so return NULL.
343 if (found_block
== NULL
) {
344 // printf("realloc() called with invalid block.\n");
350 /* Figure out how much memory is actually needed.
352 new_block_size
= new_client_size
+ sizeof(malloc_block
);
355 /* If the new size is <= the current size, don't bother.
357 if (new_block_size
<= found_block
->block_size
) {
359 if (new_client_size
> found_block
->request_size
) {
360 found_block
->request_size
= new_client_size
;
362 #endif /* CLIENT_DEBUG */
367 /* Create a new block of the requested size.
369 new_address
= malloc(new_client_size
);
371 if (new_address
== NULL
) {
372 // printf("error in realloc()\n");
378 /* Copy the data from the old block to the new one.
379 * Make sure to copy only the lesser of the existing and
380 * requested new size. (Note: The code above currently
381 * screens out a realloc to a smaller size, but it might
382 * not always do that.)
384 copy_bytecount
= found_block
->block_size
- sizeof(malloc_block
);
386 if (new_client_size
< copy_bytecount
) {
387 copy_bytecount
= new_client_size
;
390 memcpy(new_address
, address
, copy_bytecount
);
393 /* Free the old block.
397 return (void *)new_address
;
402 /*********************************************************************
403 **********************************************************************
404 ***** PACKAGE-INTERNAL FUNCTIONS BELOW HERE *****
405 **********************************************************************
406 *********************************************************************/
410 /*********************************************************************
411 * malloc_create_region()
413 * Package-internal function. VM-allocates a new region and adds it to
414 * the given region list.
415 *********************************************************************/
417 malloc_region
* malloc_create_region(vm_size_t block_size
) {
419 malloc_region
* new_region
;
420 vm_address_t vm_address
;
421 vm_size_t region_size
;
422 kern_return_t kern_result
;
425 /* Figure out how big the region needs to be and allocate it.
427 region_size
= block_size
+ sizeof(malloc_region
);
428 region_size
= round_page(region_size
);
430 kern_result
= kmem_alloc(kernel_map
,
431 &vm_address
, region_size
);
433 if (kern_result
!= KERN_SUCCESS
) {
434 // printf("kmem_alloc() failed in malloc_create_region()\n");
440 /* Cast the allocated pointer to a region header.
442 new_region
= (malloc_region
*)vm_address
;
445 /* Initialize the region header fields and link it onto
446 * the previous region.
448 new_region
->region_size
= region_size
;
449 queue_init(&new_region
->block_list
);
450 // queue_init(&new_region->free_list);
452 new_region
->free_size
= region_size
- sizeof(malloc_region
);
453 new_region
->free_address
= &new_region
->buffer
;
455 queue_enter(&malloc_region_list
, new_region
, malloc_region
*, links
);
457 /* If debugging, add the new region's size to the total.
460 malloc_hiwater_mark
+= region_size
;
462 #endif /* CLIENT_DEBUG */
466 } /* malloc_create_region() */
469 /*********************************************************************
470 * malloc_free_region()
472 * Package-internal function. VM-deallocates the given region.
473 *********************************************************************/
475 kern_return_t
malloc_free_region(malloc_region
* region
) {
477 kmem_free(kernel_map
,
478 (vm_address_t
)region
,
479 region
->region_size
);
483 #endif /* CLIENT_DEBUG */
486 } /* malloc_free_region() */
489 /*********************************************************************
490 * malloc_create_block_in_region()
492 * Package-internal function. Allocates a new block out of the given
493 * region. The requested size must include the block header. If the
494 * size requested is larger than the region's free size, returns NULL.
495 *********************************************************************/
497 malloc_block
* malloc_create_block_in_region(
498 malloc_region
* region
,
501 malloc_block
* new_block
= NULL
;
506 if (block_size
> region
->free_size
) {
512 /* Carve out a new block.
514 new_block
= (malloc_block
*)region
->free_address
;
515 region
->free_address
= (char *)region
->free_address
+ block_size
;
516 region
->free_size
-= block_size
;
518 memset(new_block
, 0, sizeof(malloc_block
));
520 new_block
->region
= region
;
521 new_block
->block_size
= block_size
;
523 /* Record the new block as the last one in the region.
525 queue_enter(®ion
->block_list
, new_block
, malloc_block
*, links
);
529 } /* malloc_create_block_in_region() */
532 /*********************************************************************
533 * malloc_find_block()
535 * Package-internal function. Given a client buffer address, find the
536 * malloc_block for it.
537 *********************************************************************/
539 void malloc_find_block(void * address
,
540 malloc_block
** block
,
541 malloc_region
** region
) {
543 malloc_region
* cur_region
;
548 queue_iterate(&malloc_region_list
, cur_region
, malloc_region
*, links
) {
550 malloc_block
* cur_block
;
552 queue_iterate(&cur_region
->block_list
, cur_block
, malloc_block
*, links
) {
553 if (cur_block
->buffer
== address
) {
555 *region
= cur_region
;
563 } /* malloc_find_block() */
566 /*********************************************************************
567 * malloc_get_free_block()
568 *********************************************************************/
570 void malloc_get_free_block(
572 malloc_block
** block
,
573 malloc_region
** region
) {
575 malloc_block
* cur_block
;
576 size_t fit_threshold
= 512;
581 queue_iterate(&sorted_free_block_list
, cur_block
, malloc_block
*, links
) {
583 /* If we find a block large enough, but not too large to waste memory,
584 * pull it out and return it, along with its region.
586 if (cur_block
->block_size
>= size
&&
587 cur_block
->block_size
< (size
+ fit_threshold
)) {
589 queue_remove(&sorted_free_block_list
, cur_block
, malloc_block
*, links
);
591 *region
= cur_block
->region
;