From 5940e912b39e4ad5c0883277bd3a687636050c49 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Tue, 5 Aug 2025 00:07:17 +0300 Subject: [PATCH] Kernel/LibC: Implement simple futex --- kernel/include/kernel/Process.h | 9 ++ kernel/kernel/Process.cpp | 91 +++++++++++++++++++ userspace/libraries/LibC/CMakeLists.txt | 1 + userspace/libraries/LibC/include/sys/futex.h | 45 +++++++++ .../libraries/LibC/include/sys/syscall.h | 1 + userspace/libraries/LibC/sys/futex.cpp | 12 +++ 6 files changed, 159 insertions(+) create mode 100644 userspace/libraries/LibC/include/sys/futex.h create mode 100644 userspace/libraries/LibC/sys/futex.cpp diff --git a/kernel/include/kernel/Process.h b/kernel/include/kernel/Process.h index 566619b8..d54bd9b1 100644 --- a/kernel/include/kernel/Process.h +++ b/kernel/include/kernel/Process.h @@ -189,6 +189,7 @@ namespace Kernel BAN::ErrorOr sys_sigpending(sigset_t* set); BAN::ErrorOr sys_sigprocmask(int how, const sigset_t* set, sigset_t* oset); + BAN::ErrorOr sys_futex(int op, const uint32_t* addr, uint32_t val, const timespec* abstime); BAN::ErrorOr sys_yield(); BAN::ErrorOr sys_set_tls(void*); BAN::ErrorOr sys_get_tls(); @@ -337,6 +338,14 @@ namespace Kernel BAN::UniqPtr m_page_table; BAN::RefPtr m_controlling_terminal; + struct futex_t + { + ThreadBlocker blocker; + uint32_t waiters { 0 }; + uint32_t to_wakeup { 0 }; + }; + BAN::HashMap> m_futexes; + friend class Thread; }; diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index 4e3bd20b..fe217af4 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -2557,6 +2558,96 @@ namespace Kernel return 0; } + BAN::ErrorOr Process::sys_futex(int op, const uint32_t* addr, uint32_t val, const timespec* abstime) + { + const vaddr_t vaddr = reinterpret_cast(addr); + if (vaddr % 4) + return BAN::Error::from_errno(EINVAL); + + const bool is_private = (op & FUTEX_PRIVATE); + const bool is_realtime = (op & FUTEX_REALTIME); + op &= ~(FUTEX_PRIVATE | FUTEX_REALTIME); + + if (!is_private) + { + dwarnln("TODO: shared futex"); + return BAN::Error::from_errno(ENOTSUP); + } + + LockGuard _(m_process_lock); + + auto* buffer_region = TRY(validate_and_pin_pointer_access(addr, sizeof(uint32_t), false)); + BAN::ScopeGuard pin_guard([&] { if (buffer_region) buffer_region->unpin(); }); + + const paddr_t paddr = m_page_table->physical_address_of(vaddr & PAGE_ADDR_MASK) | (vaddr & ~PAGE_ADDR_MASK); + ASSERT(paddr != 0); + + switch (op) + { + case FUTEX_WAIT: + { + if (BAN::atomic_load(*addr) != val) + return BAN::Error::from_errno(EAGAIN); + + const uint64_t wake_time_ns = + TRY([abstime, is_realtime, this]() -> BAN::ErrorOr + { + if (abstime == nullptr) + return BAN::numeric_limits::max(); + TRY(validate_pointer_access(abstime, sizeof(*abstime), false)); + const uint64_t abs_ns = abstime->tv_sec * 1'000'000'000 + abstime->tv_nsec; + if (!is_realtime) + return abs_ns; + const auto realtime = SystemTimer::get().real_time(); + const uint64_t real_ns = realtime.tv_sec * 1'000'000'000 + realtime.tv_nsec; + if (abs_ns <= real_ns) + return BAN::Error::from_errno(ETIMEDOUT); + return SystemTimer::get().ns_since_boot() + (abs_ns - real_ns); + }()); + + auto it = m_futexes.find(paddr); + if (it == m_futexes.end()) + it = TRY(m_futexes.emplace(paddr, TRY(BAN::UniqPtr::create()))); + futex_t* const futex = it->value.ptr(); + + futex->waiters++; + BAN::ScopeGuard _([futex, paddr, this] { + if (--futex->waiters == 0) + m_futexes.remove(paddr); + }); + + for (;;) + { + TRY(Thread::current().block_or_eintr_or_waketime_ns(futex->blocker, wake_time_ns, true, &m_process_lock)); + if (BAN::atomic_load(*addr) == val || futex->to_wakeup == 0) + continue; + futex->to_wakeup--; + return 0; + } + } + case FUTEX_WAKE: + { + auto it = m_futexes.find(paddr); + if (it == m_futexes.end()) + return 0; + futex_t* const futex = it->value.ptr(); + + if (BAN::Math::will_addition_overflow(futex->to_wakeup, val)) + futex->to_wakeup = BAN::numeric_limits::max(); + else + futex->to_wakeup += val; + + futex->to_wakeup = BAN::Math::min(futex->to_wakeup, futex->waiters); + futex->blocker.unblock(); + return 0; + } + default: + return BAN::Error::from_errno(ENOSYS); + } + + ASSERT_NOT_REACHED(); + } + BAN::ErrorOr Process::sys_yield() { Processor::yield(); diff --git a/userspace/libraries/LibC/CMakeLists.txt b/userspace/libraries/LibC/CMakeLists.txt index 5b45785f..9763e5b1 100644 --- a/userspace/libraries/LibC/CMakeLists.txt +++ b/userspace/libraries/LibC/CMakeLists.txt @@ -36,6 +36,7 @@ set(LIBC_SOURCES sys/banan-os.cpp sys/epoll.cpp sys/file.cpp + sys/futex.cpp sys/ioctl.cpp sys/mman.cpp sys/resource.cpp diff --git a/userspace/libraries/LibC/include/sys/futex.h b/userspace/libraries/LibC/include/sys/futex.h new file mode 100644 index 00000000..52814987 --- /dev/null +++ b/userspace/libraries/LibC/include/sys/futex.h @@ -0,0 +1,45 @@ +#ifndef _SYS_FUTEX_H +#define _SYS_FUTEX_H 1 + +#include + +__BEGIN_DECLS + +#include +#include + +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_PRIVATE 0x10 +#define FUTEX_REALTIME 0x20 + +#define FUTEX_WAIT_PRIVATE (FUTEX_WAIT | FUTEX_PRIVATE) +#define FUTEX_WAKE_PRIVATE (FUTEX_WAKE | FUTEX_PRIVATE) + +// op is one of FUTEX_WAIT or FUTEX_WAKE optionally or'ed with FUTEX_PRIVATE and/or FUTEX_REALTIME +// +// FUTEX_WAIT +// put current thread to sleep until *addr != value or until timeout occurs +// timeout is specified as a absolute time or NULL for indefinite wait +// +// FUTEX_WAKE +// signals waiting futexes to recheck *addr. at most value threads are woken up +// +// FUTEX_PRIVATE +// limit futex wait/wake events to the current process +// +// FUTEX_REALTIME +// abstime corresponds to CLOCK_REALTIME instead of the default CLOCK_MONOTONIC +// +// ERRORS +// ETIMEDOUT timeout occured +// EINVAL addr is not aligned on 4 byte boundary +// ENOSYS op contains unrecognized value +// EINTR function was interrupted +// ENOMEM not enough memory to allocate futex object +// EAGAIN *addr != value before thread was put to sleep +int futex(int op, const uint32_t* addr, uint32_t value, const struct timespec* abstime); + +__END_DECLS + +#endif diff --git a/userspace/libraries/LibC/include/sys/syscall.h b/userspace/libraries/LibC/include/sys/syscall.h index 8d5b0746..a909adc0 100644 --- a/userspace/libraries/LibC/include/sys/syscall.h +++ b/userspace/libraries/LibC/include/sys/syscall.h @@ -109,6 +109,7 @@ __BEGIN_DECLS O(SYS_EPOLL_PWAIT2, epoll_pwait2) \ O(SYS_FLOCK, flock) \ O(SYS_GET_NPROCESSOR, get_nprocessor) \ + O(SYS_FUTEX, futex) \ enum Syscall { diff --git a/userspace/libraries/LibC/sys/futex.cpp b/userspace/libraries/LibC/sys/futex.cpp new file mode 100644 index 00000000..593f8e27 --- /dev/null +++ b/userspace/libraries/LibC/sys/futex.cpp @@ -0,0 +1,12 @@ +#include +#include +#include +#include + +int futex(int op, const uint32_t* addr, uint32_t value, const struct timespec* abstime) +{ + errno = 0; + while (syscall(SYS_FUTEX, op, addr, value, abstime) == -1 && errno == EINTR) + errno = 0; + return errno; +}