Add comments
This commit is contained in:
@@ -155,23 +155,23 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define BUDDY_DEBUG
|
|
||||||
#ifdef BUDDY_DEBUG
|
|
||||||
#include <stdio.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "buddy.h"
|
#include "buddy.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are just for convenience
|
||||||
|
*/
|
||||||
#define MAX_BLOCK_LOG2 BUDDY_ALLOC_MAX_BLOCK_LOG2
|
#define MAX_BLOCK_LOG2 BUDDY_ALLOC_MAX_BLOCK_LOG2
|
||||||
#define MIN_BLOCK_LOG2 BUDDY_ALLOC_MIN_BLOCK_LOG2
|
#define MIN_BLOCK_LOG2 BUDDY_ALLOC_MIN_BLOCK_LOG2
|
||||||
#define MAX_BLOCK_SIZE (1 << MAX_BLOCK_LOG2)
|
#define MAX_BLOCK_SIZE BUDDY_ALLOC_MAX_BLOCK_LOG2
|
||||||
#define MIN_BLOCK_SIZE (1 << MIN_BLOCK_LOG2)
|
#define MIN_BLOCK_SIZE BUDDY_ALLOC_MIN_BLOCK_LOG2
|
||||||
#define MAX_BLOCK_ALIGN_MASK (MAX_BLOCK_SIZE - 1)
|
#define MAX_BLOCK_ALIGN_MASK (MAX_BLOCK_SIZE - 1)
|
||||||
|
|
||||||
struct page {
|
struct page { struct page *next; };
|
||||||
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*
|
static struct page*
|
||||||
page_index_to_ptr(char *base, int i)
|
page_index_to_ptr(char *base, int i)
|
||||||
{
|
{
|
||||||
@@ -189,6 +189,9 @@ static struct buddy_alloc startup_empty()
|
|||||||
return alloc;
|
return alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See buddy.h
|
||||||
|
*/
|
||||||
struct buddy_alloc buddy_startup(char *base, size_t size,
|
struct buddy_alloc buddy_startup(char *base, size_t size,
|
||||||
struct page_info *info,
|
struct page_info *info,
|
||||||
int num_info)
|
int num_info)
|
||||||
@@ -197,29 +200,28 @@ struct buddy_alloc buddy_startup(char *base, size_t size,
|
|||||||
return startup_empty();
|
return startup_empty();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ad some padding to the start of the
|
* Calculate the padding necessary to align the base pointer
|
||||||
* memory pool to align at a page boundary.
|
* 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;
|
size_t pad = -(uintptr_t) base & MAX_BLOCK_ALIGN_MASK;
|
||||||
|
|
||||||
if (pad > size) {
|
if (pad > size)
|
||||||
/*
|
|
||||||
* Pool doesn't even have a page
|
|
||||||
*/
|
|
||||||
return startup_empty();
|
return startup_empty();
|
||||||
}
|
|
||||||
|
|
||||||
base += pad;
|
base += pad;
|
||||||
size -= 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_t rem = size & MAX_BLOCK_ALIGN_MASK;
|
||||||
size -= rem;
|
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;
|
size_t max_bytes = (size_t) num_info << MAX_BLOCK_LOG2;
|
||||||
if (size > max_bytes)
|
if (size > max_bytes)
|
||||||
@@ -238,6 +240,10 @@ struct buddy_alloc buddy_startup(char *base, size_t size,
|
|||||||
}
|
}
|
||||||
*tail = NULL;
|
*tail = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the page info. The page_info bits are 0 when
|
||||||
|
* blocks are unused, so they start at zero.
|
||||||
|
*/
|
||||||
assert(info);
|
assert(info);
|
||||||
memset(info, 0, num_info * sizeof(struct page_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.base = base,
|
||||||
alloc.info = info;
|
alloc.info = info;
|
||||||
alloc.num_info = num_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++)
|
for (int i = 0; i < BUDDY_ALLOC_NUM_LISTS-1; i++)
|
||||||
alloc.lists[i] = NULL;
|
alloc.lists[i] = NULL;
|
||||||
alloc.lists[BUDDY_ALLOC_NUM_LISTS-1] = head;
|
alloc.lists[BUDDY_ALLOC_NUM_LISTS-1] = head;
|
||||||
@@ -252,16 +260,27 @@ struct buddy_alloc buddy_startup(char *base, size_t size,
|
|||||||
return alloc;
|
return alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See buddy.h
|
||||||
|
*/
|
||||||
void buddy_cleanup(struct buddy_alloc *alloc)
|
void buddy_cleanup(struct buddy_alloc *alloc)
|
||||||
{
|
{
|
||||||
(void) 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)
|
static bool is_pow2(size_t n)
|
||||||
{
|
{
|
||||||
return (n & (n-1)) == 0;
|
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)
|
static size_t round_pow2(size_t v)
|
||||||
{
|
{
|
||||||
v--;
|
v--;
|
||||||
@@ -276,30 +295,10 @@ static size_t round_pow2(size_t v)
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int first_set_8(uint8_t x)
|
/*
|
||||||
{
|
* Returns the index of the first set bit of x. The index of the
|
||||||
static const unsigned char table[] = {
|
* least significant bit is 0. If no bit is set, the result is -1.
|
||||||
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.
|
|
||||||
static int first_set(size_t x)
|
static int first_set(size_t x)
|
||||||
{
|
{
|
||||||
size_t y;
|
size_t y;
|
||||||
@@ -310,10 +309,36 @@ static int first_set(size_t x)
|
|||||||
// First check that at least one bit is set
|
// First check that at least one bit is set
|
||||||
if (x == 0) return -1;
|
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);
|
y = x & (x - 1);
|
||||||
z = x - y;
|
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;
|
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) {
|
if (sizeof(size_t) > 4) {
|
||||||
t = z >> 32;
|
t = z >> 32;
|
||||||
if (t) {
|
if (t) {
|
||||||
@@ -334,7 +359,27 @@ static int first_set(size_t x)
|
|||||||
z = t;
|
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;
|
return i;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,42 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.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_MAX_BLOCK_LOG2 13
|
||||||
#define BUDDY_ALLOC_MIN_BLOCK_LOG2 3
|
#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);
|
_Static_assert(BUDDY_ALLOC_MIN_BLOCK_LOG2 > 2);
|
||||||
|
|
||||||
#define BUDDY_ALLOC_NUM_LISTS (BUDDY_ALLOC_MAX_BLOCK_LOG2 - BUDDY_ALLOC_MIN_BLOCK_LOG2 + 1)
|
#define BUDDY_ALLOC_NUM_LISTS (BUDDY_ALLOC_MAX_BLOCK_LOG2 - BUDDY_ALLOC_MIN_BLOCK_LOG2 + 1)
|
||||||
|
#define BUDDY_ALLOC_MAX_BLOCK_SIZE (1U << BUDDY_ALLOC_MAX_BLOCK_LOG2)
|
||||||
// To keep track of the allocation state of a page,
|
#define BUDDY_ALLOC_MIN_BLOCK_SIZE (1U << BUDDY_ALLOC_MIN_BLOCK_LOG2)
|
||||||
// we need one bit for each possible block that can
|
/*
|
||||||
// be made out of it. For instance, if the page can
|
* To keep track of the allocation state of a page,
|
||||||
// only be allocated in its entirety, 1 bit is required.
|
* we need one bit for each possible block that can
|
||||||
// If the blocks halfs can be allocated too, 3 bits
|
* be made out of it. For instance, if the page can
|
||||||
// are required: 1 for the page, 1 for the frist half
|
* only be allocated in its entirety, 1 bit is required.
|
||||||
// and 1 for the second half. Allowing the allocation
|
* If the blocks halfs can be allocated too, 3 bits
|
||||||
// of page quarters requires 4 more bits, for a total
|
* are required: 1 for the page, 1 for the frist half
|
||||||
// of 7. In general, if we allow splitting a page N
|
* and 1 for the second half. Allowing the allocation
|
||||||
// times (N=0 means only the entire page can be allocated),
|
* of page quarters requires 4 more bits, for a total
|
||||||
// then 2^(N+1)-1 bits are necessary.
|
* 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_BITS_PER_PAGE ((1U << (BUDDY_ALLOC_NUM_LISTS)) - 1)
|
||||||
#define BUDDY_ALLOC_WORDS_PER_PAGE ((BUDDY_ALLOC_BITS_PER_PAGE + 31) / 32)
|
#define BUDDY_ALLOC_WORDS_PER_PAGE ((BUDDY_ALLOC_BITS_PER_PAGE + 31) / 32)
|
||||||
|
|
||||||
struct page_info {
|
struct page_info {
|
||||||
uint32_t bits[BUDDY_ALLOC_WORDS_PER_PAGE];
|
uint32_t bits[BUDDY_ALLOC_WORDS_PER_PAGE];
|
||||||
};
|
};
|
||||||
@@ -34,7 +72,37 @@ struct buddy_alloc {
|
|||||||
int num_info;
|
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);
|
struct buddy_alloc buddy_startup(char *base, size_t size, struct page_info *page_info, int num_page_info);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Deinitialize the allocator.
|
||||||
|
*/
|
||||||
void buddy_cleanup(struct buddy_alloc *alloc);
|
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);
|
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);
|
void buddy_free(struct buddy_alloc *alloc, size_t len, void *ptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user