diff options
Diffstat (limited to 'arch/x86_64/src/memory/heap')
| -rw-r--r-- | arch/x86_64/src/memory/heap/bump_allocator.cpp | 4 | ||||
| -rw-r--r-- | arch/x86_64/src/memory/heap/global_heap_allocator.cpp | 95 | ||||
| -rw-r--r-- | arch/x86_64/src/memory/heap/linked_list_allocator.cpp | 6 | ||||
| -rw-r--r-- | arch/x86_64/src/memory/heap/memory_block.cpp | 4 | ||||
| -rw-r--r-- | arch/x86_64/src/memory/heap/user_heap_allocator.cpp | 200 |
5 files changed, 283 insertions, 26 deletions
diff --git a/arch/x86_64/src/memory/heap/bump_allocator.cpp b/arch/x86_64/src/memory/heap/bump_allocator.cpp index df95346..525f45c 100644 --- a/arch/x86_64/src/memory/heap/bump_allocator.cpp +++ b/arch/x86_64/src/memory/heap/bump_allocator.cpp @@ -24,11 +24,13 @@ namespace teachos::arch::memory::heap auto bump_allocator::allocate(std::size_t size) -> void * { + // Reading the value only has to be done once, because compare_exchange_weak updates the value as well if the + // exchange failed, becuase the value was not the expected one. + auto alloc_start = next.load(std::memory_order::relaxed); // Repeat allocation until it succeeds, has to be done, because another allocator could overtake it at any time // causing the value to differ and the calculation to have to be redone. for (;;) { - auto alloc_start = next.load(std::memory_order::relaxed); auto const alloc_end = saturating_add(alloc_start, size); arch::exception_handling::assert(alloc_end <= heap_end, "[Heap Allocator] Out of memory"); // Check if the atomic value is still the one initally loaded, if it isn't we have been overtaken by another diff --git a/arch/x86_64/src/memory/heap/global_heap_allocator.cpp b/arch/x86_64/src/memory/heap/global_heap_allocator.cpp index c1ca160..35cd623 100644 --- a/arch/x86_64/src/memory/heap/global_heap_allocator.cpp +++ b/arch/x86_64/src/memory/heap/global_heap_allocator.cpp @@ -1,20 +1,43 @@ #include "arch/memory/heap/global_heap_allocator.hpp" +#include "arch/context_switching/syscall/main.hpp" #include "arch/exception_handling/assert.hpp" +#include "arch/kernel/cpu/segment_register.hpp" #include "arch/memory/heap/bump_allocator.hpp" #include "arch/memory/heap/linked_list_allocator.hpp" +#include "arch/memory/heap/user_heap_allocator.hpp" namespace teachos::arch::memory::heap { - heap_allocator * global_heap_allocator::allocator_instance = nullptr; + namespace + { + constexpr char NOT_REGISTRED_ERROR_MESSAGE[] = + "Attempted to allocate or deallocate using the global_heap_allocator before " + "register_heap_allocation_type was called."; + constexpr uint16_t KERNEL_CODE_INDEX = 1U; + + [[gnu::section(".user_text")]] + auto os_in_kernel_mode() -> bool + { + auto const cs = teachos::arch::kernel::cpu::read_code_segment_register(); + return cs.get_index() == KERNEL_CODE_INDEX; + } + } // namespace + + heap_allocator * global_heap_allocator::kernel_allocator_instance = nullptr; + user_heap_allocator * global_heap_allocator::user_allocator_instance = nullptr; + + auto global_heap_allocator::kmalloc(std::size_t size) -> void * { return kernel().allocate(size); } - auto global_heap_allocator::allocate(std::size_t size) -> void * { return get().allocate(size); } + auto global_heap_allocator::kfree(void * pointer) noexcept -> void { kernel().deallocate(pointer); } - auto global_heap_allocator::deallocate(void * pointer) noexcept -> void { get().deallocate(pointer); } + auto global_heap_allocator::malloc(std::size_t size) -> void * { return user().allocate(size); } + + auto global_heap_allocator::free(void * pointer) noexcept -> void { user().deallocate(pointer); } auto global_heap_allocator::register_heap_allocator(heap_allocator_type new_type) -> void { - exception_handling::assert(allocator_instance == nullptr, + exception_handling::assert(kernel_allocator_instance == nullptr, "Calling register_heap_allocator_type can only be done once."); switch (new_type) @@ -23,56 +46,90 @@ namespace teachos::arch::memory::heap // Nothing to do break; case heap_allocator_type::BUMP: { - static bump_allocator allocator{HEAP_START, HEAP_START + HEAP_SIZE}; - allocator_instance = &allocator; + static bump_allocator kernel_allocator{KERNEL_HEAP_START, KERNEL_HEAP_START + KERNEL_HEAP_SIZE}; + kernel_allocator_instance = &kernel_allocator; break; } case heap_allocator_type::LINKED_LIST: { - static linked_list_allocator allocator{HEAP_START, HEAP_START + HEAP_SIZE}; - allocator_instance = &allocator; + static linked_list_allocator kernel_allocator{KERNEL_HEAP_START, KERNEL_HEAP_START + KERNEL_HEAP_SIZE}; + kernel_allocator_instance = &kernel_allocator; break; } } + + [[gnu::section(".user_data")]] + static user_heap_allocator user_allocator{}; + user_allocator_instance = &user_allocator; } - auto global_heap_allocator::get() -> heap_allocator & + auto global_heap_allocator::kernel() -> heap_allocator & { - exception_handling::assert(allocator_instance != nullptr, - "Attempted to allocate or deallocate using the global_heap_allocator before " - "register_heap_allocation_type was called."); + exception_handling::assert(kernel_allocator_instance != nullptr, NOT_REGISTRED_ERROR_MESSAGE); + + return *kernel_allocator_instance; + } - return *allocator_instance; + auto global_heap_allocator::user() -> user_heap_allocator & + { + context_switching::syscall::syscall( + context_switching::syscall::type::ASSERT, + {user_allocator_instance != nullptr, reinterpret_cast<uint64_t>(&NOT_REGISTRED_ERROR_MESSAGE)}); + return *user_allocator_instance; } } // namespace teachos::arch::memory::heap auto operator new(std::size_t size) -> void * { - return teachos::arch::memory::heap::global_heap_allocator::allocate(size); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + return teachos::arch::memory::heap::global_heap_allocator::kmalloc(size); + } + return teachos::arch::memory::heap::global_heap_allocator::malloc(size); } auto operator delete(void * pointer) noexcept -> void { - teachos::arch::memory::heap::global_heap_allocator::deallocate(pointer); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); } auto operator delete(void * pointer, std::size_t size) noexcept -> void { (void)size; - teachos::arch::memory::heap::global_heap_allocator::deallocate(pointer); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); } auto operator new[](std::size_t size) -> void * { - return teachos::arch::memory::heap::global_heap_allocator::allocate(size); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + return teachos::arch::memory::heap::global_heap_allocator::kmalloc(size); + } + return teachos::arch::memory::heap::global_heap_allocator::malloc(size); } auto operator delete[](void * pointer) noexcept -> void { - teachos::arch::memory::heap::global_heap_allocator::deallocate(pointer); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); } auto operator delete[](void * pointer, std::size_t size) noexcept -> void { (void)size; - teachos::arch::memory::heap::global_heap_allocator::deallocate(pointer); + if (teachos::arch::memory::heap::os_in_kernel_mode()) + { + teachos::arch::memory::heap::global_heap_allocator::kfree(pointer); + } + teachos::arch::memory::heap::global_heap_allocator::free(pointer); } diff --git a/arch/x86_64/src/memory/heap/linked_list_allocator.cpp b/arch/x86_64/src/memory/heap/linked_list_allocator.cpp index a824c8a..63a6111 100644 --- a/arch/x86_64/src/memory/heap/linked_list_allocator.cpp +++ b/arch/x86_64/src/memory/heap/linked_list_allocator.cpp @@ -8,10 +8,8 @@ namespace teachos::arch::memory::heap { linked_list_allocator::linked_list_allocator(std::size_t heap_start, std::size_t heap_end) - : heap_start(heap_start) - , heap_end(heap_end) - , first(nullptr) - , mutex{shared::mutex{}} + : first(nullptr) + , mutex{stl::mutex{}} { auto const heap_size = heap_end - heap_start; exception_handling::assert( diff --git a/arch/x86_64/src/memory/heap/memory_block.cpp b/arch/x86_64/src/memory/heap/memory_block.cpp index 446cd96..bc97bd6 100644 --- a/arch/x86_64/src/memory/heap/memory_block.cpp +++ b/arch/x86_64/src/memory/heap/memory_block.cpp @@ -6,10 +6,10 @@ namespace teachos::arch::memory::heap { memory_block::memory_block(std::size_t size, memory_block * next) { - memset(static_cast<void *>(this), 0, size); + memset(static_cast<void *>(this), 0U, size); this->size = size; this->next = next; } - memory_block::~memory_block() { memset(static_cast<void *>(this), 0, sizeof(memory_block)); } + memory_block::~memory_block() { memset(static_cast<void *>(this), 0U, sizeof(memory_block)); } } // namespace teachos::arch::memory::heap diff --git a/arch/x86_64/src/memory/heap/user_heap_allocator.cpp b/arch/x86_64/src/memory/heap/user_heap_allocator.cpp new file mode 100644 index 0000000..427a68a --- /dev/null +++ b/arch/x86_64/src/memory/heap/user_heap_allocator.cpp @@ -0,0 +1,200 @@ +#include "arch/memory/heap/user_heap_allocator.hpp" + +#include "arch/context_switching/syscall/main.hpp" + +#include <algorithm> + +namespace teachos::arch::memory::heap +{ + auto user_heap_allocator::allocate(std::size_t size) -> void * + { + // Add size of size_t to the total allocated size, because we add a header that includes the size of the allocated + // block, to allow for deallocation without the need to call with the corresponding size + auto const total_size = size + sizeof(std::size_t); + mutex.lock(); + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + auto memory = allocate_into_memory_block_if_big_enough(current, previous, total_size); + if (memory.has_value()) + { + return memory.value(); + } + + previous = current; + current = current->next; + } + + current = expand_heap_if_full(); + + if (current != nullptr) + { + auto memory = allocate_into_memory_block_if_big_enough(current, previous, total_size); + if (memory.has_value()) + { + return memory.value(); + } + } + + char constexpr OUT_OF_MEMORY_ERROR_MESSAGE[] = "[Linked List Allocator] Out of memory"; + context_switching::syscall::syscall(context_switching::syscall::type::ASSERT, + {false, reinterpret_cast<uint64_t>(&OUT_OF_MEMORY_ERROR_MESSAGE)}); + return nullptr; + } + + auto user_heap_allocator::deallocate(void * pointer) noexcept -> void + { + mutex.lock(); + + // Read configured header size of the complete allocated block + auto const header_pointer = reinterpret_cast<void *>(reinterpret_cast<std::size_t>(pointer) - sizeof(std::size_t)); + auto const total_size = *reinterpret_cast<std::size_t *>(header_pointer); + + auto const start_address = reinterpret_cast<std::size_t>(header_pointer); + auto const end_address = start_address + total_size; + + memory_block * previous = nullptr; + auto current = first; + + while (current != nullptr) + { + // Current address of the free memory block now points to an address that is after our block to deallocate in heap + // memory space. + if (reinterpret_cast<std::size_t>(current) >= end_address) + { + break; + } + + previous = current; + current = current->next; + } + + coalesce_free_memory_block(previous, current, header_pointer, total_size); + mutex.unlock(); + } + + auto user_heap_allocator::allocate_into_memory_block_if_big_enough(memory_block * current, memory_block * previous, + std::size_t total_size) -> std::optional<void *> + { + if (current->size == total_size) + { + auto const memory_address = remove_free_memory_block(previous, current); + new (memory_address) std::size_t(total_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + else if (current->size >= total_size + min_allocatable_size()) + { + // Ensure that the allocated size block is atleast 16 bytes (required because if we free the hole afterwards + // there needs to be enough space for a memory block). Therefore we allocate more than is actually required if + // the total size was less and simply deallocate it as well + auto const max_size = std::max(total_size, min_allocatable_size()); + auto const memory_address = split_free_memory_block(previous, current, max_size); + new (memory_address) std::size_t(max_size); + mutex.unlock(); + return reinterpret_cast<void *>(reinterpret_cast<std::size_t>(memory_address) + sizeof(std::size_t)); + } + return std::nullopt; + } + + auto user_heap_allocator::expand_heap_if_full() -> memory_block * + { + auto const result = context_switching::syscall::syscall(context_switching::syscall::type::EXPAND_HEAP); + + uint64_t const heap_start = result.values.arg_0; + uint64_t const heap_size = result.values.arg_1; + return !result.error_code ? new (reinterpret_cast<void *>(heap_start)) memory_block(heap_size, nullptr) : nullptr; + } + + auto user_heap_allocator::remove_free_memory_block(memory_block * previous_block, memory_block * current_block) + -> void * + { + return replace_free_memory_block(previous_block, current_block, current_block->next); + } + + auto user_heap_allocator::split_free_memory_block(memory_block * previous_block, memory_block * current_block, + std::size_t size) -> void * + { + auto const end_address = reinterpret_cast<std::size_t>(current_block) + size; + auto const new_block = + new (reinterpret_cast<void *>(end_address)) memory_block(current_block->size - size, current_block->next); + return replace_free_memory_block(previous_block, current_block, new_block); + } + + auto user_heap_allocator::replace_free_memory_block(memory_block * previous_block, memory_block * current_block, + memory_block * new_block) -> void * + { + auto const start_address = reinterpret_cast<std::size_t>(current_block); + // If we want to allocate into the first block that is before any other free block, then there exists no previous + // free block (nullptr). Therefore we have to overwrite the first block instead of overwriting its next value. + if (previous_block == nullptr) + { + first = new_block; + } + else + { + previous_block->next = new_block; + } + current_block->~memory_block(); + return reinterpret_cast<void *>(start_address); + } + + auto user_heap_allocator::coalesce_free_memory_block(memory_block * previous_block, memory_block * current_block, + void * pointer, std::size_t size) -> void + { + auto const start_address = reinterpret_cast<std::size_t>(pointer); + auto const end_address = start_address + size; + + // Inital values if there are no adjacent blocks either before or after, meaning we have to simply create a free + // memory block that is placed in between the previous and next block. + auto block_size = size; + auto next_block = current_block; + + // If the block we want to deallocate is before another free block and we can therefore combine both into one. + // This is done by deleting the current free block and creating a new block at the start address of the block to + // deallocate with both the size of the block to deallcoate and the free block next to it. + if (end_address == reinterpret_cast<std::size_t>(current_block)) + { + block_size += current_block->size; + next_block = current_block->next; + current_block->~memory_block(); + } + + // If the block we want to deallocate is behind another free block and we can therefore combine both into one. + // This is done by simply changin the size of the previous block to include the size of the block to deallocate. + // This is done, because the previous block might still be referencered by the next field of other memory blocks. + if (previous_block != nullptr && + start_address == (reinterpret_cast<std::size_t>(previous_block) + previous_block->size)) + { + block_size += previous_block->size; + + previous_block->size = block_size; + previous_block->next = next_block; + return; + } + + // Check if the block we want to deallocate is contained in the previous block, because if it is it can only mean + // that the block has already been deallocated and we therefore attempted a double free. + char constexpr DOUBLE_FREE_ERROR_MESSAGE[] = "[Linked List Allocator] Attempted double free detected"; + context_switching::syscall::syscall( + context_switching::syscall::type::ASSERT, + {previous_block == nullptr || + start_address >= (reinterpret_cast<std::size_t>(previous_block) + previous_block->size), + reinterpret_cast<uint64_t>(&DOUBLE_FREE_ERROR_MESSAGE)}); + + auto const new_block = new (pointer) memory_block(block_size, next_block); + // If we want to deallocate the first block that is before any other free block, then there exists no previous free + // block (nullptr). Therefore we have to overwrite the first block instead of overwriting its + // next value. + if (previous_block == nullptr) + { + first = new_block; + return; + } + previous_block->next = new_block; + } + +} // namespace teachos::arch::memory::heap |
