#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); }