diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 12f1a041..5fe6a323 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES src/main.cpp src/video/Framebuffer.cpp src/memory/MemoryManager.cpp + src/memory/Heap.cpp src/boot/Init.cpp src/arch/Serial.cpp src/arch/Timer.cpp diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 6229e480..e328b55f 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -3,6 +3,7 @@ #include "arch/Serial.h" #include "arch/Timer.h" #include "boot/Init.h" +#include "memory/Heap.h" #include "memory/MemoryManager.h" #include "video/Framebuffer.h" @@ -101,6 +102,16 @@ extern "C" [[noreturn]] void _start() usize start = 0; + int* mem = (int*)kmalloc(sizeof(int)).release_value(); + *(volatile int*)mem = 6; + Serial::printf("Read %d from memory\n", *mem); + + mem = (int*)krealloc(mem, 60).release_value(); + + Serial::printf("Resized to %p\n", (void*)mem); + + kfree(mem); + while (1) { while ((Timer::ticks_ms() - start) < 20) { CPU::wait_for_interrupt(); } diff --git a/kernel/src/memory/Heap.cpp b/kernel/src/memory/Heap.cpp new file mode 100644 index 00000000..59f4660d --- /dev/null +++ b/kernel/src/memory/Heap.cpp @@ -0,0 +1,331 @@ +#include "memory/Heap.h" +#include "arch/MMU.h" +#include "arch/Serial.h" +#include "memory/MemoryManager.h" +#include +#include + +static constexpr int BLOCK_USED = 1 << 0; +static constexpr int BLOCK_START_MEM = 1 << 1; +static constexpr int BLOCK_END_MEM = 1 << 2; + +static constexpr usize BLOCK_MAGIC = 0x6d616c6c6f63210a; // echo "malloc\!" | hexdump -C +static constexpr usize BLOCK_DEAD = 0xdeaddeaddeaddead; + +static constexpr usize MINIMUM_PAGES_PER_ALLOCATION = 4; + +struct HeapBlock +{ + usize req_size; + usize full_size; + int status; + HeapBlock* next; + HeapBlock* last; + usize magic; +}; + +static_assert(sizeof(HeapBlock) == 48UL); + +static HeapBlock* heap_start = nullptr; +static HeapBlock* heap_end = nullptr; + +static usize start_addr = 0xffffffff80000000; + +static Result allocate_pages( + usize count) // FIXME: Keep track of virtual address space usage. For now, since the address + // space is so huge, we can just start at a fairly large address and assume + // we'll never run into anything, but this will probably bite us in the future. +{ + void* ptr = (void*)TRY(MemoryManager::alloc_at(start_addr, count, MMU::ReadWrite | MMU::NoExecute)); + if (ptr) start_addr += (count * ARCH_PAGE_SIZE); + return (HeapBlock*)ptr; +} + +static Result release_pages(void* ptr, usize count) +{ + return MemoryManager::unmap_owned((u64)ptr, count); +} + +// If we're allocating a large amount of memory, map enough pages for it, but otherwise just use the default amount of +// pages. +static usize get_pages_for_allocation(usize bytes) +{ + usize pages = get_blocks_from_size(bytes, ARCH_PAGE_SIZE); + if (pages < MINIMUM_PAGES_PER_ALLOCATION) pages = MINIMUM_PAGES_PER_ALLOCATION; + return pages; +} + +static bool is_block_free(HeapBlock* block) +{ + return !(block->status & BLOCK_USED); +} + +static usize space_available(HeapBlock* block) +{ + check(!is_block_free(block)); + return block->full_size - block->req_size; +} + +static HeapBlock* get_heap_block_for_pointer(void* ptr) +{ + return (HeapBlock*)offset_ptr(ptr, -48); +} + +static void* get_pointer_from_heap_block(HeapBlock* block) +{ + return (void*)offset_ptr(block, 48); +} + +static usize get_fair_offset_to_split_at(HeapBlock* block, usize min) +{ + usize available = space_available(block); + + available -= min; // reserve at least min size for the new block. + + available -= (available / + 2); // reserve half of the rest for the new block, while still leaving another half for the old one. + + return available + block->req_size; +} + +static Result split(HeapBlock* block, usize size) +{ + usize available = space_available(block); // How much space can we steal from this block? + usize old_size = + block->full_size; // Save the old value of this variable since we are going to use it after modifying it + + if (available < (size + sizeof(HeapBlock))) return err; + + usize offset = get_fair_offset_to_split_at(block, size + sizeof(HeapBlock)); + block->full_size = offset; // shrink the old block to fit this offset + + HeapBlock* new_block = offset_ptr(block, offset + sizeof(HeapBlock)); + + new_block->magic = BLOCK_MAGIC; + new_block->status = (block->status & BLOCK_END_MEM) ? BLOCK_END_MEM : 0; + new_block->full_size = old_size - (offset + sizeof(HeapBlock)); + new_block->next = block->next; + new_block->last = block; + + block->status &= ~BLOCK_END_MEM; // this block is no longer the last block in this memory range + block->next = new_block; + + return new_block; +} + +static Result combine_forward(HeapBlock* block) +{ + HeapBlock* next = block->next; + if (next == heap_end) heap_end = block; + next->magic = BLOCK_DEAD; + + block->next = block->next->next; + if (block->next) block->next->last = block; + + if (next->status & BLOCK_END_MEM) + { + if (next->status & BLOCK_START_MEM) + { + TRY(release_pages(next, get_blocks_from_size(next->full_size + sizeof(HeapBlock), ARCH_PAGE_SIZE))); + return {}; + } + else + block->status |= BLOCK_END_MEM; + } + + block->full_size += next->full_size + sizeof(HeapBlock); + + return {}; +} + +static Result combine_backward(HeapBlock* block) +{ + HeapBlock* last = block->last; + if (block == heap_end) heap_end = last; + block->magic = BLOCK_DEAD; + + last->next = block->next; + if (last->next) last->next->last = last; + + if (block->status & BLOCK_END_MEM) + { + if (block->status & BLOCK_START_MEM) + { + TRY(release_pages(block, get_blocks_from_size(block->full_size + sizeof(HeapBlock), ARCH_PAGE_SIZE))); + return last; + } + else + last->status |= BLOCK_END_MEM; + } + + last->full_size += block->full_size + sizeof(HeapBlock); + + return last; +} + +Result kmalloc(usize size) +{ + if (!size) return (void*)BLOCK_MAGIC; + + size = align_up(size, 16UL); + + if (!heap_start) + { + usize pages = get_pages_for_allocation(size); + auto* block = TRY(allocate_pages(pages)); + + block->full_size = (pages * ARCH_PAGE_SIZE) - sizeof(HeapBlock); + block->magic = BLOCK_MAGIC; + block->status = BLOCK_START_MEM | BLOCK_END_MEM; + block->next = block->last = nullptr; + heap_start = block; + + if (!heap_end) heap_end = heap_start; + } + + HeapBlock* block = heap_start; + while (block) + { + // Trying to find a free block... + if (is_block_free(block)) + { + if (block->full_size < size) + { + block = block->next; // Let's not try to split this block, it's not big enough + continue; + } + break; // We found a free block that's big enough!! + } + auto rc = split(block, size); + if (rc.has_value()) + { + block = rc.release_value(); // We managed to get a free block from a larger used block!! + break; + } + block = block->next; + } + + if (!block) // No free blocks, let's allocate a new one + { + usize pages = get_pages_for_allocation(size); + block = TRY(allocate_pages(pages)); + + block->full_size = (pages * ARCH_PAGE_SIZE) - sizeof(HeapBlock); + block->magic = BLOCK_MAGIC; + block->status = BLOCK_START_MEM | BLOCK_END_MEM; + block->next = nullptr; + block->last = heap_end; + + heap_end = block; + } + + block->req_size = size; + block->status |= BLOCK_USED; + + return get_pointer_from_heap_block(block); +} + +Result kfree(void* ptr) +{ + if (ptr == (void*)BLOCK_MAGIC) return {}; // This pointer was returned from a call to malloc(0) + if (!ptr) return {}; + + HeapBlock* block = get_heap_block_for_pointer(ptr); + + if (block->magic != BLOCK_MAGIC) + { + if (block->magic == BLOCK_DEAD) + { + Serial::printf("ERROR: Attempt to free memory at %p, which was already freed\n", ptr); + } + else + Serial::printf("ERROR: Attempt to free memory at %p, which wasn't allocated with kmalloc\n", ptr); + + return err; + } + + if (is_block_free(block)) + { + Serial::printf("ERROR: Attempt to free memory at %p, which was already freed\n", ptr); + return err; + } + else + block->status &= ~BLOCK_USED; + + if (block->next && is_block_free(block->next)) + { + // The next block is also free, thus we can merge! + TRY(combine_forward(block)); + } + + if (block->last && is_block_free(block->last)) + { + // The last block is also free, thus we can merge! + block = TRY(combine_backward(block)); + } + + if ((block->status & BLOCK_START_MEM) && (block->status & BLOCK_END_MEM)) + { + if (block == heap_start) heap_start = block->next; + if (block == heap_end) heap_end = block->last; + if (block->last) block->last->next = block->next; + if (block->next) block->next->last = block->last; + TRY(release_pages(block, get_blocks_from_size(block->full_size + sizeof(HeapBlock), ARCH_PAGE_SIZE))); + } + + return {}; +} + +Result krealloc(void* ptr, usize size) +{ + if (!ptr) return kmalloc(size); + if (ptr == (void*)BLOCK_MAGIC) return kmalloc(size); + if (!size) + { + TRY(kfree(ptr)); + return (void*)BLOCK_MAGIC; + } + + HeapBlock* block = get_heap_block_for_pointer(ptr); + + if (block->magic != BLOCK_MAGIC) + { + if (block->magic == BLOCK_DEAD) + { + Serial::printf("ERROR: Attempt to realloc memory at %p, which was already freed\n", ptr); + } + else + Serial::printf("ERROR: Attempt to realloc memory at %p, which wasn't allocated with kmalloc\n", ptr); + + return err; + } + + size = align_up(size, 16UL); + + if (is_block_free(block)) + { + Serial::printf("ERROR: Attempt to realloc memory at %p, which was already freed\n", ptr); + return err; + } + + if (block->full_size >= size) + { + // This block is already large enough! + block->req_size = size; + return ptr; + } + + void* new_ptr = TRY(kmalloc(size)); + memcpy(new_ptr, ptr, block->req_size > size ? size : block->req_size); + TRY(kfree(ptr)); + + return new_ptr; +} + +Result kcalloc(usize nmemb, usize size) +{ + // FIXME: Check for overflows. + usize realsize = nmemb * size; + void* ptr = TRY(kmalloc(realsize)); + return memset(ptr, 0, realsize); +} \ No newline at end of file diff --git a/kernel/src/memory/Heap.h b/kernel/src/memory/Heap.h new file mode 100644 index 00000000..3bb4a3bf --- /dev/null +++ b/kernel/src/memory/Heap.h @@ -0,0 +1,7 @@ +#pragma once +#include + +Result kmalloc(usize size); +Result kcalloc(usize nmemb, usize size); +Result krealloc(void* ptr, usize size); +Result kfree(void* ptr); \ No newline at end of file