Adding address manager

This commit is contained in:
Justin C. Miller
2019-02-22 19:15:55 -08:00
parent a9ac30b991
commit f9d964cccb
7 changed files with 337 additions and 19 deletions

View File

@@ -87,6 +87,7 @@ kutil:
includes: includes:
- src/libraries/kutil/include - src/libraries/kutil/include
source: source:
- src/libraries/kutil/address_manager.cpp
- src/libraries/kutil/assert.cpp - src/libraries/kutil/assert.cpp
- src/libraries/kutil/heap_manager.cpp - src/libraries/kutil/heap_manager.cpp
- src/libraries/kutil/memory.cpp - src/libraries/kutil/memory.cpp
@@ -108,6 +109,7 @@ tests:
deps: deps:
- kutil - kutil
source: source:
- src/tests/address_manager.cpp
- src/tests/linked_list.cpp - src/tests/linked_list.cpp
- src/tests/heap_manager.cpp - src/tests/heap_manager.cpp
- src/tests/main.cpp - src/tests/main.cpp

View 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

View 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

View File

@@ -189,10 +189,7 @@ public:
item_type * pop_front() item_type * pop_front()
{ {
item_type *item = m_head; item_type *item = m_head;
if (m_head) { remove(item);
m_head = item->m_next;
item->m_next = nullptr;
}
return item; return item;
} }
@@ -201,10 +198,7 @@ public:
item_type * pop_back() item_type * pop_back()
{ {
item_type *item = m_tail; item_type *item = m_tail;
if (m_tail) { remove(item);
m_tail = item->m_prev;
item->m_prev = nullptr;
}
return item; return item;
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
/// \file slab_allocator.h /// \file slab_allocator.h
/// A slab allocator and related definitions /// A slab allocator and related definitions
#include "kutil/assert.h"
#include "kutil/linked_list.h" #include "kutil/linked_list.h"
#include "kutil/memory.h" #include "kutil/memory.h"
@@ -29,6 +30,7 @@ public:
inline item_type * pop() inline item_type * pop()
{ {
if (this->empty()) allocate(); if (this->empty()) allocate();
kassert(!this->empty(), "Slab allocator is empty after allocate()");
item_type *item = this->pop_front(); item_type *item = this->pop_front();
kutil::memset(item, 0, sizeof(item_type)); kutil::memset(item, 0, sizeof(item_type));
return item; return item;

View 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();
}

View File

@@ -11,10 +11,10 @@
using namespace kutil; using namespace kutil;
std::vector<void *> memory; static std::vector<void *> memory;
size_t total_alloc_size = 0; static size_t total_alloc_size = 0;
size_t total_alloc_calls = 0; static size_t total_alloc_calls = 0;
const size_t hs = 0x10; // header size const size_t hs = 0x10; // header size
const size_t max_block = 1 << 16; const size_t max_block = 1 << 16;
@@ -33,6 +33,13 @@ void * grow_callback(void *start, size_t length)
return p; 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]" ) 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 // And we should have gotten back the start of memory
CHECK( big == offset_pointer(memory[0], hs) ); CHECK( big == offset_pointer(memory[0], hs) );
for (void *p : memory) ::free(p); free_memory();
memory.clear();
total_alloc_size = 0;
total_alloc_calls = 0;
} }
bool check_in_memory(void *p) bool check_in_memory(void *p)
@@ -182,9 +186,6 @@ TEST_CASE( "Non-contiguous blocks tests", "[memory buddy]" )
for (void *p : allocs) for (void *p : allocs)
CHECK( check_in_memory(p) ); CHECK( check_in_memory(p) );
for (void *p : memory) ::free(p); free_memory();
memory.clear();
total_alloc_size = 0;
total_alloc_calls = 0;
} }