Kernel: Implement MAP_PRIVATE file mappings
mmap() now supports mapping files with MAP_PRIVATE.
This commit is contained in:
		
							parent
							
								
									4a92f44cf6
								
							
						
					
					
						commit
						db5d6a7f80
					
				|  | @ -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 | ||||
|  |  | |||
|  | @ -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; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  | @ -21,7 +21,7 @@ namespace Kernel | |||
| 		BAN_NON_MOVABLE(MemoryRegion); | ||||
| 
 | ||||
| 	public: | ||||
| 		enum Type : uint8_t | ||||
| 		enum class Type : uint8_t | ||||
| 		{ | ||||
| 			PRIVATE, | ||||
| 			SHARED | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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()) | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue