From 13aee1755e1b825b3d30a1d8dee8a78e8e6b957a Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Sat, 26 Sep 2020 21:47:15 -0700 Subject: [PATCH] [kernel] Spit out vm_area types The vm_space allow() functionality was a bit janky; using VMAs for all regions would be a lot cleaner. To that end, this change: - Adds a "static array" ctor to kutil::vector for setting the kernel address space's VMA list. This way a kernel heap VMA can be created without the heap already existing. - Splits vm_area into different subclasses depending on desired behavior - Splits out the concept of vm_mapper which maps vm_areas to vm_spaces, so that some kinds of VMA can be inherently single-space - Implements VMA resizing so that userspace can grow allocations. - Obsolete page_table_indices is removed Also, the following bugs were fixed: - kutil::map iterators on empty maps no longer break - memory::page_count was doing page-align, not page-count See: Github bug #242 See: [frobozz blog post](https://jsix.dev/posts/frobozz/) Tags: --- modules.yaml | 1 + src/drivers/nulldrv/main.cpp | 18 ++- src/include/kernel_memory.h | 4 +- src/include/syscalls.inc | 5 +- src/kernel/memory_bootstrap.cpp | 9 +- src/kernel/objects/process.cpp | 1 + src/kernel/objects/vm_area.cpp | 126 ++++++++--------- src/kernel/objects/vm_area.h | 149 ++++++++++++++++----- src/kernel/page_table.cpp | 24 +--- src/kernel/page_table.h | 19 --- src/kernel/syscall.s | 5 + src/kernel/syscalls/vm_area.cpp | 29 +++- src/kernel/vm_mapper.cpp | 98 ++++++++++++++ src/kernel/vm_mapper.h | 79 +++++++++++ src/kernel/vm_space.cpp | 98 +++++++++++--- src/kernel/vm_space.h | 36 +++-- src/libraries/kutil/include/kutil/map.h | 3 + src/libraries/kutil/include/kutil/vector.h | 11 +- src/libraries/libc/arch/x86_64/syscalls.s | 8 +- 19 files changed, 533 insertions(+), 190 deletions(-) create mode 100644 src/kernel/vm_mapper.cpp create mode 100644 src/kernel/vm_mapper.h diff --git a/modules.yaml b/modules.yaml index b6d2284..d1fdc32 100644 --- a/modules.yaml +++ b/modules.yaml @@ -60,6 +60,7 @@ modules: - src/kernel/syscalls/thread.cpp - src/kernel/syscalls/vm_area.cpp - src/kernel/task.s + - src/kernel/vm_mapper.cpp - src/kernel/vm_space.cpp - src/kernel/crtn.s diff --git a/src/drivers/nulldrv/main.cpp b/src/drivers/nulldrv/main.cpp index d0ca593..1d579e8 100644 --- a/src/drivers/nulldrv/main.cpp +++ b/src/drivers/nulldrv/main.cpp @@ -63,13 +63,23 @@ main(int argc, const char **argv) uintptr_t base = 0xcc0000000; j6_handle_t vma = j6_handle_invalid; - j6_status_t result = _syscall_vma_create_map(&vma, 0x100000, base); + j6_status_t result = _syscall_vma_create_map(&vma, 0x100000, base, 1); if (result != j6_status_ok) return result; + size_t size = 0x800000; + result = _syscall_vma_resize(vma, &size); + if (result != j6_status_ok) + return result; + + if (size == 0x800000) + _syscall_system_log("main thread resized memory area"); + uint64_t *vma_ptr = reinterpret_cast(base); - for (int i = 0; i < 4096; ++i) - vma_ptr[i] = uint64_t(i); + for (int i = 0; i < 300; ++i) + vma_ptr[i * 512] = uint64_t(i); + + _syscall_system_log("main thread wrote to memory area"); result = _syscall_endpoint_create(&endp); if (result != j6_status_ok) @@ -84,7 +94,7 @@ main(int argc, const char **argv) _syscall_system_log("main thread created sub thread"); char message[] = "MAIN THREAD SUCCESSFULLY CALLED SENDRECV IF THIS IS LOWERCASE"; - size_t size = sizeof(message); + size = sizeof(message); result = _syscall_endpoint_sendrecv(endp, &size, (void*)message); if (result != j6_status_ok) return result; diff --git a/src/include/kernel_memory.h b/src/include/kernel_memory.h index fe8d0c2..5296220 100644 --- a/src/include/kernel_memory.h +++ b/src/include/kernel_memory.h @@ -17,7 +17,7 @@ namespace memory { constexpr uintptr_t page_offset = 0xffffc00000000000ull; /// Max number of pages for a kernel stack - constexpr unsigned kernel_stack_pages = 1; + constexpr unsigned kernel_stack_pages = 4; /// Max number of pages for a kernel buffer constexpr unsigned kernel_buffer_pages = 16; @@ -62,6 +62,6 @@ namespace memory { /// \arg bytes The number of bytes desired /// \returns The number of pages needed to contain the desired bytes inline size_t page_count(size_t bytes) { - return ((bytes - 1) & (frame_size - 1)) + 1; + return ((bytes - 1) >> 12) + 1; } } // namespace memory diff --git a/src/include/syscalls.inc b/src/include/syscalls.inc index a75e5a0..35df1c9 100644 --- a/src/include/syscalls.inc +++ b/src/include/syscalls.inc @@ -24,8 +24,9 @@ SYSCALL(0x2a, endpoint_send, j6_handle_t, size_t, void *) SYSCALL(0x2b, endpoint_receive, j6_handle_t, size_t *, void *) SYSCALL(0x2c, endpoint_sendrecv, j6_handle_t, size_t *, void *) -SYSCALL(0x30, vma_create, j6_handle_t *, size_t) -SYSCALL(0x31, vma_create_map, j6_handle_t *, size_t, uintptr_t) +SYSCALL(0x30, vma_create, j6_handle_t *, size_t, uint32_t) +SYSCALL(0x31, vma_create_map, j6_handle_t *, size_t, uintptr_t, uint32_t) SYSCALL(0x32, vma_close, j6_handle_t) SYSCALL(0x33, vma_map, j6_handle_t, uintptr_t) SYSCALL(0x34, vma_unmap, j6_handle_t) +SYSCALL(0x34, vma_resize, j6_handle_t, size_t *) diff --git a/src/kernel/memory_bootstrap.cpp b/src/kernel/memory_bootstrap.cpp index 25512ae..5b69074 100644 --- a/src/kernel/memory_bootstrap.cpp +++ b/src/kernel/memory_bootstrap.cpp @@ -35,6 +35,9 @@ kutil::heap_allocator &g_kernel_heap = __g_kernel_heap_storage.value; static kutil::no_construct __g_frame_allocator_storage; frame_allocator &g_frame_allocator = __g_frame_allocator_storage.value; +static kutil::no_construct __g_kernel_heap_area_storage; +vm_area_open &g_kernel_heap_area = __g_kernel_heap_area_storage.value; + void * operator new(size_t size) { return g_kernel_heap.allocate(size); } void * operator new [] (size_t size) { return g_kernel_heap.allocate(size); } void operator delete (void *p) noexcept { return g_kernel_heap.free(p); } @@ -104,7 +107,11 @@ memory_initialize_pre_ctors(args::header *kargs) process *kp = process::create_kernel_process(kpml4); vm_space &vm = kp->space(); - vm.allow(memory::heap_start, memory::kernel_max_heap, true); + + vm_area *heap = new (&g_kernel_heap_area) + vm_area_open(memory::kernel_max_heap, vm, vm_flags::write); + + vm.add(memory::heap_start, heap); } void diff --git a/src/kernel/objects/process.cpp b/src/kernel/objects/process.cpp index cfa852d..41f50b2 100644 --- a/src/kernel/objects/process.cpp +++ b/src/kernel/objects/process.cpp @@ -22,6 +22,7 @@ process::process() : s_processes.append(this); } +// The "kernel process"-only constructor process::process(page_table *kpml4) : kobject {kobject::type::process}, m_space {kpml4}, diff --git a/src/kernel/objects/vm_area.cpp b/src/kernel/objects/vm_area.cpp index 4c31cdf..b6cac83 100644 --- a/src/kernel/objects/vm_area.cpp +++ b/src/kernel/objects/vm_area.cpp @@ -1,4 +1,3 @@ -#include "frame_allocator.h" #include "kernel_memory.h" #include "objects/vm_area.h" #include "vm_space.h" @@ -6,64 +5,55 @@ using memory::frame_size; vm_area::vm_area(size_t size, vm_flags flags) : - m_size(size), - m_flags(flags), - kobject(kobject::type::vma) + m_size {size}, + m_flags {flags}, + kobject {kobject::type::vma} { } -vm_area::~vm_area() -{ - for (auto &it : m_spaces) - remove_from(it.key); - - frame_allocator &fa = frame_allocator::get(); - for (auto &m : m_mappings) - fa.free(m.phys, m.count); -} +vm_area::~vm_area() {} size_t vm_area::resize(size_t size) { + if (mapper().can_resize(size)) + m_size = size; return m_size; } void -vm_area::add_to(vm_space *space, uintptr_t base) +vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count) { - kassert(space, "null vm_space passed to vm_area::add_to"); - - // Multiple copies in the same space not yet supported - uintptr_t *prev = m_spaces.find(space); - if (prev) return; - - if (m_spaces.count()) { - // Just get the first address space in the map - auto it = m_spaces.begin(); - vm_space *source = it->key; - uintptr_t from = it->val; - size_t pages = memory::page_count(m_size); - space->copy_from(*source, from, base, pages); - } - - m_spaces.insert(space, base); - space->add(base, this); + mapper().map(offset, count, phys); } void -vm_area::remove_from(vm_space *space) +vm_area::uncommit(uintptr_t offset, size_t count) +{ + mapper().unmap(offset, count); +} + +void +vm_area::on_no_handles() +{ + kobject::on_no_handles(); + delete this; +} + + + +vm_area_shared::vm_area_shared(size_t size, vm_flags flags) : + m_mapper {*this}, + vm_area {size, flags} +{ +} + +vm_area_shared::~vm_area_shared() { - size_t pages = memory::page_count(m_size); - uintptr_t *base = m_spaces.find(space); - if (space && base) { - space->clear(*base, pages); - m_spaces.erase(space); - } - space->remove(this); } size_t -vm_area::overlaps(uintptr_t offset, size_t pages, size_t *count) +vm_area_shared::overlaps(uintptr_t offset, size_t pages, size_t *count) { size_t first = 0; size_t n = 0; @@ -84,14 +74,16 @@ vm_area::overlaps(uintptr_t offset, size_t pages, size_t *count) return first; } -bool -vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count) +void +vm_area_shared::commit(uintptr_t phys, uintptr_t offset, size_t count) { size_t n = 0; size_t o = overlaps(offset, count, &n); // Mapping overlaps not allowed - if (n) return false; + if (n) return; + + m_mapper.map(offset, count, phys); o = m_mappings.sorted_insert({ .offset = offset, @@ -99,7 +91,6 @@ vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count) .phys = phys}); n = 1; - map(offset, count, phys); // Try to expand to abutting similar areas if (o > 0 && @@ -134,15 +125,17 @@ vm_area::commit(uintptr_t phys, uintptr_t offset, size_t count) if (n > 1) m_mappings.remove_at(o+1, n-1); - return true; + return; } -bool -vm_area::uncommit(uintptr_t offset, size_t count) +void +vm_area_shared::uncommit(uintptr_t offset, size_t count) { size_t n = 0; size_t o = overlaps(offset, count, &n); - if (!n) return true; + if (!n) return; + + m_mapper.unmap(offset, count); mapping *first = &m_mappings[o]; mapping *last = &m_mappings[o+n-1]; @@ -169,7 +162,7 @@ vm_area::uncommit(uintptr_t offset, size_t count) size_t remove_pages = first->count; first->count = leading / frame_size; remove_pages -= first->count; - unmap(first->end(), remove_pages); + m_mapper.unmap(first->end(), remove_pages); } if (trailing) { @@ -178,7 +171,7 @@ vm_area::uncommit(uintptr_t offset, size_t count) last->offset = end; last->count = trailing / frame_size; remove_pages -= last->count; - unmap(remove_off, remove_pages); + m_mapper.unmap(remove_off, remove_pages); last->phys += remove_pages * frame_size; } @@ -189,39 +182,32 @@ vm_area::uncommit(uintptr_t offset, size_t count) if (offset <= m.offset && end >= m.end()) { if (!delete_count) delete_start = i; ++delete_count; - unmap(m.offset, m.count); + m_mapper.unmap(m.offset, m.count); } } if (delete_count) m_mappings.remove_at(delete_start, delete_count); - return true; + return; +} + + +vm_area_open::vm_area_open(size_t size, vm_space &space, vm_flags flags) : + m_mapper(*this, space), + vm_area(size, flags) +{ } void -vm_area::map(uintptr_t offset, size_t count, uintptr_t phys) +vm_area_open::commit(uintptr_t phys, uintptr_t offset, size_t count) { - for (auto &it : m_spaces) { - uintptr_t addr = it.val + offset; - vm_space *space = it.key; - space->page_in(addr, phys, count); - } + m_mapper.map(offset, count, phys); } void -vm_area::unmap(uintptr_t offset, size_t count) +vm_area_open::uncommit(uintptr_t offset, size_t count) { - for (auto &it : m_spaces) { - uintptr_t addr = it.val + offset; - vm_space *space = it.key; - space->clear(addr, count); - } + m_mapper.unmap(offset, count); } -void -vm_area::on_no_handles() -{ - kobject::on_no_handles(); - delete this; -} diff --git a/src/kernel/objects/vm_area.h b/src/kernel/objects/vm_area.h index 3ed8cea..da1e1c9 100644 --- a/src/kernel/objects/vm_area.h +++ b/src/kernel/objects/vm_area.h @@ -2,12 +2,16 @@ /// \file vm_area.h /// Definition of VMA objects and related functions +#include +#include + #include "j6/signals.h" #include "kutil/enum_bitfields.h" -#include "kutil/map.h" +#include "kutil/vector.h" #include "kernel_memory.h" #include "objects/kobject.h" +#include "vm_mapper.h" class vm_space; @@ -15,14 +19,17 @@ enum class vm_flags : uint32_t { none = 0x00000000, - zero = 0x00000001, - contiguous = 0x00000002, + write = 0x00000001, + exec = 0x00000002, + + zero = 0x00000010, + contiguous = 0x00000020, large_pages = 0x00000100, - huge_pages = 0x00000200 -}; + huge_pages = 0x00000200, -IS_BITFIELD(vm_flags); + user_mask = 0x0000ffff ///< flags allowed via syscall +}; /// Virtual memory areas allow control over memory allocation @@ -30,16 +37,20 @@ class vm_area : public kobject { public: + static constexpr kobject::type type = kobject::type::vma; + /// Constructor. /// \arg size Initial virtual size of the memory area /// \arg flags Flags for this memory area vm_area(size_t size, vm_flags flags = vm_flags::none); + virtual ~vm_area(); - static constexpr kobject::type type = kobject::type::vma; - /// Get the current virtual size of the memory area - size_t size() const { return m_size; } + inline size_t size() const { return m_size; } + + /// Get the flags set for this area + inline vm_flags flags() const { return m_flags; } /// Change the virtual size of the memory area. This may cause /// deallocation if the new size is smaller than the current size. @@ -49,37 +60,52 @@ public: /// \returns The new virtual size size_t resize(size_t size); - /// Add this virtual area to a process' virtual address space. If - /// the given base address is zero, a base address will be chosen - /// automatically. - /// \arg s The target address space - /// \arg base The base address this area will be mapped to - void add_to(vm_space *s, uintptr_t base); + /// Get the mapper object that maps this area to address spaces + virtual vm_mapper & mapper() = 0; + virtual const vm_mapper & mapper() const = 0; - /// Remove this virtual area from a process' virtual address space. - /// \arg s The target address space - void remove_from(vm_space *s); + /// Check whether allocation at the given offset is allowed + virtual bool allowed(uintptr_t offset) const { return true; } /// Commit contiguous physical pages to this area /// \arg phys The physical address of the first page /// \arg offset The offset from the start of this area these pages represent /// \arg count The number of pages - /// \returns True if successful - bool commit(uintptr_t phys, uintptr_t offset, size_t count); + virtual void commit(uintptr_t phys, uintptr_t offset, size_t count); /// Uncommit physical pages from this area /// \arg offset The offset from the start of this area these pages represent /// \arg count The number of pages - /// \returns True if successful - bool uncommit(uintptr_t offset, size_t count); - - /// Get the flags set for this area - vm_flags flags() const { return m_flags; } + virtual void uncommit(uintptr_t offset, size_t count); protected: virtual void on_no_handles() override; + size_t m_size; + vm_flags m_flags; +}; + + +/// The standard, sharable, user-controllable VMA type +class vm_area_shared : + public vm_area +{ +public: + /// Constructor. + /// \arg size Initial virtual size of the memory area + /// \arg flags Flags for this memory area + vm_area_shared(size_t size, vm_flags flags = vm_flags::none); + virtual ~vm_area_shared(); + + virtual vm_mapper & mapper() override { return m_mapper; } + virtual const vm_mapper & mapper() const override { return m_mapper; } + + virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override; + virtual void uncommit(uintptr_t offset, size_t count) override; + private: + vm_mapper_multi m_mapper; + struct mapping { uintptr_t offset; size_t count; @@ -95,11 +121,72 @@ private: size_t overlaps(uintptr_t offset, size_t pages, size_t *count); - void map(uintptr_t offset, size_t count, uintptr_t phys); - void unmap(uintptr_t offset, size_t count); - - size_t m_size; - vm_flags m_flags; - kutil::map m_spaces; kutil::vector m_mappings; }; + +/// Area split into standard-sized segments +class vm_area_buffers : + public vm_area +{ +public: + /// Constructor. + /// \arg size Initial virtual size of the memory area + /// \arg space The address space this area belongs to + /// \arg flags Flags for this memory area + vm_area_buffers(size_t size, vm_space &space, vm_flags flags); + + virtual vm_mapper & mapper() override { return m_mapper; } + virtual const vm_mapper & mapper() const override { return m_mapper; } + + virtual bool allowed(uintptr_t offset) const override; + virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override; + virtual void uncommit(uintptr_t offset, size_t count) override; + +private: + vm_mapper_single m_mapper; +}; + + +/// Area backed by an external source (like a loaded program) +class vm_area_backed : + public vm_area +{ +public: + /// Constructor. + /// \arg size Initial virtual size of the memory area + /// \arg flags Flags for this memory area + vm_area_backed(size_t size, vm_flags flags); + + virtual vm_mapper & mapper() override { return m_mapper; } + virtual const vm_mapper & mapper() const override { return m_mapper; } + + virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override; + virtual void uncommit(uintptr_t offset, size_t count) override; + +private: + vm_mapper_multi m_mapper; +}; + + +/// Area that allows open allocation (eg, kernel heap) +class vm_area_open : + public vm_area +{ +public: + /// Constructor. + /// \arg size Initial virtual size of the memory area + /// \arg space The address space this area belongs to + /// \arg flags Flags for this memory area + vm_area_open(size_t size, vm_space &space, vm_flags flags); + + virtual vm_mapper & mapper() override { return m_mapper; } + virtual const vm_mapper & mapper() const override { return m_mapper; } + + virtual void commit(uintptr_t phys, uintptr_t offset, size_t count) override; + virtual void uncommit(uintptr_t offset, size_t count) override; + +private: + vm_mapper_single m_mapper; +}; + +IS_BITFIELD(vm_flags); diff --git a/src/kernel/page_table.cpp b/src/kernel/page_table.cpp index af1500f..38ee0a6 100644 --- a/src/kernel/page_table.cpp +++ b/src/kernel/page_table.cpp @@ -165,6 +165,7 @@ page_table::iterator::ensure_table(level l) m_table[unsigned(l)] = table; parent = (phys & ~0xfffull) | flags; + __atomic_thread_fence(__ATOMIC_SEQ_CST); } page_table * @@ -296,26 +297,3 @@ page_table::dump(page_table::level lvl, bool recurse) } } } - -page_table_indices::page_table_indices(uint64_t v) : - index{ - (v >> 39) & 0x1ff, - (v >> 30) & 0x1ff, - (v >> 21) & 0x1ff, - (v >> 12) & 0x1ff } -{} - -uintptr_t -page_table_indices::addr() const -{ - return - (index[0] << 39) | - (index[1] << 30) | - (index[2] << 21) | - (index[3] << 12); -} - -bool operator==(const page_table_indices &l, const page_table_indices &r) -{ - return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3]; -} diff --git a/src/kernel/page_table.h b/src/kernel/page_table.h index 51bea87..27e916f 100644 --- a/src/kernel/page_table.h +++ b/src/kernel/page_table.h @@ -177,25 +177,6 @@ struct page_table }; -/// Helper struct for computing page table indices of a given address. -struct page_table_indices -{ - page_table_indices(uint64_t v = 0); - - uintptr_t addr() const; - - inline operator uintptr_t() const { return addr(); } - - /// Get the index for a given level of page table. - uint64_t & operator[](int i) { return index[i]; } - uint64_t operator[](int i) const { return index[i]; } - uint64_t & operator[](page_table::level i) { return index[static_cast(i)]; } - uint64_t operator[](page_table::level i) const { return index[static_cast(i)]; } - uint64_t index[4]; ///< Indices for each level of tables. -}; - -bool operator==(const page_table_indices &l, const page_table_indices &r); - inline page_table::level operator+(page_table::level a, unsigned b) { return static_cast(static_cast(a) + b); } diff --git a/src/kernel/syscall.s b/src/kernel/syscall.s index 5fae6ea..b95878b 100644 --- a/src/kernel/syscall.s +++ b/src/kernel/syscall.s @@ -22,6 +22,11 @@ syscall_handler_prelude: push rbp mov rbp, rsp + ; account for the hole in the sysv abi + ; argument list since SYSCALL uses rcx + mov rcx, r8 + mov r8, r9 + push rbx push r11 push r12 diff --git a/src/kernel/syscalls/vm_area.cpp b/src/kernel/syscalls/vm_area.cpp index cb58db6..b61eb59 100644 --- a/src/kernel/syscalls/vm_area.cpp +++ b/src/kernel/syscalls/vm_area.cpp @@ -11,17 +11,19 @@ namespace syscalls { j6_status_t -vma_create(j6_handle_t *handle, size_t size) +vma_create(j6_handle_t *handle, size_t size, uint32_t flags) { - construct_handle(handle, size); + vm_flags f = vm_flags::user_mask & flags; + construct_handle(handle, size, f); return j6_status_ok; } j6_status_t -vma_create_map(j6_handle_t *handle, size_t size, uintptr_t base) +vma_create_map(j6_handle_t *handle, size_t size, uintptr_t base, uint32_t flags) { - vm_area *a = construct_handle(handle, size); - a->add_to(&process::current().space(), base); + vm_flags f = vm_flags::user_mask & flags; + vm_area *a = construct_handle(handle, size, f); + process::current().space().add(base, a); return j6_status_ok; } @@ -31,7 +33,7 @@ vma_map(j6_handle_t handle, uintptr_t base) vm_area *a = get_handle(handle); if (!a) return j6_err_invalid_arg; - a->add_to(&process::current().space(), base); + process::current().space().add(base, a); return j6_status_ok; } @@ -41,7 +43,7 @@ vma_unmap(j6_handle_t handle) vm_area *a = get_handle(handle); if (!a) return j6_err_invalid_arg; - a->remove_from(&process::current().space()); + process::current().space().remove(a); return j6_status_ok; } @@ -56,6 +58,19 @@ vma_close(j6_handle_t handle) return j6_status_ok; } +j6_status_t +vma_resize(j6_handle_t handle, size_t *size) +{ + if (!size) + return j6_err_invalid_arg; + + vm_area *a = get_handle(handle); + if (!a) return j6_err_invalid_arg; + + *size = a->resize(*size); + return j6_status_ok; +} + } // namespace syscalls diff --git a/src/kernel/vm_mapper.cpp b/src/kernel/vm_mapper.cpp new file mode 100644 index 0000000..a70855e --- /dev/null +++ b/src/kernel/vm_mapper.cpp @@ -0,0 +1,98 @@ +#include "objects/vm_area.h" +#include "vm_mapper.h" +#include "vm_space.h" + + +vm_mapper_single::vm_mapper_single(vm_area &area, vm_space &space) : + m_area(area), m_space(space) +{} + +vm_mapper_single::~vm_mapper_single() +{ + m_space.clear(m_area, 0, memory::page_count(m_area.size()), true); +} + +bool +vm_mapper_single::can_resize(size_t size) const +{ + return m_space.can_resize(m_area, size); +} + +void +vm_mapper_single::map(uintptr_t offset, size_t count, uintptr_t phys) +{ + m_space.page_in(m_area, offset, phys, count); +} + +void +vm_mapper_single::unmap(uintptr_t offset, size_t count) +{ + m_space.clear(m_area, offset, count, true); +} + + + +vm_mapper_multi::vm_mapper_multi(vm_area &area) : + m_area(area) +{ +} + +vm_mapper_multi::~vm_mapper_multi() +{ + if (!m_spaces.count()) + return; + + size_t count = memory::page_count(m_area.size()); + + for (int i = 1; i < m_spaces.count(); ++i) + m_spaces[i]->clear(m_area, 0, count); + + m_spaces[0]->clear(m_area, 0, count, true); +} + +bool +vm_mapper_multi::can_resize(size_t size) const +{ + for (auto &it : m_spaces) + if (!it->can_resize(m_area, size)) + return false; + return true; +} + +void +vm_mapper_multi::map(uintptr_t offset, size_t count, uintptr_t phys) +{ + for (auto &it : m_spaces) + it->page_in(m_area, offset, phys, count); +} + +void +vm_mapper_multi::unmap(uintptr_t offset, size_t count) +{ + for (auto &it : m_spaces) + it->clear(m_area, offset, count); +} + +void +vm_mapper_multi::add(vm_space *space) +{ + if (m_spaces.count()) { + vm_space *source = m_spaces[0]; + space->copy_from(*source, m_area); + } + + m_spaces.append(space); +} + +void +vm_mapper_multi::remove(vm_space *space) +{ + size_t count = memory::page_count(m_area.size()); + + for (int i = 0; i < m_spaces.count(); ++i) { + if (m_spaces[i] == space) { + m_spaces.remove_swap_at(i); + space->clear(m_area, 0, count, m_spaces.count() == 0); + } + } +} diff --git a/src/kernel/vm_mapper.h b/src/kernel/vm_mapper.h new file mode 100644 index 0000000..af4a908 --- /dev/null +++ b/src/kernel/vm_mapper.h @@ -0,0 +1,79 @@ +#pragma once +/// \file vm_mapper.h +/// VMA to address space mapping interface and implementing objects + +#include +#include "kutil/vector.h" + +class vm_area; +class vm_space; + +/// An interface to map vm_areas to one or more vm_spaces +class vm_mapper +{ +public: + virtual ~vm_mapper() {} + + /// Check whether the owning VMA can be resized to the given size. + /// \arg size The desired size + /// \returns True if resize is possible + virtual bool can_resize(size_t size) const = 0; + + /// Map the given physical pages into the owning VMA at the given offset + /// \arg offset Offset into the VMA of the requested virtual address + /// \arg count Number of contiguous physical pages to map + /// \arg phys The starting physical address of the pages + virtual void map(uintptr_t offset, size_t count, uintptr_t phys) = 0; + + /// Unmap the pages corresponding to the given offset from the owning VMA + /// \arg offset Offset into the VMA of the requested virtual address + /// \arg count Number of pages to unmap + virtual void unmap(uintptr_t offset, size_t count) = 0; + + /// Add the given address space to the list of spaces the owning VMA is + /// mapped to, if applicable. + virtual void add(vm_space *space) {} + + /// Remove the given address space from the list of spaces the owning VMA + /// is mapped to, if applicable. + virtual void remove(vm_space *space) {} +}; + + +/// A vm_mapper that maps a VMA to a single vm_space +class vm_mapper_single : + public vm_mapper +{ +public: + vm_mapper_single(vm_area &area, vm_space &space); + virtual ~vm_mapper_single(); + + virtual bool can_resize(size_t size) const override; + virtual void map(uintptr_t offset, size_t count, uintptr_t phys) override; + virtual void unmap(uintptr_t offset, size_t count) override; + +private: + vm_area &m_area; + vm_space &m_space; +}; + + +/// A vm_mapper that maps a VMA to multiple vm_spaces +class vm_mapper_multi : + public vm_mapper +{ +public: + vm_mapper_multi(vm_area &area); + virtual ~vm_mapper_multi(); + + virtual bool can_resize(size_t size) const override; + virtual void map(uintptr_t offset, size_t count, uintptr_t phys) override; + virtual void unmap(uintptr_t offset, size_t count) override; + virtual void add(vm_space *space) override; + virtual void remove(vm_space *space) override; + +private: + vm_area &m_area; + kutil::vector m_spaces; +}; + diff --git a/src/kernel/vm_space.cpp b/src/kernel/vm_space.cpp index d7c9e16..d17e1f0 100644 --- a/src/kernel/vm_space.cpp +++ b/src/kernel/vm_space.cpp @@ -1,10 +1,14 @@ #include "frame_allocator.h" +#include "kernel_memory.h" #include "log.h" #include "objects/process.h" #include "objects/thread.h" #include "objects/vm_area.h" #include "vm_space.h" +// The initial memory for the array of areas for the kernel space +static uint64_t kernel_areas[16]; + int vm_space::area::compare(const vm_space::area &o) const { @@ -20,7 +24,12 @@ vm_space::area::operator==(const vm_space::area &o) const } -vm_space::vm_space(page_table *p) : m_kernel(true), m_pml4(p) {} +// Kernel address space contsructor +vm_space::vm_space(page_table *p) : + m_kernel {true}, + m_pml4 {p}, + m_areas {reinterpret_cast(kernel_areas), 0, 8} +{} vm_space::vm_space() : m_kernel(false) @@ -36,7 +45,7 @@ vm_space::vm_space() : vm_space::~vm_space() { for (auto &a : m_areas) - a.area->remove_from(this); + a.area->mapper().remove(this); kassert(!is_kernel(), "Kernel vm_space destructor!"); @@ -61,6 +70,7 @@ vm_space::add(uintptr_t base, vm_area *area) { //TODO: check for collisions m_areas.sorted_insert({base, area}); + area->mapper().add(this); return true; } @@ -70,12 +80,36 @@ vm_space::remove(vm_area *area) for (auto &a : m_areas) { if (a.area == area) { m_areas.remove(a); + area->mapper().remove(this); return true; } } return false; } +bool +vm_space::can_resize(const vm_area &vma, size_t size) const +{ + uintptr_t base = 0; + unsigned n = m_areas.count(); + for (unsigned i = 0; i < n - 1; ++i) { + const area &prev = m_areas[i - 1]; + if (prev.area != &vma) + continue; + + base = prev.base; + const area &next = m_areas[i]; + if (prev.base + size > next.base) + return false; + } + + uintptr_t end = base + size; + uintptr_t space_end = is_kernel() ? + uint64_t(-1) : 0x7fffffffffff; + + return end <= space_end; +} + vm_area * vm_space::get(uintptr_t addr, uintptr_t *base) { @@ -89,11 +123,28 @@ vm_space::get(uintptr_t addr, uintptr_t *base) return nullptr; } -void -vm_space::copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t count) +bool +vm_space::find_vma(const vm_area &vma, uintptr_t &base) const { - page_table::iterator sit {from, source.m_pml4}; + for (auto &a : m_areas) { + if (a.area != &vma) continue; + base = a.base; + return true; + } + return false; +} + +void +vm_space::copy_from(const vm_space &source, const vm_area &vma) +{ + uintptr_t to = 0; + uintptr_t from = 0; + if (!find_vma(vma, from) || !source.find_vma(vma, from)) + return; + + size_t count = memory::page_count(vma.size()); page_table::iterator dit {to, m_pml4}; + page_table::iterator sit {from, source.m_pml4}; while (count--) { uint64_t &e = dit.entry(page_table::level::pt); @@ -105,23 +156,39 @@ vm_space::copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t } void -vm_space::page_in(uintptr_t virt, uintptr_t phys, size_t count) +vm_space::page_in(const vm_area &vma, uintptr_t offset, uintptr_t phys, size_t count) { + using memory::frame_size; + + uintptr_t base = 0; + if (!find_vma(vma, base)) + return; + + uintptr_t virt = base + offset; + page_table::flag flags = + page_table::flag::present | + (m_kernel ? page_table::flag::none : page_table::flag::user) | + ((vma.flags() && vm_flags::write) ? page_table::flag::write : page_table::flag::none); + page_table::iterator it {virt, m_pml4}; + for (size_t i = 0; i < count; ++i) { - uint64_t &e = it.entry(page_table::level::pt); - bool allowed = (e & page_table::flag::allowed); - e = (phys + i * memory::frame_size) | - (allowed ? page_table::flag::allowed : page_table::flag::none); + it.entry(page_table::level::pt) = + (phys + i * frame_size) | flags; ++it; } } void -vm_space::clear(uintptr_t addr, size_t count) +vm_space::clear(const vm_area &vma, uintptr_t offset, size_t count, bool free) { using memory::frame_size; + uintptr_t base = 0; + if (!find_vma(vma, base)) + return; + + uintptr_t addr = base + offset; uintptr_t free_start = 0; size_t free_count = 0; @@ -137,7 +204,7 @@ vm_space::clear(uintptr_t addr, size_t count) if (free_count && phys == free_start + (free_count * frame_size)) { ++free_count; } else { - if (free_count) + if (free && free_count) fa.free(free_start, free_count); free_start = phys; free_count = 1; @@ -149,7 +216,7 @@ vm_space::clear(uintptr_t addr, size_t count) ++it; } - if (free_count) + if (free && free_count) fa.free(free_start, free_count); } @@ -209,7 +276,7 @@ vm_space::handle_fault(uintptr_t addr, fault_type fault) uintptr_t base = 0; vm_area *area = get(addr, &base); - if (!area && !it.allowed()) + if ((!area || !area->allowed(page)) && !it.allowed()) return false; uintptr_t phys = 0; @@ -226,12 +293,13 @@ vm_space::handle_fault(uintptr_t addr, fault_type fault) ? page_table::flag::global : page_table::flag::user); - it.entry(page_table::level::pt) = phys | flags; if (area) { uintptr_t offset = page - base; area->commit(phys, offset, 1); + return true; } + it.entry(page_table::level::pt) = phys | flags; return true; } diff --git a/src/kernel/vm_space.h b/src/kernel/vm_space.h index 3b0e1a9..8583aef 100644 --- a/src/kernel/vm_space.h +++ b/src/kernel/vm_space.h @@ -5,8 +5,8 @@ #include #include "kutil/enum_bitfields.h" #include "kutil/vector.h" +#include "page_table.h" -struct page_table; class process; struct TCB; class vm_area; @@ -47,23 +47,19 @@ public: /// Get the kernel virtual memory space static vm_space & kernel_space(); - /// Copy a range of mappings from the given address space - /// \arg source The address space that already contains the mappings - /// \arg from The starting virtual address in the source address space - /// \arg to The starting virtual address in this address space - /// \arg count The number of page entries to copy - void copy_from(const vm_space &source, uintptr_t from, uintptr_t to, size_t count); - /// Map virtual addressses to the given physical pages - /// \arg virt The starting virutal address - /// \arg phys The starting physical address - /// \arg count The number of contiugous physical pages to map - void page_in(uintptr_t virt, uintptr_t phys, size_t count); + /// \arg area The VMA this mapping applies to + /// \arg offset Offset of the starting virutal address from the VMA base + /// \arg phys The starting physical address + /// \arg count The number of contiugous physical pages to map + void page_in(const vm_area &area, uintptr_t offset, uintptr_t phys, size_t count); /// Clear mappings from the given region - /// \arg start The starting virutal address to clear + /// \arg area The VMA these mappings applies to + /// \arg offset Offset of the starting virutal address from the VMA base /// \arg count The number of pages worth of mappings to clear - void clear(uintptr_t start, size_t count); + /// \arg free If true, free the pages back to the system + void clear(const vm_area &vma, uintptr_t start, size_t count, bool free = false); /// Mark whether allocation is allowed or not in a range of /// virtual memory. @@ -106,6 +102,18 @@ public: static size_t copy(vm_space &source, vm_space &dest, void *from, void *to, size_t length); private: + friend class vm_mapper_single; + friend class vm_mapper_multi; + + /// Find a given VMA in this address space + bool find_vma(const vm_area &vma, uintptr_t &base) const; + + /// Check if a VMA can be resized + bool can_resize(const vm_area &vma, size_t size) const; + + /// Copy a range of mappings from the given address space + void copy_from(const vm_space &source, const vm_area &vma); + bool m_kernel; page_table *m_pml4; diff --git a/src/libraries/kutil/include/kutil/map.h b/src/libraries/kutil/include/kutil/map.h index 42cf143..9536e61 100644 --- a/src/libraries/kutil/include/kutil/map.h +++ b/src/libraries/kutil/include/kutil/map.h @@ -105,16 +105,19 @@ public: } iterator begin() { + if (!m_count) return iterator {0}; iterator it {m_nodes - 1, m_nodes + m_capacity}; return ++it; } const iterator begin() const { + if (!m_count) return iterator {0}; iterator it {m_nodes - 1, m_nodes + m_capacity}; return ++it; } const iterator end() const { + if (!m_count) return iterator {0}; return iterator(m_nodes + m_capacity); } diff --git a/src/libraries/kutil/include/kutil/vector.h b/src/libraries/kutil/include/kutil/vector.h index 0ce5450..d4bebbe 100644 --- a/src/libraries/kutil/include/kutil/vector.h +++ b/src/libraries/kutil/include/kutil/vector.h @@ -45,7 +45,7 @@ public: } /// Move constructor. Takes ownership of the other's array. - vector(vector&& other) : + vector(vector &&other) : m_size(other.m_size), m_capacity(other.m_capacity), m_elements(other.m_elements) @@ -55,6 +55,15 @@ public: other.m_elements = nullptr; } + /// Static array constructor. Starts the vector off with the given + /// static storage. + vector(T *data, size_t size, size_t capacity) : + m_size(size), + m_capacity(capacity), + m_elements(&data[0]) + { + } + /// Destructor. Destroys any remaining items in the array. ~vector() { diff --git a/src/libraries/libc/arch/x86_64/syscalls.s b/src/libraries/libc/arch/x86_64/syscalls.s index cf90182..4a9e42e 100644 --- a/src/libraries/libc/arch/x86_64/syscalls.s +++ b/src/libraries/libc/arch/x86_64/syscalls.s @@ -4,7 +4,11 @@ push rbp mov rbp, rsp - ; address of args should already be in rdi, etc + ; args should already be in rdi, etc, but rcx will + ; get stomped, so shift args out one spot from rcx + mov r9, r8 + mov r8, rcx + mov rax, %2 syscall ; result is now already in rax, so just return @@ -17,5 +21,7 @@ %define SYSCALL(n, name, a) SYSCALL name, n %define SYSCALL(n, name, a, b) SYSCALL name, n %define SYSCALL(n, name, a, b, c) SYSCALL name, n +%define SYSCALL(n, name, a, b, c, d) SYSCALL name, n +%define SYSCALL(n, name, a, b, c, d, e) SYSCALL name, n %include "syscalls.inc"