source: mainline/uspace/lib/c/generic/malloc.c@ 64d2b10

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 64d2b10 was 3292623, checked in by Martin Decky <martin@…>, 15 years ago

thread-safe heap allocator

  • Property mode set to 100644
File size: 14.8 KB
Line 
1/*
2 * Copyright (c) 2009 Martin Decky
3 * Copyright (c) 2009 Petr Tuma
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup libc
31 * @{
32 */
33/** @file
34 */
35
36#include <malloc.h>
37#include <bool.h>
38#include <as.h>
39#include <align.h>
40#include <macros.h>
41#include <assert.h>
42#include <errno.h>
43#include <bitops.h>
44#include <mem.h>
45#include <futex.h>
46#include <adt/gcdlcm.h>
47
48/* Magic used in heap headers. */
49#define HEAP_BLOCK_HEAD_MAGIC 0xBEEF0101
50
51/* Magic used in heap footers. */
52#define HEAP_BLOCK_FOOT_MAGIC 0xBEEF0202
53
54/** Allocation alignment (this also covers the alignment of fields
55 in the heap header and footer) */
56#define BASE_ALIGN 16
57
58/**
59 * Either 4 * 256M on 32-bit architecures or 16 * 256M on 64-bit architectures
60 */
61#define MAX_HEAP_SIZE (sizeof(uintptr_t) << 28)
62
63/**
64 *
65 */
66#define STRUCT_OVERHEAD (sizeof(heap_block_head_t) + sizeof(heap_block_foot_t))
67
68/**
69 * Calculate real size of a heap block (with header and footer)
70 */
71#define GROSS_SIZE(size) ((size) + STRUCT_OVERHEAD)
72
73/**
74 * Calculate net size of a heap block (without header and footer)
75 */
76#define NET_SIZE(size) ((size) - STRUCT_OVERHEAD)
77
78/** Header of a heap block
79 *
80 */
81typedef struct {
82 /* Size of the block (including header and footer) */
83 size_t size;
84
85 /* Indication of a free block */
86 bool free;
87
88 /* A magic value to detect overwrite of heap header */
89 uint32_t magic;
90} heap_block_head_t;
91
92/** Footer of a heap block
93 *
94 */
95typedef struct {
96 /* Size of the block (including header and footer) */
97 size_t size;
98
99 /* A magic value to detect overwrite of heap footer */
100 uint32_t magic;
101} heap_block_foot_t;
102
103/** Linker heap symbol */
104extern char _heap;
105
106/** Futex for thread-safe heap manipulation */
107static futex_t malloc_futex = FUTEX_INITIALIZER;
108
109/** Address of heap start */
110static void *heap_start = 0;
111
112/** Address of heap end */
113static void *heap_end = 0;
114
115/** Maximum heap size */
116static size_t max_heap_size = (size_t) -1;
117
118/** Current number of pages of heap area */
119static size_t heap_pages = 0;
120
121/** Initialize a heap block
122 *
123 * Fills in the structures related to a heap block.
124 * Should be called only inside the critical section.
125 *
126 * @param addr Address of the block.
127 * @param size Size of the block including the header and the footer.
128 * @param free Indication of a free block.
129 *
130 */
131static void block_init(void *addr, size_t size, bool free)
132{
133 /* Calculate the position of the header and the footer */
134 heap_block_head_t *head = (heap_block_head_t *) addr;
135 heap_block_foot_t *foot =
136 (heap_block_foot_t *) (addr + size - sizeof(heap_block_foot_t));
137
138 head->size = size;
139 head->free = free;
140 head->magic = HEAP_BLOCK_HEAD_MAGIC;
141
142 foot->size = size;
143 foot->magic = HEAP_BLOCK_FOOT_MAGIC;
144}
145
146/** Check a heap block
147 *
148 * Verifies that the structures related to a heap block still contain
149 * the magic constants. This helps detect heap corruption early on.
150 * Should be called only inside the critical section.
151 *
152 * @param addr Address of the block.
153 *
154 */
155static void block_check(void *addr)
156{
157 heap_block_head_t *head = (heap_block_head_t *) addr;
158
159 assert(head->magic == HEAP_BLOCK_HEAD_MAGIC);
160
161 heap_block_foot_t *foot =
162 (heap_block_foot_t *) (addr + head->size - sizeof(heap_block_foot_t));
163
164 assert(foot->magic == HEAP_BLOCK_FOOT_MAGIC);
165 assert(head->size == foot->size);
166}
167
168/** Increase the heap area size
169 *
170 * Should be called only inside the critical section.
171 *
172 * @param size Number of bytes to grow the heap by.
173 *
174 */
175static bool grow_heap(size_t size)
176{
177 if (size == 0)
178 return false;
179
180 if ((heap_start + size < heap_start) || (heap_end + size < heap_end))
181 return false;
182
183 size_t heap_size = (size_t) (heap_end - heap_start);
184
185 if ((max_heap_size != (size_t) -1) && (heap_size + size > max_heap_size))
186 return false;
187
188 size_t pages = (size - 1) / PAGE_SIZE + 1;
189
190 if (as_area_resize((void *) &_heap, (heap_pages + pages) * PAGE_SIZE, 0)
191 == EOK) {
192 void *end = (void *) ALIGN_DOWN(((uintptr_t) &_heap) +
193 (heap_pages + pages) * PAGE_SIZE, BASE_ALIGN);
194 block_init(heap_end, end - heap_end, true);
195 heap_pages += pages;
196 heap_end = end;
197 return true;
198 }
199
200 return false;
201}
202
203/** Decrease the heap area
204 *
205 * Should be called only inside the critical section.
206 *
207 * @param size Number of bytes to shrink the heap by.
208 *
209 */
210static void shrink_heap(void)
211{
212 // TODO
213}
214
215/** Initialize the heap allocator
216 *
217 * Find how much physical memory we have and create
218 * the heap management structures that mark the whole
219 * physical memory as a single free block.
220 *
221 */
222void __heap_init(void)
223{
224 futex_down(&malloc_futex);
225
226 if (as_area_create((void *) &_heap, PAGE_SIZE,
227 AS_AREA_WRITE | AS_AREA_READ)) {
228 heap_pages = 1;
229 heap_start = (void *) ALIGN_UP((uintptr_t) &_heap, BASE_ALIGN);
230 heap_end =
231 (void *) ALIGN_DOWN(((uintptr_t) &_heap) + PAGE_SIZE, BASE_ALIGN);
232
233 /* Make the entire area one large block. */
234 block_init(heap_start, heap_end - heap_start, true);
235 }
236
237 futex_up(&malloc_futex);
238}
239
240/** Get maximum heap address
241 *
242 */
243uintptr_t get_max_heap_addr(void)
244{
245 futex_down(&malloc_futex);
246
247 if (max_heap_size == (size_t) -1)
248 max_heap_size =
249 max((size_t) (heap_end - heap_start), MAX_HEAP_SIZE);
250
251 uintptr_t max_heap_addr = (uintptr_t) heap_start + max_heap_size;
252
253 futex_up(&malloc_futex);
254
255 return max_heap_addr;
256}
257
258/** Split heap block and mark it as used.
259 *
260 * Should be called only inside the critical section.
261 *
262 * @param cur Heap block to split.
263 * @param size Number of bytes to split and mark from the beginning
264 * of the block.
265 *
266 */
267static void split_mark(heap_block_head_t *cur, const size_t size)
268{
269 assert(cur->size >= size);
270
271 /* See if we should split the block. */
272 size_t split_limit = GROSS_SIZE(size);
273
274 if (cur->size > split_limit) {
275 /* Block big enough -> split. */
276 void *next = ((void *) cur) + size;
277 block_init(next, cur->size - size, true);
278 block_init(cur, size, false);
279 } else {
280 /* Block too small -> use as is. */
281 cur->free = false;
282 }
283}
284
285/** Allocate a memory block
286 *
287 * Should be called only inside the critical section.
288 *
289 * @param size The size of the block to allocate.
290 * @param align Memory address alignment.
291 *
292 * @return the address of the block or NULL when not enough memory.
293 *
294 */
295static void *malloc_internal(const size_t size, const size_t align)
296{
297 if (align == 0)
298 return NULL;
299
300 size_t falign = lcm(align, BASE_ALIGN);
301 size_t real_size = GROSS_SIZE(ALIGN_UP(size, falign));
302
303 bool grown = false;
304 void *result;
305
306loop:
307 result = NULL;
308 heap_block_head_t *cur = (heap_block_head_t *) heap_start;
309
310 while ((result == NULL) && ((void *) cur < heap_end)) {
311 block_check(cur);
312
313 /* Try to find a block that is free and large enough. */
314 if ((cur->free) && (cur->size >= real_size)) {
315 /* We have found a suitable block.
316 Check for alignment properties. */
317 void *addr = ((void *) cur) + sizeof(heap_block_head_t);
318 void *aligned = (void *) ALIGN_UP(addr, falign);
319
320 if (addr == aligned) {
321 /* Exact block start including alignment. */
322 split_mark(cur, real_size);
323 result = addr;
324 } else {
325 /* Block start has to be aligned */
326 size_t excess = (size_t) (aligned - addr);
327
328 if (cur->size >= real_size + excess) {
329 /* The current block is large enough to fit
330 data in including alignment */
331 if ((void *) cur > heap_start) {
332 /* There is a block before the current block.
333 This previous block can be enlarged to compensate
334 for the alignment excess */
335 heap_block_foot_t *prev_foot =
336 ((void *) cur) - sizeof(heap_block_foot_t);
337
338 heap_block_head_t *prev_head =
339 (heap_block_head_t *) (((void *) cur) - prev_foot->size);
340
341 block_check(prev_head);
342
343 size_t reduced_size = cur->size - excess;
344 heap_block_head_t *next_head = ((void *) cur) + excess;
345
346 if ((!prev_head->free) && (excess >= STRUCT_OVERHEAD)) {
347 /* The previous block is not free and there is enough
348 space to fill in a new free block between the previous
349 and current block */
350 block_init(cur, excess, true);
351 } else {
352 /* The previous block is free (thus there is no need to
353 induce additional fragmentation to the heap) or the
354 excess is small, thus just enlarge the previous block */
355 block_init(prev_head, prev_head->size + excess, prev_head->free);
356 }
357
358 block_init(next_head, reduced_size, true);
359 split_mark(next_head, real_size);
360 result = aligned;
361 cur = next_head;
362 } else {
363 /* The current block is the first block on the heap.
364 We have to make sure that the alignment excess
365 is large enough to fit a new free block just
366 before the current block */
367 while (excess < STRUCT_OVERHEAD) {
368 aligned += falign;
369 excess += falign;
370 }
371
372 /* Check for current block size again */
373 if (cur->size >= real_size + excess) {
374 size_t reduced_size = cur->size - excess;
375 cur = (heap_block_head_t *) (heap_start + excess);
376
377 block_init(heap_start, excess, true);
378 block_init(cur, reduced_size, true);
379 split_mark(cur, real_size);
380 result = aligned;
381 }
382 }
383 }
384 }
385 }
386
387 /* Advance to the next block. */
388 cur = (heap_block_head_t *) (((void *) cur) + cur->size);
389 }
390
391 if ((result == NULL) && (!grown)) {
392 if (grow_heap(real_size)) {
393 grown = true;
394 goto loop;
395 }
396 }
397
398 return result;
399}
400
401/** Allocate memory by number of elements
402 *
403 * @param nmemb Number of members to allocate.
404 * @param size Size of one member in bytes.
405 *
406 * @return Allocated memory or NULL.
407 *
408 */
409void *calloc(const size_t nmemb, const size_t size)
410{
411 void *block = malloc(nmemb * size);
412 if (block == NULL)
413 return NULL;
414
415 memset(block, 0, nmemb * size);
416 return block;
417}
418
419/** Allocate memory
420 *
421 * @param size Number of bytes to allocate.
422 *
423 * @return Allocated memory or NULL.
424 *
425 */
426void *malloc(const size_t size)
427{
428 futex_down(&malloc_futex);
429 void *block = malloc_internal(size, BASE_ALIGN);
430 futex_up(&malloc_futex);
431
432 return block;
433}
434
435/** Allocate memory with specified alignment
436 *
437 * @param align Alignment in byes.
438 * @param size Number of bytes to allocate.
439 *
440 * @return Allocated memory or NULL.
441 *
442 */
443void *memalign(const size_t align, const size_t size)
444{
445 if (align == 0)
446 return NULL;
447
448 size_t palign =
449 1 << (fnzb(max(sizeof(void *), align) - 1) + 1);
450
451 futex_down(&malloc_futex);
452 void *block = malloc_internal(size, palign);
453 futex_up(&malloc_futex);
454
455 return block;
456}
457
458/** Reallocate memory block
459 *
460 * @param addr Already allocated memory or NULL.
461 * @param size New size of the memory block.
462 *
463 * @return Reallocated memory or NULL.
464 *
465 */
466void *realloc(const void *addr, const size_t size)
467{
468 if (addr == NULL)
469 return malloc(size);
470
471 futex_down(&malloc_futex);
472
473 /* Calculate the position of the header. */
474 heap_block_head_t *head =
475 (heap_block_head_t *) (addr - sizeof(heap_block_head_t));
476
477 assert((void *) head >= heap_start);
478 assert((void *) head < heap_end);
479
480 block_check(head);
481 assert(!head->free);
482
483 void *ptr = NULL;
484 bool reloc = false;
485 size_t real_size = GROSS_SIZE(ALIGN_UP(size, BASE_ALIGN));
486 size_t orig_size = head->size;
487
488 if (orig_size > real_size) {
489 /* Shrink */
490 if (orig_size - real_size >= STRUCT_OVERHEAD) {
491 /* Split the original block to a full block
492 and a trailing free block */
493 block_init((void *) head, real_size, false);
494 block_init((void *) head + real_size,
495 orig_size - real_size, true);
496 shrink_heap();
497 }
498
499 ptr = ((void *) head) + sizeof(heap_block_head_t);
500 } else {
501 /* Look at the next block. If it is free and the size is
502 sufficient then merge the two. Otherwise just allocate
503 a new block, copy the original data into it and
504 free the original block. */
505 heap_block_head_t *next_head =
506 (heap_block_head_t *) (((void *) head) + head->size);
507
508 if (((void *) next_head < heap_end) &&
509 (head->size + next_head->size >= real_size) &&
510 (next_head->free)) {
511 block_check(next_head);
512 block_init(head, head->size + next_head->size, false);
513 split_mark(head, real_size);
514
515 ptr = ((void *) head) + sizeof(heap_block_head_t);
516 } else
517 reloc = true;
518 }
519
520 futex_up(&malloc_futex);
521
522 if (reloc) {
523 ptr = malloc(size);
524 if (ptr != NULL) {
525 memcpy(ptr, addr, NET_SIZE(orig_size));
526 free(addr);
527 }
528 }
529
530 return ptr;
531}
532
533/** Free a memory block
534 *
535 * @param addr The address of the block.
536 *
537 */
538void free(const void *addr)
539{
540 futex_down(&malloc_futex);
541
542 /* Calculate the position of the header. */
543 heap_block_head_t *head
544 = (heap_block_head_t *) (addr - sizeof(heap_block_head_t));
545
546 assert((void *) head >= heap_start);
547 assert((void *) head < heap_end);
548
549 block_check(head);
550 assert(!head->free);
551
552 /* Mark the block itself as free. */
553 head->free = true;
554
555 /* Look at the next block. If it is free, merge the two. */
556 heap_block_head_t *next_head
557 = (heap_block_head_t *) (((void *) head) + head->size);
558
559 if ((void *) next_head < heap_end) {
560 block_check(next_head);
561 if (next_head->free)
562 block_init(head, head->size + next_head->size, true);
563 }
564
565 /* Look at the previous block. If it is free, merge the two. */
566 if ((void *) head > heap_start) {
567 heap_block_foot_t *prev_foot =
568 (heap_block_foot_t *) (((void *) head) - sizeof(heap_block_foot_t));
569
570 heap_block_head_t *prev_head =
571 (heap_block_head_t *) (((void *) head) - prev_foot->size);
572
573 block_check(prev_head);
574
575 if (prev_head->free)
576 block_init(prev_head, prev_head->size + head->size, true);
577 }
578
579 shrink_heap();
580
581 futex_up(&malloc_futex);
582}
583
584/** @}
585 */
Note: See TracBrowser for help on using the repository browser.