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/SplayTree.h> 19 20 21 //#define TRACE_HEAP 22 #ifdef TRACE_HEAP 23 # define TRACE(format...) dprintf(format) 24 #else 25 # define TRACE(format...) do { } while (false) 26 #endif 27 28 29 /*! This is a very simple malloc()/free() implementation - it only 30 manages a free list. 31 After heap_init() is called, all free memory is contained in one 32 big chunk, the only entry in the free link list (which is a single 33 linked list). 34 When memory is allocated, the smallest free chunk that contains 35 the requested size is split (or taken as a whole if it can't be 36 splitted anymore), and it's lower half will be removed from the 37 free list. 38 The free list is ordered by size, starting with the smallest 39 free chunk available. When a chunk is freed, it will be joint 40 with its predecessor or successor, if possible. 41 To ease list handling, the list anchor itself is a free chunk with 42 size 0 that can't be allocated. 43 */ 44 45 #define DEBUG_ALLOCATIONS 46 // if defined, freed memory is filled with 0xcc 47 #define DEBUG_MAX_HEAP_USAGE 48 // if defined, the maximum heap usage is determined and printed before 49 // entering the kernel 50 51 52 const static size_t kAlignment = 8; 53 // all memory chunks will be a multiple of this 54 55 56 class Chunk { 57 public: 58 size_t CompleteSize() const 59 { 60 return fSize; 61 } 62 63 protected: 64 union { 65 size_t fSize; 66 char fAlignment[kAlignment]; 67 }; 68 }; 69 70 71 class FreeChunk; 72 73 74 struct FreeChunkData : SplayTreeLink<FreeChunk> { 75 76 FreeChunk* Next() const 77 { 78 return fNext; 79 } 80 81 FreeChunk** NextLink() 82 { 83 return &fNext; 84 } 85 86 protected: 87 FreeChunk* fNext; 88 }; 89 90 91 class FreeChunk : public Chunk, public FreeChunkData { 92 public: 93 void SetTo(size_t size); 94 95 size_t Size() const; 96 97 FreeChunk* Split(size_t splitSize); 98 bool IsTouching(FreeChunk* link); 99 FreeChunk* Join(FreeChunk* link); 100 101 void* AllocatedAddress() const; 102 static FreeChunk* SetToAllocated(void* allocated); 103 }; 104 105 106 struct FreeChunkKey { 107 FreeChunkKey(size_t size) 108 : 109 fSize(size), 110 fChunk(NULL) 111 { 112 } 113 114 FreeChunkKey(const FreeChunk* chunk) 115 : 116 fSize(chunk->Size()), 117 fChunk(chunk) 118 { 119 } 120 121 int Compare(const FreeChunk* chunk) const 122 { 123 size_t chunkSize = chunk->Size(); 124 if (chunkSize != fSize) 125 return fSize < chunkSize ? -1 : 1; 126 127 if (fChunk == chunk) 128 return 0; 129 return fChunk < chunk ? -1 : 1; 130 } 131 132 private: 133 size_t fSize; 134 const FreeChunk* fChunk; 135 }; 136 137 138 struct FreeChunkTreeDefinition { 139 typedef FreeChunkKey KeyType; 140 typedef FreeChunk NodeType; 141 142 static FreeChunkKey GetKey(const FreeChunk* node) 143 { 144 return FreeChunkKey(node); 145 } 146 147 static SplayTreeLink<FreeChunk>* GetLink(FreeChunk* node) 148 { 149 return node; 150 } 151 152 static int Compare(const FreeChunkKey& key, const FreeChunk* node) 153 { 154 return key.Compare(node); 155 } 156 157 static FreeChunk** GetListLink(FreeChunk* node) 158 { 159 return node->NextLink(); 160 } 161 }; 162 163 164 typedef IteratableSplayTree<FreeChunkTreeDefinition> FreeChunkTree; 165 166 167 static void* sHeapBase; 168 static size_t /*sHeapSize,*/ sMaxHeapSize, sAvailable, sMaxHeapUsage; 169 static FreeChunkTree sFreeChunkTree; 170 171 172 static inline size_t 173 align(size_t size) 174 { 175 return (size + kAlignment - 1) & ~(kAlignment - 1); 176 } 177 178 179 void 180 FreeChunk::SetTo(size_t size) 181 { 182 fSize = size; 183 fNext = NULL; 184 } 185 186 187 /*! Returns the amount of bytes that can be allocated 188 in this chunk. 189 */ 190 size_t 191 FreeChunk::Size() const 192 { 193 return (addr_t)this + fSize - (addr_t)AllocatedAddress(); 194 } 195 196 197 /*! Splits the upper half at the requested location and returns it. This chunk 198 will no longer be a valid FreeChunk object; only its fSize will be valid. 199 */ 200 FreeChunk* 201 FreeChunk::Split(size_t splitSize) 202 { 203 splitSize = align(splitSize); 204 205 FreeChunk* chunk = (FreeChunk*)((addr_t)AllocatedAddress() + splitSize); 206 size_t newSize = (addr_t)chunk - (addr_t)this; 207 chunk->fSize = fSize - newSize; 208 chunk->fNext = NULL; 209 210 fSize = newSize; 211 212 return chunk; 213 } 214 215 216 /*! Checks if the specified chunk touches this chunk, so 217 that they could be joined. 218 */ 219 bool 220 FreeChunk::IsTouching(FreeChunk* chunk) 221 { 222 return chunk 223 && (((uint8*)this + fSize == (uint8*)chunk) 224 || (uint8*)chunk + chunk->fSize == (uint8*)this); 225 } 226 227 228 /*! Joins the chunk to this chunk and returns the pointer 229 to the new chunk - which will either be one of the 230 two chunks. 231 Note, the chunks must be joinable, or else this method 232 doesn't work correctly. Use FreeChunk::IsTouching() 233 to check if this method can be applied. 234 */ 235 FreeChunk* 236 FreeChunk::Join(FreeChunk* chunk) 237 { 238 if (chunk < this) { 239 chunk->fSize += fSize; 240 chunk->fNext = fNext; 241 242 return chunk; 243 } 244 245 fSize += chunk->fSize; 246 fNext = chunk->fNext; 247 248 return this; 249 } 250 251 252 void* 253 FreeChunk::AllocatedAddress() const 254 { 255 return (void*)static_cast<const FreeChunkData*>(this); 256 } 257 258 259 FreeChunk* 260 FreeChunk::SetToAllocated(void* allocated) 261 { 262 return static_cast<FreeChunk*>((FreeChunkData*)allocated); 263 } 264 265 266 // #pragma mark - 267 268 269 void 270 heap_release(stage2_args* args) 271 { 272 platform_release_heap(args, sHeapBase); 273 } 274 275 276 void 277 heap_print_statistics() 278 { 279 #ifdef DEBUG_MAX_HEAP_USAGE 280 dprintf("maximum boot loader heap usage: %" B_PRIu32 "\n", sMaxHeapUsage); 281 #endif 282 } 283 284 285 status_t 286 heap_init(stage2_args* args) 287 { 288 void* base; 289 void* top; 290 if (platform_init_heap(args, &base, &top) < B_OK) 291 return B_ERROR; 292 293 sHeapBase = base; 294 sMaxHeapSize = (uint8*)top - (uint8*)base; 295 296 // declare the whole heap as one chunk, and add it 297 // to the free list 298 FreeChunk* chunk = (FreeChunk*)base; 299 chunk->SetTo(sMaxHeapSize); 300 sFreeChunkTree.Insert(chunk); 301 302 sAvailable = chunk->Size(); 303 #ifdef DEBUG_MAX_HEAP_USAGE 304 sMaxHeapUsage = sMaxHeapSize - sAvailable; 305 #endif 306 307 return B_OK; 308 } 309 310 311 #if 0 312 char* 313 grow_heap(uint32 bytes) 314 { 315 char* start; 316 317 if (sHeapSize + bytes > sMaxHeapSize) 318 return NULL; 319 320 start = (char*)sHeapBase + sHeapSize; 321 memset(start, 0, bytes); 322 sHeapSize += bytes; 323 324 return start; 325 } 326 #endif 327 328 #ifdef HEAP_TEST 329 void 330 dump_chunks(void) 331 { 332 FreeChunk* chunk = sFreeChunkTree.FindMin(); 333 while (chunk != NULL) { 334 printf("\t%p: chunk size = %ld, end = %p, next = %p\n", chunk, 335 chunk->Size(), (uint8*)chunk + chunk->CompleteSize(), 336 chunk->Next()); 337 chunk = chunk->Next(); 338 } 339 } 340 #endif 341 342 uint32 343 heap_available(void) 344 { 345 return (uint32)sAvailable; 346 } 347 348 349 void* 350 malloc(size_t size) 351 { 352 if (sHeapBase == NULL || size == 0) 353 return NULL; 354 355 // align the size requirement to a kAlignment bytes boundary 356 if (size < sizeof(FreeChunkData)) 357 size = sizeof(FreeChunkData); 358 size = align(size); 359 360 if (size > sAvailable) { 361 dprintf("malloc(): Out of memory!\n"); 362 return NULL; 363 } 364 365 FreeChunk* chunk = sFreeChunkTree.FindClosest(FreeChunkKey(size), true, 366 true); 367 368 if (chunk == NULL) { 369 // could not find a free chunk as large as needed 370 dprintf("malloc(): Out of memory!\n"); 371 return NULL; 372 } 373 374 sFreeChunkTree.Remove(chunk); 375 sAvailable -= chunk->Size(); 376 377 void* allocatedAddress = chunk->AllocatedAddress(); 378 379 // If this chunk is bigger than the requested size and there's enough space 380 // left over for a new chunk, we split it. 381 if (chunk->Size() >= size + align(sizeof(FreeChunk))) { 382 FreeChunk* freeChunk = chunk->Split(size); 383 sFreeChunkTree.Insert(freeChunk); 384 sAvailable += freeChunk->Size(); 385 } 386 387 #ifdef DEBUG_MAX_HEAP_USAGE 388 sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable); 389 #endif 390 391 TRACE("malloc(%lu) -> %p\n", size, allocatedAddress); 392 return allocatedAddress; 393 } 394 395 396 void* 397 realloc(void* oldBuffer, size_t newSize) 398 { 399 if (newSize == 0) { 400 TRACE("realloc(%p, %lu) -> NULL\n", oldBuffer, newSize); 401 free(oldBuffer); 402 return NULL; 403 } 404 405 size_t copySize = newSize; 406 if (oldBuffer != NULL) { 407 FreeChunk* oldChunk = FreeChunk::SetToAllocated(oldBuffer); 408 409 // Check if the old buffer still fits, and if it makes sense to keep it 410 if (oldChunk->Size() >= newSize 411 && (oldChunk->Size() < 128 || newSize > oldChunk->Size() / 3)) { 412 TRACE("realloc(%p, %lu) old buffer is large enough\n", 413 oldBuffer, newSize); 414 return oldChunk->AllocatedAddress(); 415 } 416 417 if (copySize > oldChunk->Size()) 418 copySize = oldChunk->Size(); 419 } 420 421 void* newBuffer = malloc(newSize); 422 if (newBuffer == NULL) 423 return NULL; 424 425 if (oldBuffer != NULL) { 426 memcpy(newBuffer, oldBuffer, copySize); 427 free(oldBuffer); 428 } 429 430 TRACE("realloc(%p, %lu) -> %p\n", oldBuffer, newSize, newBuffer); 431 return newBuffer; 432 } 433 434 435 void 436 free(void* allocated) 437 { 438 if (allocated == NULL) 439 return; 440 441 TRACE("free(%p)\n", allocated); 442 443 FreeChunk* freedChunk = FreeChunk::SetToAllocated(allocated); 444 445 #ifdef DEBUG_ALLOCATIONS 446 if (freedChunk->Size() > sMaxHeapSize - sAvailable) { 447 panic("freed chunk %p clobbered (%#zx)!\n", freedChunk, 448 freedChunk->Size()); 449 } 450 { 451 FreeChunk* chunk = sFreeChunkTree.FindMin(); 452 while (chunk) { 453 if (chunk->Size() > sAvailable || freedChunk == chunk) 454 panic("invalid chunk in free list (%p (%zu)), or double free\n", 455 chunk, chunk->Size()); 456 chunk = chunk->Next(); 457 } 458 } 459 #endif 460 461 // try to join the new free chunk with an existing one 462 // it may be joined with up to two chunks 463 464 FreeChunk* chunk = sFreeChunkTree.FindMin(); 465 int32 joinCount = 0; 466 467 while (chunk) { 468 FreeChunk* nextChunk = chunk->Next(); 469 470 if (chunk->IsTouching(freedChunk)) { 471 sFreeChunkTree.Remove(chunk); 472 sAvailable -= chunk->Size(); 473 474 freedChunk = chunk->Join(freedChunk); 475 476 if (++joinCount == 2) 477 break; 478 } 479 480 chunk = nextChunk; 481 } 482 483 sFreeChunkTree.Insert(freedChunk); 484 sAvailable += freedChunk->Size(); 485 #ifdef DEBUG_MAX_HEAP_USAGE 486 sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable); 487 #endif 488 } 489 490