StuBS
|
Buddy Allocator Template. More...
#include <alloc_buddy.h>
Classes | |
struct | List |
Free List structure Free lists are stored as circular doubly-linked lists. Every possible allocation size has an associated free list that is threaded through all currently free blocks of that size. That means MIN_ALLOC must be at least "sizeof(List)". More... | |
Public Member Functions | |
uintptr_t | malloc (size_t request) |
Allocate uninitialized memory. | |
void | free (uintptr_t ptr) |
Free allocated memory. | |
size_t | size (uintptr_t ptr) |
Retrieve the allocated size. | |
Private Member Functions | |
uintptr_t | ptr_ceil (uintptr_t ptr) const |
Round pointer to next BLOCK_SIZE aligned address. | |
bool | update_max_ptr (uintptr_t new_value) |
Prepare request for more reserved memory. | |
List * | ptr_for_node (size_t index, size_t bucket) const |
Map from the index of a node to the address of memory that node represents. The bucket can be derived from the index using a loop but is required to be provided here since having them means we can avoid the loop and have this function return in constant time. | |
size_t | node_for_ptr (uintptr_t ptr, size_t bucket) const |
Map from an address of memory to the node that represents that address. There are often many nodes that all map to the same address, so the bucket is needed to uniquely identify a node. | |
int | parent_is_split (size_t index) const |
Get the "is split" flag. | |
void | flip_parent_is_split (size_t index) |
Flip the "is split" flag of the parent. | |
size_t | bucket_for_request (size_t request) const |
Get the smallest bucket for the requested size. | |
Private Attributes | |
List | buckets [BUCKET_COUNT] |
Allocation buckets Each bucket corresponds to a certain allocation size and stores a free list for that size. The bucket at index 0 corresponds to an allocation size of MAX_ALLOC (i.e. the whole address space). | |
size_t | bucket_limit |
Allocation size We could initialize the allocator by giving it one free block the size of the entire address space. However, this would cause us to instantly reserve half of the entire address space on the first allocation, since the first split would store a free list entry at the start of the right child of the root. Instead, we have the tree start out small and grow the size of the tree as we use more memory. The size of the tree is tracked by this value. | |
uint8_t | node_is_split [(1<<(BUCKET_COUNT - 1))/8] |
binary tree This array represents a linearized binary tree of bits. Every possible allocation larger than MIN_ALLOC has a node in this tree (and therefore a bit in this array). | |
uintptr_t | base_ptr |
Start of reserved memory This is the starting address of the address range for this allocator. Every returned allocation will be an offset of this pointer from 0 to MAX_ALLOC. | |
uintptr_t | max_ptr |
Current end of reserved memory This is the maximum address that has ever been used by the allocator. It's used to know when to call RESERVED function to request more memory. | |
Static Private Attributes | |
static const size_t | HEADER_SIZE = sizeof(size_t) |
Header Every allocation needs an header to store the allocation size while staying aligned. The address returned by "malloc" is the address right after this header (i.e. the size occupies the size_t before the returned address). | |
static const size_t | MIN_ALLOC = static_cast<size_t>(1) << MIN_ALLOC_LOG2 |
Minimum allocation size The minimum allocation size is at least twice the header size. | |
static const size_t | MAX_ALLOC = static_cast<size_t>(1) << MAX_ALLOC_LOG2 |
Maximum total allocation size This is the total size of the heap. | |
static const size_t | BUCKET_COUNT = MAX_ALLOC_LOG2 - MIN_ALLOC_LOG2 + 1 |
Number of allocation buckets Allocations are done in powers of two starting from MIN_ALLOC and ending at MAX_ALLOC inclusive. Each allocation size has a bucket that stores the free list for that allocation size. | |
Buddy Allocator Template.
This class implements a buddy memory allocator, which is an allocator that allocates memory within a fixed linear address range. It spans the address range with a binary tree that tracks free space. Both malloc
and free
are O(log N) time where N is the maximum possible number of allocations.
The "buddy" term comes from how the tree is used. When memory is allocated, nodes in the tree are split recursively until a node of the appropriate size is reached. Every split results in two child nodes, each of which is the buddy of the other. When a node is freed, the node and its buddy can be merged again if the buddy is also free. This makes the memory available for larger allocations again.
MIN_ALLOC_LOG2 | binary logarithm value of the minimal allocation size, has to be at least 4 (= 16 bytes) |
MAX_ALLOC_LOG2 | binary logarithm value of the maximum total memory which is allocatable |
BLOCK_SIZE | allocation size will be a multiple of block size. |
RESERVE | Callback function to reserve memory for allocator. First parameter is the start address (on the first call it will be nullptr and the function has to find an adequate position, for all subsequent calls the given address inevitably has to be the start address for the requested memory.) The second parameter is the requested size, always a multiple of BLOCK_SIZE . Returns a pointer to the start address if the requested memory was successfully reserved, otherwise nullptr. |
|
inlineprivate |
Get the smallest bucket for the requested size.
the | requested size passed to malloc |
index | of the smallest bucket that can fit that size |
|
inlineprivate |
Flip the "is split" flag of the parent.
index | the index of a node |
|
inline |
Free allocated memory.
ptr | pointer to the start of a memory allocated using malloc |
|
inline |
Allocate uninitialized memory.
request | the size for the memory |
|
inlineprivate |
Map from an address of memory to the node that represents that address. There are often many nodes that all map to the same address, so the bucket is needed to uniquely identify a node.
ptr | memory address |
bucket | bucket index |
|
inlineprivate |
Get the "is split" flag.
index | the index of a node |
|
inlineprivate |
Round pointer to next BLOCK_SIZE aligned address.
ptr | pointer |
|
inlineprivate |
Map from the index of a node to the address of memory that node represents. The bucket can be derived from the index using a loop but is required to be provided here since having them means we can avoid the loop and have this function return in constant time.
index | index of the node |
bucket | bucket index |
|
inline |
Retrieve the allocated size.
ptr pointer to the start of the allocated memory
|
inlineprivate |
Prepare request for more reserved memory.
new_value | the requested new end for the reserved memory – all addresses from base_ptr to new_value have to be valid reserved addresses for the allocator. |
true
if the memory could be reserved, false
otherwise
|
staticprivate |
Number of allocation buckets Allocations are done in powers of two starting from MIN_ALLOC and ending at MAX_ALLOC inclusive. Each allocation size has a bucket that stores the free list for that allocation size.
Given a bucket index, the size of the allocations in that bucket can be found with "(size_t)1 << (MAX_ALLOC_LOG2 - bucket)".
|
private |
binary tree This array represents a linearized binary tree of bits. Every possible allocation larger than MIN_ALLOC has a node in this tree (and therefore a bit in this array).
Given the index for a node, linearized binary trees allow you to traverse to the parent node or the child nodes just by doing simple arithmetic on the index:
Each node in this tree can be in one of several states:
These states take two bits to store. However, it turns out we have enough information to distinguish between UNUSED and USED from context, so we only need to store SPLIT or not, which only takes a single bit.
Note that we don't need to store any nodes for allocations of size MIN_ALLOC since we only ever care about parent nodes.