#include #include #include #include #define IS_POWER_OF_2(n) ((n) != 0 && (((n) & ((n) - 1)) == 0)) /* ------------------------------------------------------------------------------------------------------------------ */ /* */ OS_STATIC_FORCE_INLINE buddy_block_t* buddy_block_next(buddy_block_t* block){ return (buddy_block_t*)((uint8_t*)block + block->size); } static buddy_block_t *buddy_block_split(buddy_block_t *block, size_t size) { if (block != NULL && size != 0) { // Recursive split while (size < block->size) { size_t sz = block->size >> 1; block->size = sz; block = buddy_block_next(block); block->size = sz; block->is_free = OS_TRUE; } if (size <= block->size) { return block; } } // Block cannot fit the requested allocation size return NULL; } static buddy_block_t *buddy_block_find_best(buddy_block_t *head, buddy_block_t *tail, size_t size) { // Assumes size != 0 buddy_block_t *best_block = NULL; buddy_block_t *block = head; // Left Buddy buddy_block_t *buddy = buddy_block_next(block); // Right Buddy // The entire memory section between head and tail is free, // just call 'buddy_block_split' to get the allocation if (buddy == tail && block->is_free) { return buddy_block_split(block, size); } // Find the block which is the 'best_block' to requested allocation sized while (block < tail && buddy < tail) { // make sure the buddies are within the range // If both buddies are free, coalesce them together // NOTE: this is an optimization to reduce fragmentation // this could be completely ignored if (block->is_free && buddy->is_free && block->size == buddy->size) { block->size <<= 1; if (size <= block->size && (best_block == NULL || block->size <= best_block->size)) { best_block = block; } block = buddy_block_next(buddy); if (block < tail) { // Delay the buddy block for the next iteration buddy = buddy_block_next(block); } continue; } if (block->is_free && size <= block->size && (best_block == NULL || block->size <= best_block->size)) { best_block = block; } if (buddy->is_free && size <= buddy->size && (best_block == NULL || buddy->size < best_block->size)) { // If each buddy are the same size, then it makes more sense // to pick the buddy as it "bounces around" less best_block = buddy; } if (block->size <= buddy->size) { block = buddy_block_next(buddy); if (block < tail) { // Delay the buddy block for the next iteration buddy = buddy_block_next(block); } } else { // Buddy was split into smaller blocks block = buddy; buddy = buddy_block_next(buddy); } } if (best_block != NULL) { // This will handle the case if the 'best_block' is also the perfect fit return buddy_block_split(best_block, size); } // Maybe out of memory return NULL; } OS_STATIC_FORCE_INLINE os_size_t align_forward_size(os_size_t ptr, os_size_t align) { os_size_t a, p, modulo; OS_ASSERT(IS_POWER_OF_2((os_uintptr_t)align)); a = align; p = ptr; modulo = p & (a-1); if (modulo != 0) { p += a - modulo; } return p; } OS_STATIC_FORCE_INLINE os_size_t buddy_block_size_required(buddy_t *b, size_t size) { os_size_t actual_size = b->alignment; size += sizeof(buddy_block_t); size = align_forward_size(size, b->alignment); while (size > actual_size) { actual_size <<= 1; } return actual_size; } static void buddy_block_coalescence(buddy_block_t *head, buddy_block_t *tail) { for (;;) { // Keep looping until there are no more buddies to coalesce buddy_block_t *block = head; buddy_block_t *buddy = buddy_block_next(block); #if 0 // 原版有bug bool no_coalescence = OS_TRUE; while (block < tail && buddy < tail) { // make sure the buddies are within the range if (block->is_free && buddy->is_free && block->size == buddy->size) { // Coalesce buddies into one block->size <<= 1; block = buddy_block_next(block); if (block < tail) { buddy = buddy_block_next(block); no_coalescence = C_FALSE; } } else if (block->size < buddy->size) { // The buddy block is split into smaller blocks block = buddy; buddy = buddy_block_next(buddy); } else { block = buddy_block_next(buddy); if (block < tail) { // Leave the buddy block for the next iteration buddy = buddy_block_next(block); } } } #endif bool no_coalescence = OS_TRUE; while (block < tail && buddy < tail) { // make sure the buddies are within the range if (block->is_free && buddy->is_free && block->size == buddy->size) { // Coalesce buddies into one block->size <<= 1; // block = buddy_block_next(buddy); if (block < tail) { buddy = buddy_block_next(block); no_coalescence = OS_FALSE; } } else if (block->size <= buddy->size) { block = buddy_block_next(buddy); if (block < tail) { // Leave the buddy block for the next iteration buddy = buddy_block_next(block); } } else { // The buddy block is split into smaller blocks block = buddy; buddy = buddy_block_next(buddy); } } if (no_coalescence) { return; } } } /* ------------------------------------------------------------------------------------------------------------------ */ /* */ void buddy_init(buddy_t* self, void* block, os_size_t block_size, os_size_t alignment){ OS_ASSERT(block != NULL); OS_ASSERT(IS_POWER_OF_2(block_size) && "size is not a power-of-two"); OS_ASSERT(IS_POWER_OF_2(alignment) && "alignment is not a power-of-two"); // The minimum alignment depends on the size of the `budy_block_t` header alignment = OS_MAX(alignment, sizeof(buddy_block_t)); // if (alignment < sizeof(buddy_block_t)) { // alignment = sizeof(buddy_block_t); // } OS_ASSERT((uintptr_t)block % alignment == 0 && "block is not aligned to minimum alignment"); self->head = (buddy_block_t *)block; self->head->size = block_size; self->head->is_free = OS_TRUE; // The tail here is a sentinel value and not a true block self->tail = buddy_block_next(self->head); self->alignment = alignment; } void buddy_destroy(buddy_t* self){ self->head = 0; self->tail = 0; self->alignment = 0; } void* buddy_alloc(buddy_t* b, os_size_t size){ if (size != 0) { size_t actual_size = buddy_block_size_required(b, size); buddy_block_t *found = buddy_block_find_best(b->head, b->tail, actual_size); if (found == NULL) { // Try to coalesce all the free buddy blocks and then search again buddy_block_coalescence(b->head, b->tail); found = buddy_block_find_best(b->head, b->tail, actual_size); } if (found != NULL) { found->is_free = OS_FALSE; return (void *)((uint8_t *)found + b->alignment); } // Out of memory (possibly due to too much internal fragmentation) } return NULL; } void* buddy_calloc(buddy_t* b, os_size_t count, os_size_t size){ void* mem = buddy_alloc(b, size * count); if(mem){ memset(mem, 0, size); } return mem; } void* buddy_realloc(buddy_t* self, void* ptr, os_size_t size){ void* mem = buddy_alloc(self, size); if(!mem){ return NULL; } buddy_block_t *block =(buddy_block_t *)((uint8_t *)ptr - self->alignment); size = OS_MIN(size, block->size); memcpy(mem, ptr, size); buddy_free(self, ptr); return mem; } void buddy_free(buddy_t* b, void* data){ if (data != NULL) { buddy_block_t *block; OS_ASSERT(b->head <= (buddy_block_t*)data); OS_ASSERT((buddy_block_t*)data < b->tail); block = (buddy_block_t *)((uint8_t *)data - b->alignment); block->is_free = OS_TRUE; // NOTE: Coalescence could be done now but it is optional // buddy_block_coalescence(b->head, b->tail); } }