170 lines
5.6 KiB
C
170 lines
5.6 KiB
C
#include <c_Buddy.h>
|
|
#include <c_Alignment.h>
|
|
#include <c_Memory.h>
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
C_STATIC_FORCE_INLINE
|
|
c_bool_t is_power_of_two(c_size_t x) {
|
|
return (x & (x-1)) == 0;
|
|
}
|
|
|
|
C_STATIC_FORCE_INLINE
|
|
c_BuddyBlock_t* buddy_block_next(c_BuddyBlock_t* block) {
|
|
return (c_BuddyBlock_t*)((uint8_t*)block + block->size);
|
|
}
|
|
|
|
static c_BuddyBlock_t *buddy_block_split(c_BuddyBlock_t *block, const int size) {
|
|
if (block != NULL && size != 0) {
|
|
// 當目標大小小於當前區塊時,進行對半裂變
|
|
while (size < block->size) {
|
|
int sz = block->size >> 1;
|
|
block->size = sz;
|
|
|
|
// 裂變出的右半邊(Right Buddy)設為空閒
|
|
c_BuddyBlock_t* right_buddy = buddy_block_next(block);
|
|
right_buddy->size = sz;
|
|
right_buddy->is_free = C_TRUE;
|
|
|
|
// 經典夥伴系統優先分配左半邊(Left Buddy),因此 block 指針保持在左側繼續循環
|
|
}
|
|
|
|
if (size <= block->size) {
|
|
return block;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static
|
|
c_BuddyBlock_t *buddy_block_find_best(c_BuddyBlock_t *head, c_BuddyBlock_t *tail, int size) {
|
|
c_BuddyBlock_t *best_block = NULL;
|
|
c_BuddyBlock_t *curr = head;
|
|
|
|
while (curr < tail) {
|
|
if (curr->is_free && curr->size >= size) {
|
|
// 尋找符合條件且最小的區塊(Best Fit),減少外部碎片
|
|
if (best_block == NULL || curr->size < best_block->size) {
|
|
best_block = curr;
|
|
}
|
|
}
|
|
// 精確前進當前區塊的實際大小,不論其內部被切得多碎,都能地毯式掃描
|
|
curr = buddy_block_next(curr);
|
|
}
|
|
|
|
if (best_block != NULL) {
|
|
return buddy_block_split(best_block, size);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
C_STATIC_FORCE_INLINE
|
|
int buddy_block_size_required(c_Buddy_t *b, int size) {
|
|
int actual_size = b->alignment;
|
|
const int total_needed = size + b->alignment; // 預留足夠空間確保 padding 後依然夠用
|
|
|
|
while (actual_size < total_needed) {
|
|
actual_size <<= 1;
|
|
}
|
|
return actual_size;
|
|
}
|
|
|
|
static
|
|
void buddy_block_coalescence(c_BuddyBlock_t *head, c_BuddyBlock_t *tail) {
|
|
for (;;) {
|
|
c_BuddyBlock_t *curr = head;
|
|
c_bool_t consolidated_any = C_FALSE;
|
|
|
|
while (curr < tail) {
|
|
c_BuddyBlock_t *next = buddy_block_next(curr);
|
|
|
|
// 檢查安全邊界
|
|
if (next >= tail) {
|
|
break;
|
|
}
|
|
|
|
// 凝聚條件:兩相鄰區塊皆空閒,且大小相等
|
|
// 注意:在夥伴系統中,左夥伴的偏移量必須是其兩倍大小的整數倍(對齊檢查)
|
|
if (curr->is_free && next->is_free && curr->size == next->size) {
|
|
const c_uintptr_t offset = (c_uintptr_t)curr - (c_uintptr_t)head;
|
|
if ((offset % (curr->size << 1)) == 0) {
|
|
curr->size <<= 1; // 融合!容量翻倍
|
|
consolidated_any = C_TRUE;
|
|
// 融合後,下一次推進會自動從融合後的大區塊末端繼續,不用前進 next
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// 若無法融合,正常步進到下一個區塊
|
|
curr = next;
|
|
}
|
|
|
|
// 如果整輪掃描下來沒有任何區塊可以再融合,代表凝聚完成,退出循環
|
|
if (!consolidated_any) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
void c_Buddy_Init(c_Buddy_t *b, void *data, int size, int alignment) {
|
|
assert(b != NULL);
|
|
assert(data != NULL);
|
|
assert(is_power_of_two(size) && "size is not a power-of-two");
|
|
assert(is_power_of_two(alignment) && "alignment is not a power-of-two");
|
|
|
|
// 確保對齊基準至少能放下一個標頭結構體
|
|
if (alignment < (int)sizeof(c_BuddyBlock_t)) {
|
|
alignment = C_ALIGN_UPB(sizeof(c_BuddyBlock_t), 2); // 向上取 2 的冪次
|
|
while (!is_power_of_two(alignment)) {
|
|
alignment++; // 穩健保險
|
|
}
|
|
}
|
|
assert((c_uintptr_t)data % alignment == 0 && "data is not aligned to minimum alignment");
|
|
|
|
b->alignment = alignment;
|
|
b->head = (c_BuddyBlock_t *)data;
|
|
b->head->size = size;
|
|
b->head->is_free = C_TRUE;
|
|
|
|
// 哨兵結尾
|
|
b->tail = buddy_block_next(b->head);
|
|
}
|
|
|
|
void *c_Buddy_Alloc(c_Buddy_t *b, int size) {
|
|
if (!b || size <= 0) return NULL;
|
|
|
|
int actual_size = buddy_block_size_required(b, size);
|
|
|
|
// 第一階段:尋找現有最合適的空閒塊
|
|
c_BuddyBlock_t *found = buddy_block_find_best(b->head, b->tail, actual_size);
|
|
if (found == NULL) {
|
|
// 第二階段:若找不到,強制進行全面碎片凝聚(Coalesce),再搜尋一次
|
|
buddy_block_coalescence(b->head, b->tail);
|
|
found = buddy_block_find_best(b->head, b->tail, actual_size);
|
|
}
|
|
|
|
if (found != NULL) {
|
|
found->is_free = C_FALSE;
|
|
return (void *)((uint8_t *)found + b->alignment);
|
|
}
|
|
|
|
return NULL; // OOM
|
|
}
|
|
|
|
void c_Buddy_Free(c_Buddy_t *b, void *data) {
|
|
if (data != NULL) {
|
|
assert((c_uintptr_t)b->head <= (c_uintptr_t)data);
|
|
assert((c_uintptr_t)data < (c_uintptr_t)b->tail);
|
|
|
|
c_BuddyBlock_t *block = (c_BuddyBlock_t *) ((uint8_t *) data - b->alignment);
|
|
block->is_free = C_TRUE;
|
|
|
|
// NOTE: Coalescence could be done now but it is optional
|
|
// buddy_block_coalescence(b->head, b->tail);
|
|
}
|
|
}
|