From ed95574c243196a1a25ecacea592b8d9d9f058f5 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Sun, 26 Feb 2023 11:32:30 -0800 Subject: [PATCH] [kernel] Add (wip) futex syscalls Add the syscalls j6_futex_wait and j6_futex_wake. Currently marking this as WIP as they need more testing. Added to support futexes: - vm_area and vm_space support for looking up physical address for a virtual address - libj6 mutex implementation using futex system calls --- definitions/syscalls.def | 13 ++++++ src/kernel/kernel.module | 1 + src/kernel/objects/vm_area.cpp | 24 ++++++---- src/kernel/objects/vm_area.h | 14 +++--- src/kernel/syscalls/futex.cpp | 84 ++++++++++++++++++++++++++++++++++ src/kernel/vm_space.cpp | 14 ++++++ src/kernel/vm_space.h | 6 +++ src/libraries/j6/j6.module | 2 + src/libraries/j6/j6/mutex.hh | 42 +++++++++++++++++ src/libraries/j6/mutex.cpp | 36 +++++++++++++++ 10 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 src/kernel/syscalls/futex.cpp create mode 100644 src/libraries/j6/j6/mutex.hh create mode 100644 src/libraries/j6/mutex.cpp diff --git a/definitions/syscalls.def b/definitions/syscalls.def index fcad59e..05f72b1 100644 --- a/definitions/syscalls.def +++ b/definitions/syscalls.def @@ -43,6 +43,19 @@ interface syscalls [syscall] { param mask uint32 # The capability bitmask } + # Block waiting on a futex + function futex_wait [static] { + param address uint32* # Address of the futex value + param current uint32 # Current value of the futex + param timeout uint64 # Wait timeout in nanoseconds + } + + # Wake threads waiting on a futex + function futex_wake [static] { + param address uint32* # Address of the futex value + param count uint64 # Number of threads to wake, or 0 for all + } + # Testing mode only: Have the kernel finish and exit QEMU with the given exit code function test_finish [test] { param exit_code uint32 diff --git a/src/kernel/kernel.module b/src/kernel/kernel.module index 3449fca..0506277 100644 --- a/src/kernel/kernel.module +++ b/src/kernel/kernel.module @@ -57,6 +57,7 @@ kernel = module("kernel", "syscalls/mailbox.cpp", "syscalls/object.cpp", "syscalls/process.cpp", + "syscalls/futex.cpp", "syscalls/system.cpp", "syscalls/thread.cpp", "syscalls/vm_area.cpp", diff --git a/src/kernel/objects/vm_area.cpp b/src/kernel/objects/vm_area.cpp index 1203ebf..0bd01f9 100644 --- a/src/kernel/objects/vm_area.cpp +++ b/src/kernel/objects/vm_area.cpp @@ -86,7 +86,7 @@ vm_area_fixed::resize(size_t size) } bool -vm_area_fixed::get_page(uintptr_t offset, uintptr_t &phys) +vm_area_fixed::get_page(uintptr_t offset, uintptr_t &phys, bool alloc) { if (offset > m_size) return false; @@ -106,11 +106,16 @@ vm_area_untracked::~vm_area_untracked() } bool -vm_area_untracked::get_page(uintptr_t offset, uintptr_t &phys) +vm_area_untracked::get_page(uintptr_t offset, uintptr_t &phys, bool alloc) { if (offset > m_size) return false; + if (!alloc) { + phys = 0; + return true; + } + return frame_allocator::get().allocate(1, &phys); } @@ -136,9 +141,12 @@ vm_area_open::~vm_area_open() } bool -vm_area_open::get_page(uintptr_t offset, uintptr_t &phys) +vm_area_open::get_page(uintptr_t offset, uintptr_t &phys, bool alloc) { - return page_tree::find_or_add(m_mapped, offset, phys); + if (alloc) + return page_tree::find_or_add(m_mapped, offset, phys); + else + return page_tree::find(m_mapped, offset, phys); } void @@ -171,7 +179,7 @@ vm_area_guarded::return_section(uintptr_t addr) } bool -vm_area_guarded::get_page(uintptr_t offset, uintptr_t &phys) +vm_area_guarded::get_page(uintptr_t offset, uintptr_t &phys, bool alloc) { if (offset >= m_stacks.end()) return false; @@ -181,7 +189,7 @@ vm_area_guarded::get_page(uintptr_t offset, uintptr_t &phys) if ((offset >> 12) % m_pages == 0) return false; - return vm_area_open::get_page(offset, phys); + return vm_area_open::get_page(offset, phys, alloc); } vm_area_ring::vm_area_ring(size_t size, vm_flags flags) : @@ -193,11 +201,11 @@ vm_area_ring::vm_area_ring(size_t size, vm_flags flags) : vm_area_ring::~vm_area_ring() {} bool -vm_area_ring::get_page(uintptr_t offset, uintptr_t &phys) +vm_area_ring::get_page(uintptr_t offset, uintptr_t &phys, bool alloc) { if (offset > m_bufsize) offset -= m_bufsize; - return vm_area_open::get_page(offset, phys); + return vm_area_open::get_page(offset, phys, alloc); } } // namespace obj diff --git a/src/kernel/objects/vm_area.h b/src/kernel/objects/vm_area.h index 8d28b32..517315b 100644 --- a/src/kernel/objects/vm_area.h +++ b/src/kernel/objects/vm_area.h @@ -71,8 +71,10 @@ public: /// Get the physical page for the given offset /// \arg offset The offset into the VMA /// \arg phys [out] Receives the physical page address, if any + /// \arg alloc If true, and this is a valid address with no frame, + /// allocate one if applicable /// \returns True if there should be a page at the given offset - virtual bool get_page(uintptr_t offset, uintptr_t &phys) = 0; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) = 0; protected: /// A VMA is not deleted until both no handles remain AND it's not @@ -106,7 +108,7 @@ public: virtual ~vm_area_fixed(); virtual size_t resize(size_t size) override; - virtual bool get_page(uintptr_t offset, uintptr_t &phys) override; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) override; private: uintptr_t m_start; @@ -124,7 +126,7 @@ public: vm_area_open(size_t size, vm_flags flags); virtual ~vm_area_open(); - virtual bool get_page(uintptr_t offset, uintptr_t &phys) override; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) override; /// Tell this VMA about an existing mapping that did not originate /// from get_page. @@ -147,7 +149,7 @@ public: virtual ~vm_area_untracked(); virtual bool add_to(vm_space *space) override; - virtual bool get_page(uintptr_t offset, uintptr_t &phys) override; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) override; }; @@ -175,7 +177,7 @@ public: /// Return a section address to the available pool void return_section(uintptr_t addr); - virtual bool get_page(uintptr_t offset, uintptr_t &phys) override; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) override; private: size_t m_pages; @@ -196,7 +198,7 @@ public: vm_area_ring(size_t size, vm_flags flags); virtual ~vm_area_ring(); - virtual bool get_page(uintptr_t offset, uintptr_t &phys) override; + virtual bool get_page(uintptr_t offset, uintptr_t &phys, bool alloc = true) override; private: size_t m_bufsize; diff --git a/src/kernel/syscalls/futex.cpp b/src/kernel/syscalls/futex.cpp new file mode 100644 index 0000000..880cdfc --- /dev/null +++ b/src/kernel/syscalls/futex.cpp @@ -0,0 +1,84 @@ +#include +#include +#include + +#include "clock.h" +#include "objects/process.h" +#include "objects/thread.h" +#include "vm_space.h" + +using namespace obj; + +namespace syscalls { + +struct futex +{ + uintptr_t address; + wait_queue queue; + + futex() = default; + futex(futex &&other) : + address {other.address}, + queue {std::move(other.queue)} {} + + futex & operator=(futex &&other) { + address = other.address; + queue = std::move(other.queue); + return *this; + } +}; + +inline uint64_t & get_map_key(futex &f) { return f.address; } + +util::node_map g_futexes; +util::spinlock g_futexes_lock; + +j6_status_t +futex_wait(const uint32_t *value, uint32_t expected, uint64_t timeout) +{ + if (*value != expected) + return j6_status_would_block; + + uintptr_t address = reinterpret_cast(value); + vm_space &space = process::current().space(); + uintptr_t phys = space.find_physical(address); + + util::scoped_lock lock {g_futexes_lock}; + + futex &f = g_futexes[phys]; + thread& t = thread::current(); + + if (timeout) { + timeout += clock::get().value(); + t.set_wake_timeout(timeout); + } + + lock.release(); + f.queue.wait(); + return j6_status_ok; +} + +j6_status_t +futex_wake(const uint32_t *value, size_t count) +{ + uintptr_t address = reinterpret_cast(value); + vm_space &space = process::current().space(); + uintptr_t phys = space.find_physical(address); + + util::scoped_lock lock {g_futexes_lock}; + + futex *f = g_futexes.find(phys); + if (f) { + for (unsigned i = 0; i < count; ++i) { + obj::thread *t = f->queue.pop_next(); + t->wake(); + } + + if (f->queue.empty()) + g_futexes.erase(phys); + } + + return j6_status_ok; +} + +} // namespace syscalls diff --git a/src/kernel/vm_space.cpp b/src/kernel/vm_space.cpp index 60d8303..5d2ed9a 100644 --- a/src/kernel/vm_space.cpp +++ b/src/kernel/vm_space.cpp @@ -367,3 +367,17 @@ vm_space::copy(vm_space &source, vm_space &dest, const void *from, void *to, siz return length; } + +uintptr_t +vm_space::find_physical(uintptr_t virt) +{ + uintptr_t base = 0; + obj::vm_area *area = get(virt, &base); + if (!area) + return 0; + + uintptr_t phys = 0; + area->get_page(virt - base, phys, false); + return phys; +} + diff --git a/src/kernel/vm_space.h b/src/kernel/vm_space.h index ef330ad..a83319f 100644 --- a/src/kernel/vm_space.h +++ b/src/kernel/vm_space.h @@ -117,6 +117,12 @@ public: /// \returnd The number of bytes copied static size_t copy(vm_space &source, vm_space &dest, const void *from, void *to, size_t length); + /// Get the physical address of a virtual address from this space + /// \arg vrit The virtual address + /// \returns The physical address mapped to that virtual address, + /// or 0 for none. + uintptr_t find_physical(uintptr_t virt); + private: friend class obj::vm_area; diff --git a/src/libraries/j6/j6.module b/src/libraries/j6/j6.module index d693649..b878c00 100644 --- a/src/libraries/j6/j6.module +++ b/src/libraries/j6/j6.module @@ -6,6 +6,7 @@ j6 = module("j6", sources = [ "init.cpp", "init.s", + "mutex.cpp", "protocol_ids.cpp", "syscalls.s.cog", "sysconf.cpp.cog", @@ -17,6 +18,7 @@ j6 = module("j6", "j6/errors.h", "j6/flags.h", "j6/init.h", + "j6/mutex.hh", "j6/protocols.h", "j6/protocols/service_locator.h", "j6/syscalls.h.cog", diff --git a/src/libraries/j6/j6/mutex.hh b/src/libraries/j6/j6/mutex.hh new file mode 100644 index 0000000..a1d5b06 --- /dev/null +++ b/src/libraries/j6/j6/mutex.hh @@ -0,0 +1,42 @@ +#pragma once +/// \file mutex.hh +/// High level mutex interface + +// The kernel depends on libj6 for some shared code, +// but should not include the user-specific code. +#ifndef __j6kernel + +#include + +namespace j6 { + +class mutex +{ +public: + mutex() : m_state(0) {} + + void lock(); + void unlock(); + +private: + uint32_t m_state; +}; + +class scoped_lock +{ +public: + inline scoped_lock(mutex &m) : m_mutex {m} { + m_mutex.lock(); + } + + inline ~scoped_lock() { + m_mutex.unlock(); + } + +private: + mutex &m_mutex; +}; + +} // namespace j6 + +#endif // __j6kernel diff --git a/src/libraries/j6/mutex.cpp b/src/libraries/j6/mutex.cpp new file mode 100644 index 0000000..af89861 --- /dev/null +++ b/src/libraries/j6/mutex.cpp @@ -0,0 +1,36 @@ +// The kernel depends on libj6 for some shared code, +// but should not include the user-specific code. +#ifndef __j6kernel + +#include +#include + +namespace j6 { + +void +mutex::lock() +{ + uint32_t lock = 0; + if ((lock = __sync_val_compare_and_swap(&m_state, 0, 1)) != 0) { + if (lock != 2) + lock = __atomic_exchange_n(&m_state, 2, __ATOMIC_ACQ_REL); + while (lock) { + j6_futex_wait(&m_state, 2, 0); + lock = __atomic_exchange_n(&m_state, 2, __ATOMIC_ACQ_REL); + } + } +} + +void +mutex::unlock() +{ + if (__atomic_fetch_sub(&m_state, 1, __ATOMIC_ACQ_REL) != 1) { + __atomic_store_n(&m_state, 0, __ATOMIC_RELEASE); + j6_futex_wake(&m_state, 1); + } +} + + +} // namespace j6 + +#endif // __j6kernel