Adding address manager
This commit is contained in:
159
src/libraries/kutil/address_manager.cpp
Normal file
159
src/libraries/kutil/address_manager.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "kutil/address_manager.h"
|
||||
#include "kutil/assert.h"
|
||||
|
||||
namespace kutil {
|
||||
|
||||
address_manager::address_manager(uintptr_t start, size_t length)
|
||||
{
|
||||
uintptr_t p = start;
|
||||
unsigned size = size_max;
|
||||
|
||||
while (size >= size_min) {
|
||||
size_t chunk_size = 1ull << size;
|
||||
|
||||
while (p + chunk_size <= start + length) {
|
||||
region_node *r = m_alloc.pop();
|
||||
r->size = size_max;
|
||||
r->address = p;
|
||||
|
||||
free_bucket(size).sorted_insert(r);
|
||||
p += chunk_size;
|
||||
}
|
||||
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
address_manager::region_node *
|
||||
address_manager::split(region_node *reg)
|
||||
{
|
||||
region_node *other = m_alloc.pop();
|
||||
|
||||
other->size = --reg->size;
|
||||
other->address = reg->address + (1ull << reg->size);
|
||||
return other;
|
||||
}
|
||||
|
||||
address_manager::region_node *
|
||||
address_manager::find(uintptr_t p, bool used)
|
||||
{
|
||||
for (unsigned size = size_max; size >= size_min; --size) {
|
||||
auto &list = used ? used_bucket(size) : free_bucket(size);
|
||||
for (auto *f : list) {
|
||||
if (f->address < p) continue;
|
||||
if (f->address == p) return f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
address_manager::allocate(size_t length)
|
||||
{
|
||||
unsigned size = size_min;
|
||||
while ((1ull << size) < length)
|
||||
if (size++ == size_max)
|
||||
return 0;
|
||||
|
||||
unsigned request = size;
|
||||
while (free_bucket(request).empty())
|
||||
if (request++ == size_max)
|
||||
return 0;
|
||||
|
||||
region_node *r = nullptr;
|
||||
while (request > size) {
|
||||
r = free_bucket(request).pop_front();
|
||||
region_node *n = split(r);
|
||||
request--;
|
||||
|
||||
free_bucket(request).sorted_insert(n);
|
||||
if (request != size)
|
||||
free_bucket(request).sorted_insert(r);
|
||||
}
|
||||
|
||||
if (r == nullptr) r = free_bucket(size).pop_front();
|
||||
used_bucket(size).sorted_insert(r);
|
||||
|
||||
return r->address;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
address_manager::mark(uintptr_t start, size_t length)
|
||||
{
|
||||
uintptr_t end = start + length;
|
||||
region_node *found = nullptr;
|
||||
|
||||
for (unsigned i = size_max; i >= size_min && !found; --i) {
|
||||
for (auto *r : free_bucket(i)) {
|
||||
if (start >= r->address && end <= r->end()) {
|
||||
found = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kassert(found, "address_manager::mark called for unknown region");
|
||||
if (!found)
|
||||
return 0;
|
||||
|
||||
while (found->size > size_min) {
|
||||
// Split if the request fits in the second half
|
||||
if (start >= found->half()) {
|
||||
region_node *other = split(found);
|
||||
free_bucket(found->size).sorted_insert(found);
|
||||
found = other;
|
||||
}
|
||||
|
||||
// Split if the request fits in the first half
|
||||
else if (start + length < found->half()) {
|
||||
region_node *other = split(found);
|
||||
free_bucket(other->size).sorted_insert(other);
|
||||
}
|
||||
|
||||
// If neither, we've split as much as possible
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
used_bucket(found->size).sorted_insert(found);
|
||||
return found->address;
|
||||
}
|
||||
|
||||
address_manager::region_node *
|
||||
address_manager::get_buddy(region_node *r)
|
||||
{
|
||||
region_node *b = r->elder() ? r->next() : r->prev();
|
||||
if (b && b->address == r->buddy())
|
||||
return b;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
address_manager::free(uintptr_t p)
|
||||
{
|
||||
region_node *found = find(p, true);
|
||||
|
||||
kassert(found, "address_manager::free called for unknown region");
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
used_bucket(found->size).remove(found);
|
||||
free_bucket(found->size).sorted_insert(found);
|
||||
|
||||
while (auto *bud = get_buddy(found)) {
|
||||
region_node *eld = found->elder() ? found : bud;
|
||||
region_node *other = found->elder() ? bud : found;
|
||||
|
||||
free_bucket(other->size).remove(other);
|
||||
m_alloc.push(other);
|
||||
|
||||
free_bucket(eld->size).remove(eld);
|
||||
eld->size++;
|
||||
free_bucket(eld->size).sorted_insert(eld);
|
||||
|
||||
found = eld;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kutil
|
||||
78
src/libraries/kutil/include/kutil/address_manager.h
Normal file
78
src/libraries/kutil/include/kutil/address_manager.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
/// \file address_manager.h
|
||||
/// The virtual memory address space manager
|
||||
|
||||
#include <stdint.h>
|
||||
#include "kutil/linked_list.h"
|
||||
#include "kutil/slab_allocator.h"
|
||||
|
||||
namespace kutil {
|
||||
|
||||
class address_manager
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/// \arg start Initial address in the managed range
|
||||
/// \arg length Size of the managed range, in bytes
|
||||
address_manager(uintptr_t start, size_t length);
|
||||
|
||||
/// Allocate address space from the managed area.
|
||||
/// \arg length The amount of memory to allocate, in bytes
|
||||
/// \returns The address of the start of the allocated area, or 0 on
|
||||
/// failure
|
||||
uintptr_t allocate(size_t length);
|
||||
|
||||
/// Mark a region as allocated.
|
||||
/// \arg start The start of the region
|
||||
/// \arg length The size of the region, in bytes
|
||||
/// \returns The address of the start of the allocated area, or 0 on
|
||||
/// failure. This may be less than `start`.
|
||||
uintptr_t mark(uintptr_t start, size_t length);
|
||||
|
||||
/// Free a previous allocation.
|
||||
/// \arg p An address previously retuned by allocate()
|
||||
void free(uintptr_t p);
|
||||
|
||||
private:
|
||||
struct region
|
||||
{
|
||||
inline int compare(const region *o) const {
|
||||
return address > o->address ? 1 : \
|
||||
address < o->address ? -1 : 0;
|
||||
}
|
||||
|
||||
inline uintptr_t end() const { return address + (1ull << size); }
|
||||
inline uintptr_t half() const { return address + (1ull << (size - 1)); }
|
||||
inline uintptr_t buddy() const { return address ^ (1ull << size); }
|
||||
inline bool elder() const { return address < buddy(); }
|
||||
|
||||
uint16_t size;
|
||||
uintptr_t address;
|
||||
};
|
||||
|
||||
using region_node = list_node<region>;
|
||||
using region_list = linked_list<region>;
|
||||
|
||||
/// Split a region of the given size into two smaller regions, returning
|
||||
/// the new latter half
|
||||
region_node * split(region_node *reg);
|
||||
|
||||
/// Find a node with the given address
|
||||
region_node * find(uintptr_t p, bool used = true);
|
||||
|
||||
/// Helper to get the buddy for a node, if it's adjacent
|
||||
region_node * get_buddy(region_node *r);
|
||||
|
||||
linked_list<region> & used_bucket(unsigned size) { return m_used[size - size_min]; }
|
||||
linked_list<region> & free_bucket(unsigned size) { return m_free[size - size_min]; }
|
||||
|
||||
static const unsigned size_min = 16; // Min allocation: 64KiB
|
||||
static const unsigned size_max = 36; // Max allocation: 64GiB
|
||||
static const unsigned buckets = (size_max - size_min);
|
||||
|
||||
region_list m_free[buckets];
|
||||
region_list m_used[buckets];
|
||||
slab_allocator<region> m_alloc;
|
||||
};
|
||||
|
||||
} //namespace kutil
|
||||
@@ -189,10 +189,7 @@ public:
|
||||
item_type * pop_front()
|
||||
{
|
||||
item_type *item = m_head;
|
||||
if (m_head) {
|
||||
m_head = item->m_next;
|
||||
item->m_next = nullptr;
|
||||
}
|
||||
remove(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -201,10 +198,7 @@ public:
|
||||
item_type * pop_back()
|
||||
{
|
||||
item_type *item = m_tail;
|
||||
if (m_tail) {
|
||||
m_tail = item->m_prev;
|
||||
item->m_prev = nullptr;
|
||||
}
|
||||
remove(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
/// \file slab_allocator.h
|
||||
/// A slab allocator and related definitions
|
||||
#include "kutil/assert.h"
|
||||
#include "kutil/linked_list.h"
|
||||
#include "kutil/memory.h"
|
||||
|
||||
@@ -29,6 +30,7 @@ public:
|
||||
inline item_type * pop()
|
||||
{
|
||||
if (this->empty()) allocate();
|
||||
kassert(!this->empty(), "Slab allocator is empty after allocate()");
|
||||
item_type *item = this->pop_front();
|
||||
kutil::memset(item, 0, sizeof(item_type));
|
||||
return item;
|
||||
|
||||
82
src/tests/address_manager.cpp
Normal file
82
src/tests/address_manager.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kutil/address_manager.h"
|
||||
#include "kutil/heap_manager.h"
|
||||
#include "kutil/memory.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace kutil;
|
||||
|
||||
extern void * grow_callback(void*, size_t);
|
||||
extern void free_memory();
|
||||
|
||||
const size_t max_block = 1ull << 36;
|
||||
const size_t start = max_block;
|
||||
const size_t GB = 1ull << 30;
|
||||
|
||||
TEST_CASE( "Buddy addresses tests", "[address buddy]" )
|
||||
{
|
||||
heap_manager mm(nullptr, grow_callback);
|
||||
kutil::setup::set_heap(&mm);
|
||||
|
||||
using clock = std::chrono::system_clock;
|
||||
unsigned seed = clock::now().time_since_epoch().count();
|
||||
std::default_random_engine rng(seed);
|
||||
|
||||
address_manager am(start, max_block * 2);
|
||||
|
||||
// Blocks should be:
|
||||
// 36: 0-64G, 64-128G
|
||||
|
||||
uintptr_t a = am.allocate(0x4000); // under 64K min
|
||||
uintptr_t b = am.allocate(0x4000);
|
||||
CHECK( b == a + (1<<16));
|
||||
|
||||
am.free(a);
|
||||
am.free(b);
|
||||
|
||||
// Should be back to
|
||||
// 36: 0-64G, 64-128G
|
||||
|
||||
a = am.allocate(max_block);
|
||||
CHECK(a == start);
|
||||
am.free(a);
|
||||
|
||||
// Should be back to
|
||||
// 36: 0-64G, 64-128G
|
||||
|
||||
// This should allocate the 66-68G block, not the
|
||||
// 66-67G block.
|
||||
a = am.mark( start + 66 * GB + 0x1000, GB );
|
||||
CHECK( a == start + 66 * GB );
|
||||
|
||||
// Free blocks should be:
|
||||
// 36: 0-64G
|
||||
// 35: 96-128G
|
||||
// 34: 80-96G
|
||||
// 33: 72-80G
|
||||
// 32: 68-72G
|
||||
// 31: 64-66G
|
||||
|
||||
// This should cause a split of the one 31 block, NOT
|
||||
// be a leftover of the above mark.
|
||||
b = am.allocate(GB);
|
||||
CHECK( b == start + 64 * GB );
|
||||
am.free(b);
|
||||
am.free(a);
|
||||
|
||||
// Should be back to
|
||||
// 36: 0-64G, 64-128G
|
||||
a = am.allocate(max_block);
|
||||
b = am.allocate(max_block);
|
||||
CHECK( b == start + max_block );
|
||||
CHECK( a == start );
|
||||
|
||||
kutil::setup::set_heap(nullptr);
|
||||
free_memory();
|
||||
}
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
using namespace kutil;
|
||||
|
||||
std::vector<void *> memory;
|
||||
static std::vector<void *> memory;
|
||||
|
||||
size_t total_alloc_size = 0;
|
||||
size_t total_alloc_calls = 0;
|
||||
static size_t total_alloc_size = 0;
|
||||
static size_t total_alloc_calls = 0;
|
||||
|
||||
const size_t hs = 0x10; // header size
|
||||
const size_t max_block = 1 << 16;
|
||||
@@ -33,6 +33,13 @@ void * grow_callback(void *start, size_t length)
|
||||
return p;
|
||||
}
|
||||
|
||||
void free_memory()
|
||||
{
|
||||
for (void *p : memory) ::free(p);
|
||||
memory.clear();
|
||||
total_alloc_size = 0;
|
||||
total_alloc_calls = 0;
|
||||
}
|
||||
|
||||
TEST_CASE( "Buddy blocks tests", "[memory buddy]" )
|
||||
{
|
||||
@@ -114,10 +121,7 @@ TEST_CASE( "Buddy blocks tests", "[memory buddy]" )
|
||||
// And we should have gotten back the start of memory
|
||||
CHECK( big == offset_pointer(memory[0], hs) );
|
||||
|
||||
for (void *p : memory) ::free(p);
|
||||
memory.clear();
|
||||
total_alloc_size = 0;
|
||||
total_alloc_calls = 0;
|
||||
free_memory();
|
||||
}
|
||||
|
||||
bool check_in_memory(void *p)
|
||||
@@ -182,9 +186,6 @@ TEST_CASE( "Non-contiguous blocks tests", "[memory buddy]" )
|
||||
for (void *p : allocs)
|
||||
CHECK( check_in_memory(p) );
|
||||
|
||||
for (void *p : memory) ::free(p);
|
||||
memory.clear();
|
||||
total_alloc_size = 0;
|
||||
total_alloc_calls = 0;
|
||||
free_memory();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user