xref: /haiku/src/system/boot/loader/heap.cpp (revision 08b9db66ac0a5d4fa92dcb82820e1435f203ea42)
1 /*
2  * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <boot/heap.h>
8 
9 #ifdef HEAP_TEST
10 #	include <stdio.h>
11 #endif
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include <algorithm>
16 
17 #include <boot/platform.h>
18 #include <util/OpenHashTable.h>
19 #include <util/SplayTree.h>
20 
21 
22 //#define TRACE_HEAP
23 #ifdef TRACE_HEAP
24 #	define TRACE(format...)	dprintf(format)
25 #else
26 #	define TRACE(format...)	do { } while (false)
27 #endif
28 
29 
30 /*!	This is a very simple malloc()/free() implementation - it only
31 	manages a free list.
32 	After heap_init() is called, all free memory is contained in one
33 	big chunk, the only entry in the free link list (which is a single
34 	linked list).
35 	When memory is allocated, the smallest free chunk that contains
36 	the requested size is split (or taken as a whole if it can't be
37 	splitted anymore), and it's lower half will be removed from the
38 	free list.
39 	The free list is ordered by size, starting with the smallest
40 	free chunk available. When a chunk is freed, it will be joint
41 	with its predecessor or successor, if possible.
42 	To ease list handling, the list anchor itself is a free chunk with
43 	size 0 that can't be allocated.
44 */
45 
46 #define DEBUG_ALLOCATIONS
47 	// if defined, freed memory is filled with 0xcc
48 #define DEBUG_MAX_HEAP_USAGE
49 	// if defined, the maximum heap usage is determined and printed before
50 	// entering the kernel
51 
52 
53 const static size_t kAlignment = 8;
54 	// all memory chunks will be a multiple of this
55 const static size_t kLargeAllocationThreshold = 16 * 1024;
56 	// allocations of this size or larger are allocated via
57 	// platform_allocate_region()
58 
59 
60 class Chunk {
61 public:
62 	size_t CompleteSize() const
63 	{
64 		return fSize;
65 	}
66 
67 protected:
68 	union {
69 		size_t	fSize;
70 		char	fAlignment[kAlignment];
71 	};
72 };
73 
74 
75 class FreeChunk;
76 
77 
78 struct FreeChunkData : SplayTreeLink<FreeChunk> {
79 
80 	FreeChunk* Next() const
81 	{
82 		return fNext;
83 	}
84 
85 	FreeChunk** NextLink()
86 	{
87 		return &fNext;
88 	}
89 
90 protected:
91 	FreeChunk*	fNext;
92 };
93 
94 
95 class FreeChunk : public Chunk, public FreeChunkData {
96 public:
97 			void				SetTo(size_t size);
98 
99 			size_t				Size() const;
100 
101 			FreeChunk*			Split(size_t splitSize);
102 			bool				IsTouching(FreeChunk* link);
103 			FreeChunk*			Join(FreeChunk* link);
104 
105 			void*				AllocatedAddress() const;
106 	static	FreeChunk*			SetToAllocated(void* allocated);
107 };
108 
109 
110 struct FreeChunkKey {
111 	FreeChunkKey(size_t size)
112 		:
113 		fSize(size),
114 		fChunk(NULL)
115 	{
116 	}
117 
118 	FreeChunkKey(const FreeChunk* chunk)
119 		:
120 		fSize(chunk->Size()),
121 		fChunk(chunk)
122 	{
123 	}
124 
125 	int Compare(const FreeChunk* chunk) const
126 	{
127 		size_t chunkSize = chunk->Size();
128 		if (chunkSize != fSize)
129 			return fSize < chunkSize ? -1 : 1;
130 
131 		if (fChunk == chunk)
132 			return 0;
133 		return fChunk < chunk ? -1 : 1;
134 	}
135 
136 private:
137 	size_t				fSize;
138 	const FreeChunk*	fChunk;
139 };
140 
141 
142 struct FreeChunkTreeDefinition {
143 	typedef FreeChunkKey	KeyType;
144 	typedef FreeChunk		NodeType;
145 
146 	static FreeChunkKey GetKey(const FreeChunk* node)
147 	{
148 		return FreeChunkKey(node);
149 	}
150 
151 	static SplayTreeLink<FreeChunk>* GetLink(FreeChunk* node)
152 	{
153 		return node;
154 	}
155 
156 	static int Compare(const FreeChunkKey& key, const FreeChunk* node)
157 	{
158 		return key.Compare(node);
159 	}
160 
161 	static FreeChunk** GetListLink(FreeChunk* node)
162 	{
163 		return node->NextLink();
164 	}
165 };
166 
167 
168 typedef IteratableSplayTree<FreeChunkTreeDefinition> FreeChunkTree;
169 
170 
171 struct LargeAllocation {
172 	LargeAllocation()
173 	{
174 	}
175 
176 	void SetTo(void* address, size_t size)
177 	{
178 		fAddress = address;
179 		fSize = size;
180 	}
181 
182 	status_t Allocate(size_t size)
183 	{
184 		fSize = size;
185 		return platform_allocate_region(&fAddress, fSize,
186 			B_READ_AREA | B_WRITE_AREA, false);
187 	}
188 
189 	void Free()
190 	{
191 		platform_free_region(fAddress, fSize);
192 	}
193 
194 	void* Address() const
195 	{
196 		return fAddress;
197 	}
198 
199 	size_t Size() const
200 	{
201 		return fSize;
202 	}
203 
204 	LargeAllocation*& HashNext()
205 	{
206 		return fHashNext;
207 	}
208 
209 private:
210 	void*				fAddress;
211 	size_t				fSize;
212 	LargeAllocation*	fHashNext;
213 };
214 
215 
216 struct LargeAllocationHashDefinition {
217 	typedef void*			KeyType;
218 	typedef	LargeAllocation	ValueType;
219 
220 	size_t HashKey(void* key) const
221 	{
222 		return size_t(key) / kAlignment;
223 	}
224 
225 	size_t Hash(LargeAllocation* value) const
226 	{
227 		return HashKey(value->Address());
228 	}
229 
230 	bool Compare(void* key, LargeAllocation* value) const
231 	{
232 		return value->Address() == key;
233 	}
234 
235 	LargeAllocation*& GetLink(LargeAllocation* value) const
236 	{
237 		return value->HashNext();
238 	}
239 };
240 
241 
242 typedef BOpenHashTable<LargeAllocationHashDefinition> LargeAllocationHashTable;
243 
244 
245 static void* sHeapBase;
246 static void* sHeapEnd;
247 static size_t sMaxHeapSize, sAvailable, sMaxHeapUsage;
248 static FreeChunkTree sFreeChunkTree;
249 
250 static LargeAllocationHashTable sLargeAllocations;
251 
252 
253 static inline size_t
254 align(size_t size)
255 {
256 	return (size + kAlignment - 1) & ~(kAlignment - 1);
257 }
258 
259 
260 static void*
261 malloc_large(size_t size)
262 {
263 	LargeAllocation* allocation = new(std::nothrow) LargeAllocation;
264 	if (allocation == NULL) {
265 		dprintf("malloc_large(): Out of memory!\n");
266 		return NULL;
267 	}
268 
269 	if (allocation->Allocate(size) != B_OK) {
270 		dprintf("malloc_large(): Out of memory!\n");
271 		delete allocation;
272 		return NULL;
273 	}
274 
275 	sLargeAllocations.InsertUnchecked(allocation);
276 	TRACE("malloc_large(%lu) -> %p\n", size, allocation->Address());
277 	return allocation->Address();
278 }
279 
280 
281 static void
282 free_large(void* address)
283 {
284 	LargeAllocation* allocation = sLargeAllocations.Lookup(address);
285 	if (allocation == NULL)
286 		panic("free_large(%p): unknown allocation!\n", address);
287 
288 	sLargeAllocations.RemoveUnchecked(allocation);
289 	allocation->Free();
290 	delete allocation;
291 }
292 
293 
294 void
295 FreeChunk::SetTo(size_t size)
296 {
297 	fSize = size;
298 	fNext = NULL;
299 }
300 
301 
302 /*!	Returns the amount of bytes that can be allocated
303 	in this chunk.
304 */
305 size_t
306 FreeChunk::Size() const
307 {
308 	return (addr_t)this + fSize - (addr_t)AllocatedAddress();
309 }
310 
311 
312 /*!	Splits the upper half at the requested location and returns it. This chunk
313 	will no longer be a valid FreeChunk object; only its fSize will be valid.
314  */
315 FreeChunk*
316 FreeChunk::Split(size_t splitSize)
317 {
318 	splitSize = align(splitSize);
319 
320 	FreeChunk* chunk = (FreeChunk*)((addr_t)AllocatedAddress() + splitSize);
321 	size_t newSize = (addr_t)chunk - (addr_t)this;
322 	chunk->fSize = fSize - newSize;
323 	chunk->fNext = NULL;
324 
325 	fSize = newSize;
326 
327 	return chunk;
328 }
329 
330 
331 /*!	Checks if the specified chunk touches this chunk, so
332 	that they could be joined.
333 */
334 bool
335 FreeChunk::IsTouching(FreeChunk* chunk)
336 {
337 	return chunk
338 		&& (((uint8*)this + fSize == (uint8*)chunk)
339 			|| (uint8*)chunk + chunk->fSize == (uint8*)this);
340 }
341 
342 
343 /*!	Joins the chunk to this chunk and returns the pointer
344 	to the new chunk - which will either be one of the
345 	two chunks.
346 	Note, the chunks must be joinable, or else this method
347 	doesn't work correctly. Use FreeChunk::IsTouching()
348 	to check if this method can be applied.
349 */
350 FreeChunk*
351 FreeChunk::Join(FreeChunk* chunk)
352 {
353 	if (chunk < this) {
354 		chunk->fSize += fSize;
355 		chunk->fNext = fNext;
356 
357 		return chunk;
358 	}
359 
360 	fSize += chunk->fSize;
361 	fNext = chunk->fNext;
362 
363 	return this;
364 }
365 
366 
367 void*
368 FreeChunk::AllocatedAddress() const
369 {
370 	return (void*)static_cast<const FreeChunkData*>(this);
371 }
372 
373 
374 FreeChunk*
375 FreeChunk::SetToAllocated(void* allocated)
376 {
377 	return static_cast<FreeChunk*>((FreeChunkData*)allocated);
378 }
379 
380 
381 //	#pragma mark -
382 
383 
384 void
385 heap_release(stage2_args* args)
386 {
387 	platform_release_heap(args, sHeapBase);
388 }
389 
390 
391 void
392 heap_print_statistics()
393 {
394 #ifdef DEBUG_MAX_HEAP_USAGE
395 	dprintf("maximum boot loader heap usage: %zu, currently used: %zu\n",
396 		sMaxHeapUsage, sMaxHeapSize - sAvailable);
397 #endif
398 }
399 
400 
401 status_t
402 heap_init(stage2_args* args)
403 {
404 	void* base;
405 	void* top;
406 	if (platform_init_heap(args, &base, &top) < B_OK)
407 		return B_ERROR;
408 
409 	sHeapBase = base;
410 	sHeapEnd = top;
411 	sMaxHeapSize = (uint8*)top - (uint8*)base;
412 
413 	// declare the whole heap as one chunk, and add it
414 	// to the free list
415 	FreeChunk* chunk = (FreeChunk*)base;
416 	chunk->SetTo(sMaxHeapSize);
417 	sFreeChunkTree.Insert(chunk);
418 
419 	sAvailable = chunk->Size();
420 #ifdef DEBUG_MAX_HEAP_USAGE
421 	sMaxHeapUsage = sMaxHeapSize - sAvailable;
422 #endif
423 
424 	if (sLargeAllocations.Init(64) != B_OK)
425 		return B_NO_MEMORY;
426 
427 	return B_OK;
428 }
429 
430 
431 #ifdef HEAP_TEST
432 void
433 dump_chunks(void)
434 {
435 	FreeChunk* chunk = sFreeChunkTree.FindMin();
436 	while (chunk != NULL) {
437 		printf("\t%p: chunk size = %ld, end = %p, next = %p\n", chunk,
438 			chunk->Size(), (uint8*)chunk + chunk->CompleteSize(),
439 			chunk->Next());
440 		chunk = chunk->Next();
441 	}
442 }
443 #endif
444 
445 uint32
446 heap_available(void)
447 {
448 	return (uint32)sAvailable;
449 }
450 
451 
452 void*
453 malloc(size_t size)
454 {
455 	if (sHeapBase == NULL || size == 0)
456 		return NULL;
457 
458 	// align the size requirement to a kAlignment bytes boundary
459 	if (size < sizeof(FreeChunkData))
460 		size = sizeof(FreeChunkData);
461 	size = align(size);
462 
463 	if (size >= kLargeAllocationThreshold)
464 		return malloc_large(size);
465 
466 	if (size > sAvailable) {
467 		dprintf("malloc(): Out of memory allocating a block of %ld bytes, "
468 			"only %ld left!\n", size, sAvailable);
469 		return NULL;
470 	}
471 
472 	FreeChunk* chunk = sFreeChunkTree.FindClosest(FreeChunkKey(size), true,
473 		true);
474 
475 	if (chunk == NULL) {
476 		// could not find a free chunk as large as needed
477 		dprintf("malloc(): Out of memory allocating a block of %ld bytes, "
478 			"no free chunks!\n", size);
479 		return NULL;
480 	}
481 
482 	sFreeChunkTree.Remove(chunk);
483 	sAvailable -= chunk->Size();
484 
485 	void* allocatedAddress = chunk->AllocatedAddress();
486 
487 	// If this chunk is bigger than the requested size and there's enough space
488 	// left over for a new chunk, we split it.
489 	if (chunk->Size() >= size + align(sizeof(FreeChunk))) {
490 		FreeChunk* freeChunk = chunk->Split(size);
491 		sFreeChunkTree.Insert(freeChunk);
492 		sAvailable += freeChunk->Size();
493 	}
494 
495 #ifdef DEBUG_MAX_HEAP_USAGE
496 	sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable);
497 #endif
498 
499 	TRACE("malloc(%lu) -> %p\n", size, allocatedAddress);
500 	return allocatedAddress;
501 }
502 
503 
504 void*
505 realloc(void* oldBuffer, size_t newSize)
506 {
507 	if (newSize == 0) {
508 		TRACE("realloc(%p, %lu) -> NULL\n", oldBuffer, newSize);
509 		free(oldBuffer);
510 		return NULL;
511 	}
512 
513 	size_t oldSize = 0;
514 	if (oldBuffer != NULL) {
515 		if (oldBuffer >= sHeapBase && oldBuffer < sHeapEnd) {
516 			FreeChunk* oldChunk = FreeChunk::SetToAllocated(oldBuffer);
517 			oldSize = oldChunk->Size();
518 		} else {
519 			LargeAllocation* allocation = sLargeAllocations.Lookup(oldBuffer);
520 			if (allocation == NULL) {
521 				panic("realloc(%p, %zu): unknown allocation!\n", oldBuffer,
522 					newSize);
523 			}
524 
525 			oldSize = allocation->Size();
526 		}
527 
528 		// Check if the old buffer still fits, and if it makes sense to keep it.
529 		if (oldSize >= newSize
530 			&& (oldSize < 128 || newSize > oldSize / 3)) {
531 			TRACE("realloc(%p, %lu) old buffer is large enough\n",
532 				oldBuffer, newSize);
533 			return oldBuffer;
534 		}
535 	}
536 
537 	void* newBuffer = malloc(newSize);
538 	if (newBuffer == NULL)
539 		return NULL;
540 
541 	if (oldBuffer != NULL) {
542 		memcpy(newBuffer, oldBuffer, std::min(oldSize, newSize));
543 		free(oldBuffer);
544 	}
545 
546 	TRACE("realloc(%p, %lu) -> %p\n", oldBuffer, newSize, newBuffer);
547 	return newBuffer;
548 }
549 
550 
551 void
552 free(void* allocated)
553 {
554 	if (allocated == NULL)
555 		return;
556 
557 	TRACE("free(%p)\n", allocated);
558 
559 	if (allocated < sHeapBase || allocated >= sHeapEnd) {
560 		free_large(allocated);
561 		return;
562 	}
563 
564 	FreeChunk* freedChunk = FreeChunk::SetToAllocated(allocated);
565 
566 #ifdef DEBUG_ALLOCATIONS
567 	if (freedChunk->Size() > sMaxHeapSize - sAvailable) {
568 		panic("freed chunk %p clobbered (%#zx)!\n", freedChunk,
569 			freedChunk->Size());
570 	}
571 	{
572 		FreeChunk* chunk = sFreeChunkTree.FindMin();
573 		while (chunk) {
574 			if (chunk->Size() > sAvailable || freedChunk == chunk)
575 				panic("invalid chunk in free list (%p (%zu)), or double free\n",
576 					chunk, chunk->Size());
577 			chunk = chunk->Next();
578 		}
579 	}
580 #endif
581 
582 	// try to join the new free chunk with an existing one
583 	// it may be joined with up to two chunks
584 
585 	FreeChunk* chunk = sFreeChunkTree.FindMin();
586 	int32 joinCount = 0;
587 
588 	while (chunk) {
589 		FreeChunk* nextChunk = chunk->Next();
590 
591 		if (chunk->IsTouching(freedChunk)) {
592 			sFreeChunkTree.Remove(chunk);
593 			sAvailable -= chunk->Size();
594 
595 			freedChunk = chunk->Join(freedChunk);
596 
597 			if (++joinCount == 2)
598 				break;
599 		}
600 
601 		chunk = nextChunk;
602 	}
603 
604 	sFreeChunkTree.Insert(freedChunk);
605 	sAvailable += freedChunk->Size();
606 #ifdef DEBUG_MAX_HEAP_USAGE
607 	sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable);
608 #endif
609 }
610 
611