Files
RTOS/AppKit/buddy.c
T

286 lines
8.8 KiB
C
Raw Normal View History

2026-05-19 11:49:22 +08:00
#include <buddy.h>
#include <os_macros.h>
#include <string.h>
#include <os_compiler.h>
#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);
}
}