Kernel: Implement MAP_PRIVATE file mappings

mmap() now supports mapping files with MAP_PRIVATE.
This commit is contained in:
Bananymous 2023-09-29 17:23:42 +03:00
parent 4a92f44cf6
commit db5d6a7f80
8 changed files with 202 additions and 15 deletions

View File

@ -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

View File

@ -0,0 +1,30 @@
#pragma once
#include <kernel/FS/Inode.h>
#include <kernel/Memory/MemoryRegion.h>
namespace Kernel
{
class FileBackedRegion final : public MemoryRegion
{
BAN_NON_COPYABLE(FileBackedRegion);
BAN_NON_MOVABLE(FileBackedRegion);
public:
static BAN::ErrorOr<BAN::UniqPtr<FileBackedRegion>> create(BAN::RefPtr<Inode>, PageTable&, off_t offset, size_t size, AddressRange address_range, Type, PageTable::flags_t);
~FileBackedRegion();
virtual BAN::ErrorOr<bool> allocate_page_containing(vaddr_t vaddr) override;
virtual BAN::ErrorOr<BAN::UniqPtr<MemoryRegion>> clone(PageTable& new_page_table) override;
private:
FileBackedRegion(BAN::RefPtr<Inode>, PageTable&, off_t offset, ssize_t size, Type flags, PageTable::flags_t page_flags);
private:
BAN::RefPtr<Inode> m_inode;
const off_t m_offset;
};
}

View File

@ -21,7 +21,7 @@ namespace Kernel
BAN_NON_MOVABLE(MemoryRegion);
public:
enum Type : uint8_t
enum class Type : uint8_t
{
PRIVATE,
SHARED

View File

@ -48,6 +48,7 @@ namespace Kernel
BAN::ErrorOr<BAN::StringView> path_of(int) const;
BAN::ErrorOr<BAN::RefPtr<Inode>> inode_of(int);
BAN::ErrorOr<int> flags_of(int) const;
private:
struct OpenFileDescription : public BAN::RefCounted<OpenFileDescription>

View File

@ -0,0 +1,112 @@
#include <kernel/LockGuard.h>
#include <kernel/Memory/FileBackedRegion.h>
#include <kernel/Memory/Heap.h>
namespace Kernel
{
BAN::ErrorOr<BAN::UniqPtr<FileBackedRegion>> FileBackedRegion::create(BAN::RefPtr<Inode> 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<FileBackedRegion>::adopt(region_ptr);
TRY(region->initialize(address_range));
return region;
}
FileBackedRegion::FileBackedRegion(BAN::RefPtr<Inode> 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<size_t>(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<bool> 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<size_t>(m_size - file_offset, PAGE_SIZE);
BAN::ErrorOr<size_t> 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<BAN::UniqPtr<MemoryRegion>> FileBackedRegion::clone(PageTable& new_page_table)
{
ASSERT_NOT_REACHED();
}
}

View File

@ -1,13 +1,14 @@
#include <kernel/LockGuard.h>
#include <kernel/Memory/Heap.h>
#include <kernel/Memory/MemoryBackedRegion.h>
#include <kernel/LockGuard.h>
namespace Kernel
{
BAN::ErrorOr<BAN::UniqPtr<MemoryBackedRegion>> 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)

View File

@ -309,6 +309,12 @@ namespace Kernel
return m_open_files[fd]->inode;
}
BAN::ErrorOr<int> OpenFileDescriptorSet::flags_of(int fd) const
{
TRY(validate_fd(fd));
return m_open_files[fd]->flags;
}
BAN::ErrorOr<void> OpenFileDescriptorSet::validate_fd(int fd) const
{
if (fd < 0 || fd >= (int)m_open_files.size())

View File

@ -6,6 +6,7 @@
#include <kernel/IDT.h>
#include <kernel/InterruptController.h>
#include <kernel/LockGuard.h>
#include <kernel/Memory/FileBackedRegion.h>
#include <kernel/Memory/Heap.h>
#include <kernel/Memory/MemoryBackedRegion.h>
#include <kernel/Memory/PageTableScope.h>
@ -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);