From 60ff6216768f51a8fbf91e36dc9e9446a537ee7f Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Fri, 19 Apr 2024 02:11:41 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + buddy.c | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++ buddy.h | 24 +++ makefile | 3 + test.c | 65 ++++++++ 5 files changed, 555 insertions(+) create mode 100644 .gitignore create mode 100644 buddy.c create mode 100644 buddy.h create mode 100644 makefile create mode 100644 test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0ed3d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +test +test.exe \ No newline at end of file diff --git a/buddy.c b/buddy.c new file mode 100644 index 0000000..c73304e --- /dev/null +++ b/buddy.c @@ -0,0 +1,461 @@ +#include +#include +#include "buddy.h" + +enum { + INDEX_256B, + INDEX_512B, + INDEX_1K, + INDEX_2K, + INDEX_4K, +}; + +struct page { + struct page *next; +}; + +void init_buddy_alloc(struct buddy_alloc *alloc, + char *base, size_t size, + uint32_t *bitsets, + int num_bitsets) +{ + size_t page_size = 1 << 12; + + /* + * Ad some padding to the start of the + * memory pool to align at a page boundary. + */ + size_t pad = -(uintptr_t) base & (page_size - 1); + + if (pad > size) { + /* + * Pool doesn't have a whole page + */ + alloc->base = NULL; + alloc->lists[INDEX_256B] = NULL; + alloc->lists[INDEX_512B] = NULL; + alloc->lists[INDEX_1K] = NULL; + alloc->lists[INDEX_2K] = NULL; + alloc->lists[INDEX_4K] = NULL; + alloc->bitsets = NULL; + alloc->num_bitsets = 0; + return; + } + + base += pad; + size -= pad; + + /* + * Make the size a multiple of 4K + */ + size_t rem = size % page_size; + + size -= rem; + + /* + * Each page requires a bitset to keep track of its state + */ + size_t max_bytes = (size_t) num_bitsets * page_size; + if (size > max_bytes) + size = max_bytes; + + /* + * Make the linked list of pages + */ + struct page *head; + struct page **tail = &head; + size_t num_pages = size / page_size; + + for (size_t i = 0; i < num_pages; i++) { + struct page *p = (struct page*) (base + i * page_size); + *tail = p; + tail = &p->next; + } + *tail = NULL; + + alloc->base = base; + alloc->lists[INDEX_256B] = NULL; + alloc->lists[INDEX_512B] = NULL; + alloc->lists[INDEX_1K] = NULL; + alloc->lists[INDEX_2K] = NULL; + alloc->lists[INDEX_4K] = head; + alloc->bitsets = bitsets; + alloc->num_bitsets = num_bitsets; + + for (int i = 0; i < num_bitsets; i++) + alloc->bitsets[i] = 0; +} + +void free_buddy_alloc(struct buddy_alloc *alloc) +{ + (void) alloc; +} + +static bool is_pow2(size_t n) +{ + return (n & (n-1)) == 0; +} + +static size_t round_pow2(size_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + if (sizeof(v) > 4) + v |= v >> 32; + v++; + return v; +} + +/* + * Returns the index from the right of the + * first set bit or -1 otherwise. + */ +static int first_set_bit(size_t bits) +{ + // First check that at least one bit is set + if (bits == 0) return -1; + + size_t bits_no_rightmost = bits & (bits - 1); + size_t bits_only_rightmost = bits - bits_no_rightmost; + + int index = 0; + size_t temp; + + if (sizeof(size_t) > 4) { + // The index of the rightmost bit is the log2 + temp = bits_only_rightmost >> 32; + if (temp) { + // Bit is in the upper 32 bits + index += 32; + bits_only_rightmost = temp; + } + } + + temp = bits_only_rightmost >> 16; + if (temp) { + index += 16; + bits_only_rightmost = temp; + } + + temp = bits_only_rightmost >> 8; + if (temp) { + index += 8; + bits_only_rightmost = temp; + } + + static const unsigned char table[] = { + 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + index += table[bits_only_rightmost]; + + return index; +} + +void *malloc_internal(struct buddy_alloc *alloc, size_t len) +{ + size_t page_size = 1 << 12; + + if (len > page_size) + return NULL; + + if (len == 0) + return NULL; + + if (len < 256) + len = 256; + else { + len = round_pow2(len); + if (len > page_size) + return NULL; + } + + int i = first_set_bit(len); + + assert(first_set_bit(256) == 8); + int list_idx = i - 8; + + struct page *p = alloc->lists[list_idx]; + + /* + * If there isn't a page of the appropriate size, + * allocate a block twice as big, allocate one half + * and put the other one in a list. + */ + if (p == NULL) { + + char *ptr = malloc_internal(alloc, len << 1); + if (ptr == NULL) + return NULL; + + p = (struct page*) ptr; + p->next = NULL; + + struct page *p2; + p2 = (struct page*) (ptr + len); + p2->next = NULL; + alloc->lists[list_idx] = p2; + + } else { + alloc->lists[list_idx] = p->next; + } + + return p; +} + +static void *get_sibling(void *ptr, size_t len) +{ + size_t double_len = len << 1; + + if ((uintptr_t) ptr & (double_len - 1)) + return ptr + len; + else + return ptr - len; +} + +static void *parent_chunk(void *ptr, size_t len) +{ + void *sib = get_sibling(ptr, len); + if ((uintptr_t) sib < (uintptr_t) ptr) + return sib; + else + return ptr; +} + +static void +append_to_list(struct buddy_alloc *alloc, + void *ptr, size_t len) +{ + assert(is_pow2(len)); + assert(len >= 256); + + int list_idx = first_set_bit(len) - 8; + + assert(list_idx >= 0 && list_idx <= INDEX_4K); + + struct page *p = ptr; + + p->next = alloc->lists[list_idx]; + alloc->lists[list_idx] = p; +} + +void free_internal(struct buddy_alloc *alloc, + void *ptr, size_t len) +{ + if (len < 256) + len = 256; + else { + if (len > 4096) + return; + len = round_pow2(len); + } + + size_t page_size = 1 << 12; + assert(len > 0 && len <= page_size); + + if (len == page_size) { + /* + * Deallocation is easy, just push into + * the last list. + */ + append_to_list(alloc, ptr, len); + return; + } + + /* + * Before placing this chunk in the free list + * look for its sibling and pop it. + * + * If the chunk is aligned to double its size, + * its sibling is the one after it, else it's + * the one before it. + */ + bool found = false; + char *sibling = get_sibling(ptr, len); + { + int list_idx = first_set_bit(len) - 8; + struct page *curs = (struct page*) alloc->lists[list_idx]; + struct page **prev = (struct page**) &alloc->lists[list_idx]; + + while (curs) { + if (curs == (struct page*) sibling) { + *prev = curs->next; + found = true; + break; + } + prev = &curs->next; + curs = curs->next; + } + } + + if (found == false) { + /* + * No sybling so just push this chunk in + * the list. + */ + append_to_list(alloc, ptr, len); + } else { + /* + * Deallocate the larger chunk + */ + struct page *p = parent_chunk(ptr, len); + free_internal(alloc, p, len << 1); + } +} + +uint32_t get_chunk_mask(void *ptr, size_t len) +{ + assert(len > 0); + + len = round_pow2(len); + + size_t page_size = 1 << 12; + + uintptr_t x = (uintptr_t) ptr; + + /* + * Get the bit associated to the chunk + * + * The first bit refers to the entire page, + * the following 2 bits refer to its halfs, + * then the following 4 the halfs of the + * halfs and so on. + */ + + int len_log2 = first_set_bit(len); + assert(len_log2 <= 12); + + // Pointer relative to its page + size_t reloff = x & (page_size - 1); + + size_t chidx = reloff / len; + + size_t sh = 12 - len_log2 + chidx; + + uint32_t mask = 1; + mask <<= sh; + + /* + * Each bit is associated to a chunk. Chunk bits are + * grouped by their size. The bit index of the first + * chunk if its length can be calculated as: + * + * 12 - log2(len) + * + * From there, the bit index is displaced as the chunk + * in the page: + * + * 12 - log2(len) + (ptr - page_ptr) / len + * + * For convenience, here's the list of the bits: + * + * 1 - 4K - 2^12 -> 2^0 + * 2 - 2K - 2^11 -> 2^1 + * 3 - 2K + * 4 - 1K - 2^10 -> 2^2 + * 5 - 1K + * 6 - 1K + * 7 - 1K + * 8 - 512b - 2^9 -> 2^3 + * 9 - 512b + * 10 - 512b + * 11 - 512b + * 12 - 512b + * 13 - 512b + * 14 - 512b + * 15 - 512b + * 16 - 256b - 2^8 -> 2^4 + * 17 - 256b + * 18 - 256b + * 19 - 256b + * 20 - 256b + * 21 - 256b + * 22 - 256b + * 23 - 256b + * 24 - 256b + * 25 - 256b + * 26 - 256b + * 27 - 256b + * 28 - 256b + * 29 - 256b + * 30 - 256b + * 31 - 256b + */ + + return mask; +} + +void set_chunk_state(struct buddy_alloc *alloc, + void *ptr, size_t len, bool used) +{ + len = round_pow2(len); + + size_t page_size = 1 << 12; + + uintptr_t x = (uintptr_t) ptr; + + int page_index = (x - (uintptr_t) alloc->base) / page_size; + + uint32_t mask = get_chunk_mask(ptr, len); + + if (used) + alloc->bitsets[page_index] |= mask; + else + alloc->bitsets[page_index] &= ~mask; +} + +bool get_chunk_state(struct buddy_alloc *alloc, + void *ptr, size_t len) +{ + size_t page_size = 1 << 12; + + uintptr_t x = (uintptr_t) ptr; + + int page_index = (x - (uintptr_t) alloc->base) / page_size; + + uint32_t mask = get_chunk_mask(ptr, len); + + return (alloc->bitsets[page_index] & mask) == mask; +} + +void *buddy_malloc(struct buddy_alloc *alloc, size_t len) +{ + void *ptr = malloc_internal(alloc, len); + if (ptr) + set_chunk_state(alloc, ptr, len, 1); + return ptr; +} + +void buddy_free(struct buddy_alloc *alloc, + void *ptr, size_t len) +{ + if (get_chunk_state(alloc, ptr, len)) { + set_chunk_state(alloc, ptr, len, 0); + free_internal(alloc, ptr, len); + } +} + +bool buddy_allocated(struct buddy_alloc *alloc, + void *ptr, size_t len) +{ + return get_chunk_state(alloc, ptr, len); +} diff --git a/buddy.h b/buddy.h new file mode 100644 index 0000000..1ad11ee --- /dev/null +++ b/buddy.h @@ -0,0 +1,24 @@ +#include +#include + +struct buddy_alloc { + void *base; + void *lists[5]; + uint32_t *bitsets; + int num_bitsets; +}; + +void init_buddy_alloc(struct buddy_alloc *alloc, + char *base, size_t size, + uint32_t *bitsets, int num_bitsets); + +void free_buddy_alloc(struct buddy_alloc *alloc); + +void *buddy_malloc(struct buddy_alloc *alloc, + size_t len); + +void buddy_free(struct buddy_alloc *alloc, + void *ptr, size_t len); + +bool buddy_allocated(struct buddy_alloc *alloc, + void *ptr, size_t len); \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..310df35 --- /dev/null +++ b/makefile @@ -0,0 +1,3 @@ + +all: + gcc test.c buddy.c -o test -Wall -Wextra -ggdb #-fsanitize=address,undefined \ No newline at end of file diff --git a/test.c b/test.c new file mode 100644 index 0000000..30c8b70 --- /dev/null +++ b/test.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include "buddy.h" + +#define NUM_PAGES 32 +#define PAGE_SIZE (1 << 12) +#define MAX_ALLOCS (1 << 14) + +struct alloc_info { + uintptr_t ptr; + uintptr_t len; +}; + +static bool contains_ptr(struct alloc_info *array, int count, void *ptr) +{ + uintptr_t x = (uintptr_t) ptr; + + for (int i = 0; i < count; i++) { + uintptr_t head = array[i].ptr; + uintptr_t tail = array[i].len + head; + if (x >= head && x < tail) + return true; + } + return false; +} + +int main(void) +{ + _Alignas(PAGE_SIZE) char mem[NUM_PAGES * PAGE_SIZE]; + struct buddy_alloc alloc; + uint32_t alloc_bits[NUM_PAGES]; + init_buddy_alloc(&alloc, mem, sizeof(mem), alloc_bits, NUM_PAGES); + + struct alloc_info current_allocs[MAX_ALLOCS]; + int num_current_allocs = 0; + + for (;;) { + + if (num_current_allocs > 0) { + int i = rand() % num_current_allocs; + struct alloc_info deallocating = current_allocs[i]; + current_allocs[i] = current_allocs[--num_current_allocs]; + buddy_free(&alloc, (void*) deallocating.ptr, deallocating.len); + } + + size_t len = 1 + (rand() % PAGE_SIZE); + assert(len > 0 && len <= PAGE_SIZE); + + void *ptr = buddy_malloc(&alloc, len); + if (ptr == NULL) + continue; + + /* + * Check that it isn't currently allocated + */ + assert(contains_ptr(current_allocs, num_current_allocs, ptr) == false); + current_allocs[num_current_allocs++] = (struct alloc_info) {.ptr=(uintptr_t)ptr, .len=len}; + } + + free_buddy_alloc(&alloc); + return 0; +} \ No newline at end of file