diff --git a/buddy.c b/buddy.c index 85ba591..91b1063 100644 --- a/buddy.c +++ b/buddy.c @@ -155,23 +155,23 @@ #include #include -#define BUDDY_DEBUG -#ifdef BUDDY_DEBUG -#include -#endif - #include "buddy.h" +/* + * These are just for convenience + */ #define MAX_BLOCK_LOG2 BUDDY_ALLOC_MAX_BLOCK_LOG2 #define MIN_BLOCK_LOG2 BUDDY_ALLOC_MIN_BLOCK_LOG2 -#define MAX_BLOCK_SIZE (1 << MAX_BLOCK_LOG2) -#define MIN_BLOCK_SIZE (1 << MIN_BLOCK_LOG2) +#define MAX_BLOCK_SIZE BUDDY_ALLOC_MAX_BLOCK_LOG2 +#define MIN_BLOCK_SIZE BUDDY_ALLOC_MIN_BLOCK_LOG2 #define MAX_BLOCK_ALIGN_MASK (MAX_BLOCK_SIZE - 1) -struct page { - struct page *next; -}; +struct page { struct page *next; }; +/* + * Gets the address of the i-th page of the memory pool. + * In this context, a page is a block of size MAX_BLOCK_SIZE. + */ static struct page* page_index_to_ptr(char *base, int i) { @@ -189,6 +189,9 @@ static struct buddy_alloc startup_empty() return alloc; } +/* + * See buddy.h + */ struct buddy_alloc buddy_startup(char *base, size_t size, struct page_info *info, int num_info) @@ -197,29 +200,28 @@ struct buddy_alloc buddy_startup(char *base, size_t size, return startup_empty(); /* - * Ad some padding to the start of the - * memory pool to align at a page boundary. + * Calculate the padding necessary to align the base pointer + * to MAX_BLOCK_SIZE. If the padding is greater than the size + * of the pool not even one aligned page was provided so the + * allocator is basically empty. */ size_t pad = -(uintptr_t) base & MAX_BLOCK_ALIGN_MASK; - if (pad > size) { - /* - * Pool doesn't even have a page - */ + if (pad > size) return startup_empty(); - } base += pad; size -= pad; /* - * Make the size a multiple of 4K + * Discard any bites from the end of the pool that don't + * make up an entire block. */ size_t rem = size & MAX_BLOCK_ALIGN_MASK; size -= rem; /* - * Each page requires a bitset to keep track of its state + * Discard blocks for which there isn't a page_info structure. */ size_t max_bytes = (size_t) num_info << MAX_BLOCK_LOG2; if (size > max_bytes) @@ -238,6 +240,10 @@ struct buddy_alloc buddy_startup(char *base, size_t size, } *tail = NULL; + /* + * Initialize the page info. The page_info bits are 0 when + * blocks are unused, so they start at zero. + */ assert(info); memset(info, 0, num_info * sizeof(struct page_info)); @@ -245,6 +251,8 @@ struct buddy_alloc buddy_startup(char *base, size_t size, alloc.base = base, alloc.info = info; alloc.num_info = num_info; + + // All lists are empty except for the one of larger chunks for (int i = 0; i < BUDDY_ALLOC_NUM_LISTS-1; i++) alloc.lists[i] = NULL; alloc.lists[BUDDY_ALLOC_NUM_LISTS-1] = head; @@ -252,16 +260,27 @@ struct buddy_alloc buddy_startup(char *base, size_t size, return alloc; } +/* + * See buddy.h + */ void buddy_cleanup(struct buddy_alloc *alloc) { (void) alloc; } +/* + * Returns true iff n is a power of 2. To understand how this works, + * refer to the comment at start of the file. + */ static bool is_pow2(size_t n) { return (n & (n-1)) == 0; } +/* + * Returns the first power of 2 that comes after v, of v if its + * already a power of 2. + */ static size_t round_pow2(size_t v) { v--; @@ -276,30 +295,10 @@ static size_t round_pow2(size_t v) return v; } -static int first_set_8(uint8_t x) -{ - 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, - }; - return table[x]; -} - -// Returns the index from the right of the first set bit or -1 otherwise. +/* + * Returns the index of the first set bit of x. The index of the + * least significant bit is 0. If no bit is set, the result is -1. + */ static int first_set(size_t x) { size_t y; @@ -310,10 +309,36 @@ static int first_set(size_t x) // First check that at least one bit is set if (x == 0) return -1; + // Subtracting 1 from x lowers the less significan bit and + // sets all zeros that come before it: + // + // x = 1010 0100 + // x-1 = 1010 0011 + // + // So and-ing x and x-1 removes the less significant bit + // of x: + // + // x = 1010 0100 + // x & (x-1) = 1010 0000 + // + // Subtracting from x its version without the lower bit, + // leavs that bit only. y = x & (x - 1); z = x - y; + + // At this point z has the less significant bit set only, + // and we need to find its index. We do so with a binary + // search, which requires a number of "steps" equal to the + // log2 of the number of bits in x. Each step consists of + // testing the upper half of the bit group and, if the test + // is positive and the upper half contains the set bit, add + // to the index the half the number of bits of the group + // and swap the low half with the high half. This is done + // until down to 8 bits. The last byte is done using a table. i = 0; + // The size_t can be 8 or 4 bytes. If it's 8 bytes we need + // to do one more step. if (sizeof(size_t) > 4) { t = z >> 32; if (t) { @@ -334,7 +359,27 @@ static int first_set(size_t x) z = t; } - i += first_set_8(z); + // Table associating all powers of 2 lower than 256 and their + // logarithm, which is also the index of the set bit. + 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, + }; + i += table[z]; return i; } diff --git a/buddy.h b/buddy.h index 586f079..a0bdee1 100644 --- a/buddy.h +++ b/buddy.h @@ -1,6 +1,42 @@ #include #include +/* === INTRODUCTION === + * This is the implementation of a general purpose allocator that uses + * the "buddy system". It uses a pool of memory specified by the user + * and allows allocations up to a specified threshold. + * + * === THE BUDDY SYSTEM === + * The buddy system is an allocator that puts memory regions available + * for allocation in buckets based on their size. Each bucket contains + * regions of a different power of 2. When the user request the allocation + * of a region of a given length, the allocator looks for an unused + * region from the appropriate bucket (the one containing the smallest + * regions that aren't smaller of the requested size) and returns it. + * If the bucket is empty, the allocator gets one from the list of larger + * blocks and splits it. One half is returned to the user and the other + * is put in the bucket. These two blocks that were split from one larger + * block are called "buddies". When deallocating a block, the allocator + * checks if its "buddy" is currently used. If it's not, it merges the + * buddies and puts the larger block in the bucket. If the buddy is used, + * only the region provided by the user is put in the bucket. This mechanism + * is recursive, so if two buddies of size N can be merged, the allocator + * now looks for the buddy of size 2N and so on until either a buddy is + * in use or it got to the largest block possible. The same goes for the + * allocation code. + */ + + +/* + * This is the minimum and maximum block size. The allocator uses free + * lists to keep track of unused blocks, so a block must be at least + * the size of a pointer. We assume a pointer is 8 bytes long, so the + * minimum value must be greater or equal to 3 (log2(8) = 3). + * + * For the maximum value there is really no downside in making it big, + * except for the fact that the pool provided by the user should at + * least be that big. + */ #define BUDDY_ALLOC_MAX_BLOCK_LOG2 13 #define BUDDY_ALLOC_MIN_BLOCK_LOG2 3 @@ -8,21 +44,23 @@ _Static_assert(BUDDY_ALLOC_MIN_BLOCK_LOG2 <= BUDDY_ALLOC_MAX_BLOCK_LOG2); _Static_assert(BUDDY_ALLOC_MIN_BLOCK_LOG2 > 2); #define BUDDY_ALLOC_NUM_LISTS (BUDDY_ALLOC_MAX_BLOCK_LOG2 - BUDDY_ALLOC_MIN_BLOCK_LOG2 + 1) - -// To keep track of the allocation state of a page, -// we need one bit for each possible block that can -// be made out of it. For instance, if the page can -// only be allocated in its entirety, 1 bit is required. -// If the blocks halfs can be allocated too, 3 bits -// are required: 1 for the page, 1 for the frist half -// and 1 for the second half. Allowing the allocation -// of page quarters requires 4 more bits, for a total -// of 7. In general, if we allow splitting a page N -// times (N=0 means only the entire page can be allocated), -// then 2^(N+1)-1 bits are necessary. +#define BUDDY_ALLOC_MAX_BLOCK_SIZE (1U << BUDDY_ALLOC_MAX_BLOCK_LOG2) +#define BUDDY_ALLOC_MIN_BLOCK_SIZE (1U << BUDDY_ALLOC_MIN_BLOCK_LOG2) +/* + * To keep track of the allocation state of a page, + * we need one bit for each possible block that can + * be made out of it. For instance, if the page can + * only be allocated in its entirety, 1 bit is required. + * If the blocks halfs can be allocated too, 3 bits + * are required: 1 for the page, 1 for the frist half + * and 1 for the second half. Allowing the allocation + * of page quarters requires 4 more bits, for a total + * of 7. In general, if we allow splitting a page N + * times (N=0 means only the entire page can be allocated), + * then 2^(N+1)-1 bits are necessary. + */ #define BUDDY_ALLOC_BITS_PER_PAGE ((1U << (BUDDY_ALLOC_NUM_LISTS)) - 1) #define BUDDY_ALLOC_WORDS_PER_PAGE ((BUDDY_ALLOC_BITS_PER_PAGE + 31) / 32) - struct page_info { uint32_t bits[BUDDY_ALLOC_WORDS_PER_PAGE]; }; @@ -34,7 +72,37 @@ struct buddy_alloc { int num_info; }; +/* + * Initialize the allocator. + * + * The allocator will use as allocation memory the [size] + * bytes as position [base]. If the memory pool isn't + * aligned to BUDDY_ALLOC_MAX_BLOCK_SIZE, the first bytes + * are discarded. + * + * The user needs to provide the allocator with an array + * of [struct page_info] with a capacity equal to the number + * of (aligned) BUDDY_ALLOC_MAX_BLOCK_SIZE blocks in the + * pool. If less page_info structs are provided than necessary + * for the pool, the exceeding portion of the pool is + * discarded. + */ struct buddy_alloc buddy_startup(char *base, size_t size, struct page_info *page_info, int num_page_info); -void buddy_cleanup(struct buddy_alloc *alloc); -void *buddy_malloc (struct buddy_alloc *alloc, size_t len); -void buddy_free (struct buddy_alloc *alloc, size_t len, void *ptr); + +/* + * Deinitialize the allocator. + */ +void buddy_cleanup(struct buddy_alloc *alloc); + +/* + * Allocate a memory region of size [len]. If allocation + * fails, NULL is returned. + */ +void *buddy_malloc(struct buddy_alloc *alloc, size_t len); + +/* + * Deallocate a memory region allocated using [buddy_malloc]. + * The [len] argument must be the same value passed when + * allocating. + */ +void buddy_free(struct buddy_alloc *alloc, size_t len, void *ptr);