From 4a92f44cf6fff0d580c0d1c191561182b85f37d6 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 29 Sep 2023 16:18:23 +0300 Subject: [PATCH] Kernel: Implement new abstract MemoryRegion MemoryBackedRegion now inherits from this and is used for private anonymous mappigs. This will make shared mappings and file backed mappings much easier to implement. --- kernel/CMakeLists.txt | 2 + .../kernel/Memory/MemoryBackedRegion.h | 29 ++++ kernel/include/kernel/Memory/MemoryRegion.h | 59 +++++++++ kernel/include/kernel/Process.h | 4 +- kernel/kernel/Memory/MemoryBackedRegion.cpp | 124 ++++++++++++++++++ kernel/kernel/Memory/MemoryRegion.cpp | 50 +++++++ kernel/kernel/Process.cpp | 105 +++++++-------- 7 files changed, 319 insertions(+), 54 deletions(-) create mode 100644 kernel/include/kernel/Memory/MemoryBackedRegion.h create mode 100644 kernel/include/kernel/Memory/MemoryRegion.h create mode 100644 kernel/kernel/Memory/MemoryBackedRegion.cpp create mode 100644 kernel/kernel/Memory/MemoryRegion.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 188443673..281a560c5 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -35,6 +35,8 @@ set(KERNEL_SOURCES kernel/Memory/GeneralAllocator.cpp kernel/Memory/Heap.cpp kernel/Memory/kmalloc.cpp + kernel/Memory/MemoryBackedRegion.cpp + kernel/Memory/MemoryRegion.cpp kernel/Memory/PhysicalRange.cpp kernel/Memory/VirtualRange.cpp kernel/Networking/E1000.cpp diff --git a/kernel/include/kernel/Memory/MemoryBackedRegion.h b/kernel/include/kernel/Memory/MemoryBackedRegion.h new file mode 100644 index 000000000..0b81d1b74 --- /dev/null +++ b/kernel/include/kernel/Memory/MemoryBackedRegion.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace Kernel +{ + + class MemoryBackedRegion final : public MemoryRegion + { + BAN_NON_COPYABLE(MemoryBackedRegion); + BAN_NON_MOVABLE(MemoryBackedRegion); + + public: + static BAN::ErrorOr> create(PageTable&, size_t size, AddressRange, Type, PageTable::flags_t); + ~MemoryBackedRegion(); + + virtual BAN::ErrorOr allocate_page_containing(vaddr_t vaddr) override; + + virtual BAN::ErrorOr> clone(PageTable& new_page_table) override; + + // Copy data from buffer into this region + // This can fail if no memory is mapped and no free memory was available + BAN::ErrorOr copy_data_to_region(size_t offset_into_region, const uint8_t* buffer, size_t buffer_size); + + private: + MemoryBackedRegion(PageTable&, size_t size, Type, PageTable::flags_t); + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/Memory/MemoryRegion.h b/kernel/include/kernel/Memory/MemoryRegion.h new file mode 100644 index 000000000..b73448718 --- /dev/null +++ b/kernel/include/kernel/Memory/MemoryRegion.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Kernel +{ + + struct AddressRange + { + vaddr_t start; + vaddr_t end; + }; + + class MemoryRegion + { + BAN_NON_COPYABLE(MemoryRegion); + BAN_NON_MOVABLE(MemoryRegion); + + public: + enum Type : uint8_t + { + PRIVATE, + SHARED + }; + + public: + virtual ~MemoryRegion(); + + bool contains(vaddr_t address) const; + bool contains_fully(vaddr_t address, size_t size) const; + bool overlaps(vaddr_t address, size_t size) const; + + size_t size() const { return m_size; } + vaddr_t vaddr() const { return m_vaddr; } + + // Returns error if no memory was available + // Returns true if page was succesfully allocated + // Returns false if page was already allocated + virtual BAN::ErrorOr allocate_page_containing(vaddr_t address) = 0; + + virtual BAN::ErrorOr> clone(PageTable& new_page_table) = 0; + + protected: + MemoryRegion(PageTable&, size_t size, Type type, PageTable::flags_t flags); + BAN::ErrorOr initialize(AddressRange); + + protected: + PageTable& m_page_table; + const size_t m_size; + const Type m_type; + const PageTable::flags_t m_flags; + vaddr_t m_vaddr { 0 }; + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/Process.h b/kernel/include/kernel/Process.h index 10b33c9c4..6e7b04bc7 100644 --- a/kernel/include/kernel/Process.h +++ b/kernel/include/kernel/Process.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -174,7 +174,7 @@ namespace Kernel OpenFileDescriptorSet m_open_file_descriptors; BAN::UniqPtr m_loadable_elf; - BAN::Vector> m_mapped_ranges; + BAN::Vector> m_mapped_regions; pid_t m_sid; pid_t m_pgrp; diff --git a/kernel/kernel/Memory/MemoryBackedRegion.cpp b/kernel/kernel/Memory/MemoryBackedRegion.cpp new file mode 100644 index 000000000..fbec159e4 --- /dev/null +++ b/kernel/kernel/Memory/MemoryBackedRegion.cpp @@ -0,0 +1,124 @@ +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr> MemoryBackedRegion::create(PageTable& page_table, size_t size, AddressRange address_range, Type type, PageTable::flags_t flags) + { + ASSERT(type == Type::PRIVATE); + + auto* region_ptr = new MemoryBackedRegion(page_table, size, type, flags); + if (region_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + auto region = BAN::UniqPtr::adopt(region_ptr); + + TRY(region->initialize(address_range)); + + return region; + } + + MemoryBackedRegion::MemoryBackedRegion(PageTable& page_table, size_t size, Type type, PageTable::flags_t flags) + : MemoryRegion(page_table, size, type, flags) + { + } + + MemoryBackedRegion::~MemoryBackedRegion() + { + ASSERT(m_type == Type::PRIVATE); + + size_t needed_pages = BAN::Math::div_round_up(m_size, PAGE_SIZE); + for (size_t i = 0; i < needed_pages; i++) + { + paddr_t paddr = m_page_table.physical_address_of(m_vaddr + i * PAGE_SIZE); + if (paddr != 0) + Heap::get().release_page(paddr); + } + } + + BAN::ErrorOr MemoryBackedRegion::allocate_page_containing(vaddr_t address) + { + ASSERT(m_type == Type::PRIVATE); + + ASSERT(contains(address)); + + // Check if address is already mapped + vaddr_t vaddr = address & PAGE_ADDR_MASK; + if (m_page_table.physical_address_of(vaddr) != 0) + return false; + + // Map new physcial page to address + paddr_t paddr = Heap::get().take_free_page(); + if (paddr == 0) + return BAN::Error::from_errno(ENOMEM); + m_page_table.map_page_at(paddr, vaddr, m_flags); + + // Zero out the new page + if (&PageTable::current() == &m_page_table) + memset((void*)vaddr, 0x00, PAGE_SIZE); + else + { + LockGuard _(PageTable::current()); + ASSERT(PageTable::current().is_page_free(0)); + + PageTable::current().map_page_at(paddr, 0, PageTable::Flags::ReadWrite | PageTable::Flags::Present); + memset((void*)0, 0x00, PAGE_SIZE); + PageTable::current().unmap_page(0); + } + + return true; + } + + BAN::ErrorOr> MemoryBackedRegion::clone(PageTable& new_page_table) + { + ASSERT(&PageTable::current() == &m_page_table); + + auto result = TRY(MemoryBackedRegion::create(new_page_table, m_size, { .start = m_vaddr, .end = m_vaddr + m_size }, m_type, m_flags)); + + for (size_t offset = 0; offset < m_size; offset += PAGE_SIZE) + { + paddr_t paddr = m_page_table.physical_address_of(m_vaddr + offset); + if (paddr == 0) + continue; + TRY(result->copy_data_to_region(offset, (const uint8_t*)(m_vaddr + offset), PAGE_SIZE)); + } + + return BAN::UniqPtr(BAN::move(result)); + } + + BAN::ErrorOr MemoryBackedRegion::copy_data_to_region(size_t offset_into_region, const uint8_t* buffer, size_t buffer_size) + { + ASSERT(offset_into_region + buffer_size <= m_size); + + size_t written = 0; + while (written < buffer_size) + { + vaddr_t write_vaddr = m_vaddr + offset_into_region + written; + vaddr_t page_offset = write_vaddr % PAGE_SIZE; + size_t bytes = BAN::Math::min(buffer_size - written, PAGE_SIZE - page_offset); + + TRY(allocate_page_containing(write_vaddr)); + + if (&PageTable::current() == &m_page_table) + memcpy((void*)write_vaddr, (void*)(buffer + written), bytes); + else + { + paddr_t paddr = m_page_table.physical_address_of(write_vaddr & PAGE_ADDR_MASK); + ASSERT(paddr); + + LockGuard _(PageTable::current()); + ASSERT(PageTable::current().is_page_free(0)); + + PageTable::current().map_page_at(paddr, 0, PageTable::Flags::ReadWrite | PageTable::Flags::Present); + memcpy((void*)page_offset, (void*)(buffer + written), bytes); + PageTable::current().unmap_page(0); + } + + written += bytes; + } + + return {}; + } + +} \ No newline at end of file diff --git a/kernel/kernel/Memory/MemoryRegion.cpp b/kernel/kernel/Memory/MemoryRegion.cpp new file mode 100644 index 000000000..e6cd61d0b --- /dev/null +++ b/kernel/kernel/Memory/MemoryRegion.cpp @@ -0,0 +1,50 @@ +#include + +namespace Kernel +{ + + MemoryRegion::MemoryRegion(PageTable& page_table, size_t size, Type type, PageTable::flags_t flags) + : m_page_table(page_table) + , m_size(size) + , m_type(type) + , m_flags(flags) + { + } + + MemoryRegion::~MemoryRegion() + { + if (m_vaddr) + m_page_table.unmap_range(m_vaddr, m_size); + } + + BAN::ErrorOr MemoryRegion::initialize(AddressRange address_range) + { + size_t needed_pages = BAN::Math::div_round_up(m_size, PAGE_SIZE); + m_vaddr = m_page_table.reserve_free_contiguous_pages(needed_pages, address_range.start); + if (m_vaddr == 0) + return BAN::Error::from_errno(ENOMEM); + if (m_vaddr + needed_pages * PAGE_SIZE > address_range.end) + return BAN::Error::from_errno(ENOMEM); + return {}; + } + + bool MemoryRegion::contains(vaddr_t address) const + { + return m_vaddr <= address && address < m_vaddr + m_size; + } + + bool MemoryRegion::contains_fully(vaddr_t address, size_t size) const + { + return m_vaddr <= address && address + size < m_vaddr + m_size; + } + + bool MemoryRegion::overlaps(vaddr_t address, size_t size) const + { + if (address + size < m_vaddr) + return false; + if (address >= m_vaddr + m_size) + return false; + return true; + } + +} diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index 3e62bc2dd..8f4f29a9a 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -128,23 +129,23 @@ namespace Kernel if (auto rem = needed_bytes % PAGE_SIZE) needed_bytes += PAGE_SIZE - rem; - auto argv_range = MUST(VirtualRange::create_to_vaddr_range( + auto argv_region = MUST(MemoryBackedRegion::create( process->page_table(), - 0x400000, KERNEL_OFFSET, needed_bytes, - PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present, - true + { .start = 0x400000, .end = KERNEL_OFFSET }, + MemoryRegion::Type::PRIVATE, + PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present )); - uintptr_t temp = argv_range->vaddr() + sizeof(char*) * 2; - argv_range->copy_from(0, (uint8_t*)&temp, sizeof(char*)); + uintptr_t temp = argv_region->vaddr() + sizeof(char*) * 2; + MUST(argv_region->copy_data_to_region(0, (const uint8_t*)&temp, sizeof(char*))); temp = 0; - argv_range->copy_from(sizeof(char*), (uint8_t*)&temp, sizeof(char*)); + MUST(argv_region->copy_data_to_region(sizeof(char*), (const uint8_t*)&temp, sizeof(char*))); - argv_range->copy_from(sizeof(char*) * 2, (const uint8_t*)path.data(), path.size()); + MUST(argv_region->copy_data_to_region(sizeof(char*) * 2, (const uint8_t*)path.data(), path.size())); - MUST(process->m_mapped_ranges.push_back(BAN::move(argv_range))); + MUST(process->m_mapped_regions.push_back(BAN::move(argv_region))); } process->m_userspace_info.argc = 1; @@ -172,7 +173,8 @@ namespace Kernel Process::~Process() { ASSERT(m_threads.empty()); - ASSERT(m_mapped_ranges.empty()); + ASSERT(m_mapped_regions.empty()); + ASSERT(!m_loadable_elf); ASSERT(m_exit_status.waiting == 0); ASSERT(&PageTable::current() != m_page_table.ptr()); } @@ -205,7 +207,8 @@ namespace Kernel m_open_file_descriptors.close_all(); // NOTE: We must unmap ranges while the page table is still alive - m_mapped_ranges.clear(); + m_mapped_regions.clear(); + m_loadable_elf.clear(); } void Process::on_thread_exit(Thread& thread) @@ -318,10 +321,10 @@ namespace Kernel OpenFileDescriptorSet open_file_descriptors(m_credentials); TRY(open_file_descriptors.clone_from(m_open_file_descriptors)); - BAN::Vector> mapped_ranges; - TRY(mapped_ranges.reserve(m_mapped_ranges.size())); - for (auto& mapped_range : m_mapped_ranges) - MUST(mapped_ranges.push_back(TRY(mapped_range->clone(*page_table)))); + BAN::Vector> mapped_regions; + TRY(mapped_regions.reserve(m_mapped_regions.size())); + for (auto& mapped_region : m_mapped_regions) + MUST(mapped_regions.push_back(TRY(mapped_region->clone(*page_table)))); auto loadable_elf = TRY(m_loadable_elf->clone(*page_table)); @@ -330,7 +333,7 @@ namespace Kernel forked->m_working_directory = BAN::move(working_directory); forked->m_page_table = BAN::move(page_table); forked->m_open_file_descriptors = BAN::move(open_file_descriptors); - forked->m_mapped_ranges = BAN::move(mapped_ranges); + forked->m_mapped_regions = BAN::move(mapped_regions); forked->m_loadable_elf = BAN::move(loadable_elf); forked->m_is_userspace = m_is_userspace; forked->m_userspace_info = m_userspace_info; @@ -373,7 +376,7 @@ namespace Kernel m_open_file_descriptors.close_cloexec(); - m_mapped_ranges.clear(); + m_mapped_regions.clear(); m_loadable_elf.clear(); m_loadable_elf = TRY(load_elf_for_exec(m_credentials, executable_path, m_working_directory, page_table())); @@ -387,8 +390,8 @@ namespace Kernel ASSERT(&Process::current() == this); // allocate memory on the new process for arguments and environment - auto create_range = - [&](const auto& container) -> BAN::UniqPtr + auto create_region = + [&](BAN::Span container) -> BAN::ErrorOr> { size_t bytes = sizeof(char*); for (auto& elem : container) @@ -397,36 +400,36 @@ namespace Kernel if (auto rem = bytes % PAGE_SIZE) bytes += PAGE_SIZE - rem; - auto range = MUST(VirtualRange::create_to_vaddr_range( + auto region = TRY(MemoryBackedRegion::create( page_table(), - 0x400000, KERNEL_OFFSET, bytes, - PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present, - true + { .start = 0x400000, .end = KERNEL_OFFSET }, + MemoryRegion::Type::PRIVATE, + PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present )); size_t data_offset = sizeof(char*) * (container.size() + 1); for (size_t i = 0; i < container.size(); i++) { - uintptr_t ptr_addr = range->vaddr() + data_offset; - range->copy_from(sizeof(char*) * i, (const uint8_t*)&ptr_addr, sizeof(char*)); - range->copy_from(data_offset, (const uint8_t*)container[i].data(), container[i].size()); + uintptr_t ptr_addr = region->vaddr() + data_offset; + TRY(region->copy_data_to_region(sizeof(char*) * i, (const uint8_t*)&ptr_addr, sizeof(char*))); + TRY(region->copy_data_to_region(data_offset, (const uint8_t*)container[i].data(), container[i].size())); data_offset += container[i].size() + 1; } uintptr_t null = 0; - range->copy_from(sizeof(char*) * container.size(), (const uint8_t*)&null, sizeof(char*)); + TRY(region->copy_data_to_region(sizeof(char*) * container.size(), (const uint8_t*)&null, sizeof(char*))); - return BAN::move(range); + return BAN::UniqPtr(BAN::move(region)); }; - auto argv_range = create_range(str_argv); - m_userspace_info.argv = (char**)argv_range->vaddr(); - MUST(m_mapped_ranges.push_back(BAN::move(argv_range))); + auto argv_region = MUST(create_region(str_argv.span())); + m_userspace_info.argv = (char**)argv_region->vaddr(); + MUST(m_mapped_regions.push_back(BAN::move(argv_region))); - auto envp_range = create_range(str_envp); - m_userspace_info.envp = (char**)envp_range->vaddr(); - MUST(m_mapped_ranges.push_back(BAN::move(envp_range))); + auto envp_region = MUST(create_region(str_envp.span())); + m_userspace_info.envp = (char**)envp_region->vaddr(); + MUST(m_mapped_regions.push_back(BAN::move(envp_region))); m_userspace_info.argc = str_argv.size(); @@ -544,11 +547,11 @@ namespace Kernel return true; } - for (auto& mapped_range : m_mapped_ranges) + for (auto& region : m_mapped_regions) { - if (!mapped_range->contains(address)) + if (!region->contains(address)) continue; - TRY(mapped_range->allocate_page_for_demand_paging(address)); + TRY(region->allocate_page_containing(address)); return true; } @@ -824,17 +827,17 @@ namespace Kernel if (args->len % PAGE_SIZE != 0) return BAN::Error::from_errno(EINVAL); - auto range = TRY(VirtualRange::create_to_vaddr_range( + auto region = TRY(MemoryBackedRegion::create( page_table(), - 0x400000, KERNEL_OFFSET, args->len, - PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present, - false + { .start = 0x400000, .end = KERNEL_OFFSET }, + MemoryRegion::Type::PRIVATE, + PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present )); LockGuard _(m_lock); - TRY(m_mapped_ranges.push_back(BAN::move(range))); - return m_mapped_ranges.back()->vaddr(); + TRY(m_mapped_regions.push_back(BAN::move(region))); + return m_mapped_regions.back()->vaddr(); } return BAN::Error::from_errno(ENOTSUP); @@ -851,13 +854,10 @@ namespace Kernel LockGuard _(m_lock); - for (size_t i = 0; i < m_mapped_ranges.size(); i++) - { - auto& range = m_mapped_ranges[i]; - if (vaddr + len < range->vaddr() || vaddr >= range->vaddr() + range->size()) - continue; - m_mapped_ranges.remove(i); - } + // FIXME: We should only map partial regions + for (size_t i = 0; i < m_mapped_regions.size(); i++) + if (m_mapped_regions[i]->overlaps(vaddr, len)) + m_mapped_regions.remove(i); return 0; } @@ -1341,10 +1341,11 @@ namespace Kernel return; // FIXME: should we allow cross mapping access? - for (auto& mapped_range : m_mapped_ranges) - if (vaddr >= mapped_range->vaddr() && vaddr + size <= mapped_range->vaddr() + mapped_range->size()) + for (auto& mapped_region : m_mapped_regions) + mapped_region->contains_fully(vaddr, size); return; + // FIXME: elf should contain full range [vaddr, vaddr + size) if (m_loadable_elf->contains(vaddr)) return;