mirror of
https://github.com/justinian/jsix.git
synced 2025-12-10 00:14:32 -08:00
Overhaul memory allocation model
This commit makes several fundamental changes to memory handling:
- the frame allocator is now only an allocator for free frames, and does
not track used frames.
- the frame allocator now stores its free list inside the free frames
themselves, as a hybrid stack/span model.
- This has the implication that all frames must currently fit within
the offset area.
- kutil has a new allocator interface, which is the only allowed way for
any code outside of src/kernel to allocate. Code under src/kernel
_may_ use new/delete, but should prefer the allocator interface.
- the heap manager has become heap_allocator, which is merely an
implementation of kutil::allocator which doles out sections of a given
address range.
- the heap manager now only writes block headers when necessary,
avoiding page faults until they're actually needed
- page_manager now has a page fault handler, which checks with the
address_manager to see if the address is known, and provides a frame
mapping if it is, allowing heap manager to work with its entire
address size from the start. (Currently 32GiB.)
This commit is contained in:
170
src/tests/heap_allocator.cpp
Normal file
170
src/tests/heap_allocator.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "kutil/memory.h"
|
||||
#include "kutil/heap_allocator.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace kutil;
|
||||
|
||||
const size_t hs = 0x10; // header size
|
||||
const size_t max_block = 1 << 22;
|
||||
|
||||
int signalled = 0;
|
||||
void *signalled_at = nullptr;
|
||||
|
||||
void *mem_base = nullptr;
|
||||
|
||||
std::vector<size_t> sizes = {
|
||||
16000, 8000, 4000, 4000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 150,
|
||||
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 48, 48, 48, 13 };
|
||||
|
||||
void segfault_handler(int signum, siginfo_t *info, void *ctxp)
|
||||
{
|
||||
signalled += 1;
|
||||
signalled_at = info->si_addr;
|
||||
mprotect(signalled_at, max_block, PROT_READ|PROT_WRITE);
|
||||
}
|
||||
|
||||
TEST_CASE( "Buddy blocks tests", "[memory buddy]" )
|
||||
{
|
||||
using clock = std::chrono::system_clock;
|
||||
unsigned seed = clock::now().time_since_epoch().count();
|
||||
std::default_random_engine rng(seed);
|
||||
|
||||
mem_base = aligned_alloc(max_block, max_block * 4);
|
||||
|
||||
// Catch segfaults so we can track memory access
|
||||
struct sigaction sigact;
|
||||
memset(&sigact, 0, sizeof(sigact));
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_NODEFER|SA_SIGINFO;
|
||||
sigact.sa_sigaction = segfault_handler;
|
||||
sigaction(SIGSEGV, &sigact, nullptr);
|
||||
|
||||
// Protect our memory arena so we trigger out fault handler
|
||||
REQUIRE( mprotect(mem_base, max_block*4, PROT_NONE) == 0 );
|
||||
|
||||
heap_allocator mm(
|
||||
reinterpret_cast<uintptr_t>(mem_base),
|
||||
max_block * 4);
|
||||
|
||||
// Initial creation should not have allocated
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
// Allocating should signal just at the first page.
|
||||
void *p = mm.allocate(max_block - hs);
|
||||
CHECK( p == offset_pointer(mem_base, hs) );
|
||||
CHECK( signalled == 1 );
|
||||
CHECK( signalled_at == mem_base );
|
||||
signalled = 0;
|
||||
|
||||
// Freeing and allocating should not allocate
|
||||
mm.free(p);
|
||||
p = mm.allocate(max_block - hs);
|
||||
CHECK( p == offset_pointer(mem_base, hs) );
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
mm.free(p);
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
// Blocks should be:
|
||||
// 22: 0-4M
|
||||
|
||||
std::vector<void *> allocs(6);
|
||||
for (int i = 0; i < 6; ++i)
|
||||
allocs[i] = mm.allocate(150); // size 8
|
||||
|
||||
// Should not have grown
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
// Blocks should be:
|
||||
// 22: [0-4M]
|
||||
// 21: [0-2M], 2-4M
|
||||
// 20: [0-1M], 1-2M
|
||||
// 19: [0-512K], 512K-1M
|
||||
// 18: [0-256K], 256-512K
|
||||
// 17: [0-128K], 128-256K
|
||||
// 16: [0-64K], 64-128K
|
||||
// 15: [0-32K], 32K-64K
|
||||
// 14: [0-16K], 16K-32K
|
||||
// 13: [0-8K], 8K-16K
|
||||
// 12: [0-4K], 4K-8K
|
||||
// 11: [0-2K], 2K-4K
|
||||
// 10: [0-1K, 1-2K]
|
||||
// 9: [0, 512, 1024], 1536
|
||||
// 8: [0, 256, 512, 768, 1024, 1280]
|
||||
|
||||
// We have free memory at 1526 and 2K, but we should get 4K
|
||||
void *big = mm.allocate(4000); // size 12
|
||||
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
REQUIRE( big == offset_pointer(mem_base, 4096 + hs) );
|
||||
mm.free(big);
|
||||
|
||||
// free up 512
|
||||
mm.free(allocs[3]);
|
||||
mm.free(allocs[4]);
|
||||
|
||||
// Blocks should be:
|
||||
// ...
|
||||
// 9: [0, 512, 1024], 1536
|
||||
// 8: [0, 256, 512], 768, 1024, [1280]
|
||||
|
||||
// A request for a 512-block should not cross the buddy divide
|
||||
big = mm.allocate(500); // size 9
|
||||
REQUIRE( big >= offset_pointer(mem_base, 1536 + hs) );
|
||||
mm.free(big);
|
||||
|
||||
mm.free(allocs[0]);
|
||||
mm.free(allocs[1]);
|
||||
mm.free(allocs[2]);
|
||||
mm.free(allocs[5]);
|
||||
allocs.clear();
|
||||
|
||||
std::shuffle(sizes.begin(), sizes.end(), rng);
|
||||
|
||||
allocs.reserve(sizes.size());
|
||||
for (size_t size : sizes)
|
||||
allocs.push_back(mm.allocate(size));
|
||||
|
||||
std::shuffle(allocs.begin(), allocs.end(), rng);
|
||||
for (void *p: allocs)
|
||||
mm.free(p);
|
||||
allocs.clear();
|
||||
|
||||
big = mm.allocate(max_block / 2 + 1);
|
||||
|
||||
// If everything was freed / joined correctly, that should not have allocated
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
// And we should have gotten back the start of memory
|
||||
CHECK( big == offset_pointer(mem_base, hs) );
|
||||
|
||||
// Allocating again should signal at the next page.
|
||||
void *p2 = mm.allocate(max_block - hs);
|
||||
CHECK( p2 == offset_pointer(mem_base, max_block + hs) );
|
||||
CHECK( signalled == 1 );
|
||||
CHECK( signalled_at == offset_pointer(mem_base, max_block) );
|
||||
signalled = 0;
|
||||
|
||||
mm.free(p2);
|
||||
CHECK( signalled == 0 );
|
||||
signalled = 0;
|
||||
|
||||
free(mem_base);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user