From 7b1c573ad0428c7f169b90ad19b0fb808ba22287 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 29 Sep 2023 17:23:42 +0300 Subject: [PATCH] Kernel: Implement MAP_PRIVATE file mappings mmap() now supports mapping files with MAP_PRIVATE. --- kernel/CMakeLists.txt | 1 + .../include/kernel/Memory/FileBackedRegion.h | 30 +++++ kernel/include/kernel/Memory/MemoryRegion.h | 2 +- kernel/include/kernel/OpenFileDescriptorSet.h | 1 + kernel/kernel/Memory/FileBackedRegion.cpp | 112 ++++++++++++++++++ kernel/kernel/Memory/MemoryBackedRegion.cpp | 5 +- kernel/kernel/OpenFileDescriptorSet.cpp | 6 + kernel/kernel/Process.cpp | 60 ++++++++-- 8 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 kernel/include/kernel/Memory/FileBackedRegion.h create mode 100644 kernel/kernel/Memory/FileBackedRegion.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 281a560c5e..e7e16fb537 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -32,6 +32,7 @@ set(KERNEL_SOURCES kernel/Input/PS2Keymap.cpp kernel/InterruptController.cpp kernel/kernel.cpp + kernel/Memory/FileBackedRegion.cpp kernel/Memory/GeneralAllocator.cpp kernel/Memory/Heap.cpp kernel/Memory/kmalloc.cpp diff --git a/kernel/include/kernel/Memory/FileBackedRegion.h b/kernel/include/kernel/Memory/FileBackedRegion.h new file mode 100644 index 0000000000..b2d0df94eb --- /dev/null +++ b/kernel/include/kernel/Memory/FileBackedRegion.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace Kernel +{ + + class FileBackedRegion final : public MemoryRegion + { + BAN_NON_COPYABLE(FileBackedRegion); + BAN_NON_MOVABLE(FileBackedRegion); + + public: + static BAN::ErrorOr> create(BAN::RefPtr, PageTable&, off_t offset, size_t size, AddressRange address_range, Type, PageTable::flags_t); + ~FileBackedRegion(); + + virtual BAN::ErrorOr allocate_page_containing(vaddr_t vaddr) override; + + virtual BAN::ErrorOr> clone(PageTable& new_page_table) override; + + private: + FileBackedRegion(BAN::RefPtr, PageTable&, off_t offset, ssize_t size, Type flags, PageTable::flags_t page_flags); + + private: + BAN::RefPtr m_inode; + const off_t m_offset; + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/Memory/MemoryRegion.h b/kernel/include/kernel/Memory/MemoryRegion.h index b734487185..c1a5f93666 100644 --- a/kernel/include/kernel/Memory/MemoryRegion.h +++ b/kernel/include/kernel/Memory/MemoryRegion.h @@ -21,7 +21,7 @@ namespace Kernel BAN_NON_MOVABLE(MemoryRegion); public: - enum Type : uint8_t + enum class Type : uint8_t { PRIVATE, SHARED diff --git a/kernel/include/kernel/OpenFileDescriptorSet.h b/kernel/include/kernel/OpenFileDescriptorSet.h index 17057d2f81..6996bc9617 100644 --- a/kernel/include/kernel/OpenFileDescriptorSet.h +++ b/kernel/include/kernel/OpenFileDescriptorSet.h @@ -48,6 +48,7 @@ namespace Kernel BAN::ErrorOr path_of(int) const; BAN::ErrorOr> inode_of(int); + BAN::ErrorOr flags_of(int) const; private: struct OpenFileDescription : public BAN::RefCounted diff --git a/kernel/kernel/Memory/FileBackedRegion.cpp b/kernel/kernel/Memory/FileBackedRegion.cpp new file mode 100644 index 0000000000..f4bdb8025f --- /dev/null +++ b/kernel/kernel/Memory/FileBackedRegion.cpp @@ -0,0 +1,112 @@ +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr> FileBackedRegion::create(BAN::RefPtr inode, PageTable& page_table, off_t offset, size_t size, AddressRange address_range, Type type, PageTable::flags_t flags) + { + ASSERT(inode->mode().ifreg()); + + if (type != Type::PRIVATE) + return BAN::Error::from_errno(ENOTSUP); + + if (offset < 0 || offset % PAGE_SIZE || size == 0) + return BAN::Error::from_errno(EINVAL); + if (size > (size_t)inode->size() || (size_t)offset > (size_t)inode->size() - size) + return BAN::Error::from_errno(EOVERFLOW); + + auto* region_ptr = new FileBackedRegion(inode, page_table, offset, 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; + } + + FileBackedRegion::FileBackedRegion(BAN::RefPtr inode, PageTable& page_table, off_t offset, ssize_t size, Type type, PageTable::flags_t flags) + : MemoryRegion(page_table, size, type, flags) + , m_inode(inode) + , m_offset(offset) + { + } + + FileBackedRegion::~FileBackedRegion() + { + if (m_vaddr == 0) + return; + + 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 FileBackedRegion::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); + + size_t file_offset = m_offset + (vaddr - m_vaddr); + size_t bytes = BAN::Math::min(m_size - file_offset, PAGE_SIZE); + + BAN::ErrorOr read_ret = 0; + + // Zero out the new page + if (&PageTable::current() == &m_page_table) + read_ret = m_inode->read(file_offset, (void*)vaddr, bytes); + else + { + LockGuard _(PageTable::current()); + ASSERT(PageTable::current().is_page_free(0)); + + PageTable::current().map_page_at(paddr, 0, PageTable::Flags::ReadWrite | PageTable::Flags::Present); + read_ret = m_inode->read(file_offset, (void*)0, bytes); + memset((void*)0, 0x00, PAGE_SIZE); + PageTable::current().unmap_page(0); + } + + if (read_ret.is_error()) + { + Heap::get().release_page(paddr); + m_page_table.unmap_page(vaddr); + return read_ret.release_error(); + } + + if (read_ret.value() < bytes) + { + dwarnln("Only {}/{} bytes read", read_ret.value(), bytes); + Heap::get().release_page(paddr); + m_page_table.unmap_page(vaddr); + return BAN::Error::from_errno(EIO); + } + + return true; + } + + BAN::ErrorOr> FileBackedRegion::clone(PageTable& new_page_table) + { + ASSERT_NOT_REACHED(); + } + +} diff --git a/kernel/kernel/Memory/MemoryBackedRegion.cpp b/kernel/kernel/Memory/MemoryBackedRegion.cpp index fbec159e4d..971a8592eb 100644 --- a/kernel/kernel/Memory/MemoryBackedRegion.cpp +++ b/kernel/kernel/Memory/MemoryBackedRegion.cpp @@ -1,13 +1,14 @@ +#include #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); + if (type != Type::PRIVATE) + return BAN::Error::from_errno(ENOTSUP); auto* region_ptr = new MemoryBackedRegion(page_table, size, type, flags); if (region_ptr == nullptr) diff --git a/kernel/kernel/OpenFileDescriptorSet.cpp b/kernel/kernel/OpenFileDescriptorSet.cpp index 3226cae041..f160f8acef 100644 --- a/kernel/kernel/OpenFileDescriptorSet.cpp +++ b/kernel/kernel/OpenFileDescriptorSet.cpp @@ -309,6 +309,12 @@ namespace Kernel return m_open_files[fd]->inode; } + BAN::ErrorOr OpenFileDescriptorSet::flags_of(int fd) const + { + TRY(validate_fd(fd)); + return m_open_files[fd]->flags; + } + BAN::ErrorOr OpenFileDescriptorSet::validate_fd(int fd) const { if (fd < 0 || fd >= (int)m_open_files.size()) diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index 8f4f29a9a6..713e345dd8 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -810,29 +811,64 @@ namespace Kernel if (args->prot != PROT_NONE && args->prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) return BAN::Error::from_errno(EINVAL); - PageTable::flags_t flags = PageTable::Flags::UserSupervisor; - if (args->prot & PROT_READ) - flags |= PageTable::Flags::Present; - if (args->prot & PROT_WRITE) - flags |= PageTable::Flags::ReadWrite | PageTable::Flags::Present; - if (args->prot & PROT_EXEC) - flags |= PageTable::Flags::Execute | PageTable::Flags::Present; + if (args->flags & MAP_FIXED) + return BAN::Error::from_errno(ENOTSUP); - if (args->flags == (MAP_ANONYMOUS | MAP_PRIVATE)) + if (!(args->flags & MAP_PRIVATE) == !(args->flags & MAP_SHARED)) + return BAN::Error::from_errno(EINVAL); + auto region_type = (args->flags & MAP_PRIVATE) ? MemoryRegion::Type::PRIVATE : MemoryRegion::Type::SHARED; + + PageTable::flags_t page_flags = 0; + if (args->prot & PROT_READ) + page_flags |= PageTable::Flags::Present; + if (args->prot & PROT_WRITE) + page_flags |= PageTable::Flags::ReadWrite | PageTable::Flags::Present; + if (args->prot & PROT_EXEC) + page_flags |= PageTable::Flags::Execute | PageTable::Flags::Present; + + if (page_flags == 0) + page_flags = PageTable::Flags::Reserved; + else + page_flags |= PageTable::Flags::UserSupervisor; + + if (args->flags & MAP_ANONYMOUS) { if (args->addr != nullptr) return BAN::Error::from_errno(ENOTSUP); if (args->off != 0) return BAN::Error::from_errno(EINVAL); - if (args->len % PAGE_SIZE != 0) - return BAN::Error::from_errno(EINVAL); auto region = TRY(MemoryBackedRegion::create( page_table(), args->len, { .start = 0x400000, .end = KERNEL_OFFSET }, - MemoryRegion::Type::PRIVATE, - PageTable::Flags::UserSupervisor | PageTable::Flags::ReadWrite | PageTable::Flags::Present + region_type, page_flags + )); + + LockGuard _(m_lock); + TRY(m_mapped_regions.push_back(BAN::move(region))); + return m_mapped_regions.back()->vaddr(); + } + + auto inode = TRY(m_open_file_descriptors.inode_of(args->fildes)); + if (inode->mode().ifreg()) + { + if (args->addr != nullptr) + return BAN::Error::from_errno(ENOTSUP); + + auto inode_flags = TRY(m_open_file_descriptors.flags_of(args->fildes)); + if (!(inode_flags & O_RDONLY)) + return BAN::Error::from_errno(EACCES); + if (region_type == MemoryRegion::Type::SHARED) + if (!(args->prot & PROT_WRITE) || !(inode_flags & O_WRONLY)) + return BAN::Error::from_errno(EACCES); + + auto region = TRY(FileBackedRegion::create( + inode, + page_table(), + args->off, args->len, + { .start = 0x400000, .end = KERNEL_OFFSET }, + region_type, page_flags )); LockGuard _(m_lock);