mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 08:24:32 -08:00
Adding address manager
This commit is contained in:
@@ -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
|
||||||
|
|||||||
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 * 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
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;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user