Add comments

This commit is contained in:
2024-04-21 21:41:54 +02:00
parent 457bc24019
commit 6c7465845b
2 changed files with 173 additions and 60 deletions
+89 -44
View File
@@ -155,23 +155,23 @@
#include <string.h>
#include <stdbool.h>
#define BUDDY_DEBUG
#ifdef BUDDY_DEBUG
#include <stdio.h>
#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;
}
+84 -16
View File
@@ -1,6 +1,42 @@
#include <stddef.h>
#include <stdint.h>
/* === 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);