From f9d964cccb04aeb243340a5d38e86f1a087eb249 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Fri, 22 Feb 2019 19:15:55 -0800 Subject: [PATCH] Adding address manager --- modules.yaml | 2 + src/libraries/kutil/address_manager.cpp | 159 ++++++++++++++++++ .../kutil/include/kutil/address_manager.h | 78 +++++++++ .../kutil/include/kutil/linked_list.h | 10 +- .../kutil/include/kutil/slab_allocator.h | 2 + src/tests/address_manager.cpp | 82 +++++++++ src/tests/heap_manager.cpp | 23 +-- 7 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 src/libraries/kutil/address_manager.cpp create mode 100644 src/libraries/kutil/include/kutil/address_manager.h create mode 100644 src/tests/address_manager.cpp diff --git a/modules.yaml b/modules.yaml index 7fe6979..5e0593a 100644 --- a/modules.yaml +++ b/modules.yaml @@ -87,6 +87,7 @@ kutil: includes: - src/libraries/kutil/include source: + - src/libraries/kutil/address_manager.cpp - src/libraries/kutil/assert.cpp - src/libraries/kutil/heap_manager.cpp - src/libraries/kutil/memory.cpp @@ -108,6 +109,7 @@ tests: deps: - kutil source: + - src/tests/address_manager.cpp - src/tests/linked_list.cpp - src/tests/heap_manager.cpp - src/tests/main.cpp diff --git a/src/libraries/kutil/address_manager.cpp b/src/libraries/kutil/address_manager.cpp new file mode 100644 index 0000000..6dff0fe --- /dev/null +++ b/src/libraries/kutil/address_manager.cpp @@ -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 diff --git a/src/libraries/kutil/include/kutil/address_manager.h b/src/libraries/kutil/include/kutil/address_manager.h new file mode 100644 index 0000000..d5fa8a6 --- /dev/null +++ b/src/libraries/kutil/include/kutil/address_manager.h @@ -0,0 +1,78 @@ +#pragma once +/// \file address_manager.h +/// The virtual memory address space manager + +#include +#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; + using region_list = linked_list; + + /// 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 & used_bucket(unsigned size) { return m_used[size - size_min]; } + linked_list & 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 m_alloc; +}; + +} //namespace kutil diff --git a/src/libraries/kutil/include/kutil/linked_list.h b/src/libraries/kutil/include/kutil/linked_list.h index 3331bf3..9b6b901 100644 --- a/src/libraries/kutil/include/kutil/linked_list.h +++ b/src/libraries/kutil/include/kutil/linked_list.h @@ -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; } diff --git a/src/libraries/kutil/include/kutil/slab_allocator.h b/src/libraries/kutil/include/kutil/slab_allocator.h index 87ad9d5..6b55694 100644 --- a/src/libraries/kutil/include/kutil/slab_allocator.h +++ b/src/libraries/kutil/include/kutil/slab_allocator.h @@ -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; diff --git a/src/tests/address_manager.cpp b/src/tests/address_manager.cpp new file mode 100644 index 0000000..7150f75 --- /dev/null +++ b/src/tests/address_manager.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include + +#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(); +} diff --git a/src/tests/heap_manager.cpp b/src/tests/heap_manager.cpp index 6ce4481..fb9371e 100644 --- a/src/tests/heap_manager.cpp +++ b/src/tests/heap_manager.cpp @@ -11,10 +11,10 @@ using namespace kutil; -std::vector memory; +static std::vector 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(); }