Kernel/LibC: Implement simple futex

This commit is contained in:
Bananymous 2025-08-05 00:07:17 +03:00
parent 658a001d91
commit 5940e912b3
6 changed files with 159 additions and 0 deletions

View File

@ -189,6 +189,7 @@ namespace Kernel
BAN::ErrorOr<long> sys_sigpending(sigset_t* set);
BAN::ErrorOr<long> sys_sigprocmask(int how, const sigset_t* set, sigset_t* oset);
BAN::ErrorOr<long> sys_futex(int op, const uint32_t* addr, uint32_t val, const timespec* abstime);
BAN::ErrorOr<long> sys_yield();
BAN::ErrorOr<long> sys_set_tls(void*);
BAN::ErrorOr<long> sys_get_tls();
@ -337,6 +338,14 @@ namespace Kernel
BAN::UniqPtr<PageTable> m_page_table;
BAN::RefPtr<TTY> m_controlling_terminal;
struct futex_t
{
ThreadBlocker blocker;
uint32_t waiters { 0 };
uint32_t to_wakeup { 0 };
};
BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> m_futexes;
friend class Thread;
};

View File

@ -26,6 +26,7 @@
#include <pthread.h>
#include <stdio.h>
#include <sys/banan-os.h>
#include <sys/futex.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
@ -2557,6 +2558,96 @@ namespace Kernel
return 0;
}
BAN::ErrorOr<long> Process::sys_futex(int op, const uint32_t* addr, uint32_t val, const timespec* abstime)
{
const vaddr_t vaddr = reinterpret_cast<vaddr_t>(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<uint64_t>
{
if (abstime == nullptr)
return BAN::numeric_limits<uint64_t>::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<futex_t>::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<uint32_t>::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<long> Process::sys_yield()
{
Processor::yield();

View File

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

View File

@ -0,0 +1,45 @@
#ifndef _SYS_FUTEX_H
#define _SYS_FUTEX_H 1
#include <sys/cdefs.h>
__BEGIN_DECLS
#include <stdint.h>
#include <time.h>
#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

View File

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

View File

@ -0,0 +1,12 @@
#include <errno.h>
#include <sys/futex.h>
#include <sys/syscall.h>
#include <unistd.h>
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;
}