Kernel: Optimize futexes

Add support for processor local futexes. These work the exact same way
as global ones, but only lock a process specific lock and use a process
specific hash map.

Also reduce the time futex lock is held. There was no need to hold the
global lock while validating addresses in the process' address space.
This commit is contained in:
Bananymous 2026-01-09 22:09:52 +02:00
parent 5c9151d3e9
commit 2961a49dc7
4 changed files with 66 additions and 40 deletions

View File

@ -346,6 +346,19 @@ namespace Kernel
vaddr_t m_shared_page_vaddr { 0 }; vaddr_t m_shared_page_vaddr { 0 };
struct futex_t
{
ThreadBlocker blocker;
uint32_t waiters { 0 };
uint32_t to_wakeup { 0 };
};
static BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> s_futexes;
static Mutex s_futex_lock;
BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> m_futexes;
Mutex m_futex_lock;
BAN::Vector<Thread*> m_threads; BAN::Vector<Thread*> m_threads;
struct pthread_info_t struct pthread_info_t

View File

@ -38,14 +38,8 @@ namespace Kernel
static BAN::Vector<Process*> s_processes; static BAN::Vector<Process*> s_processes;
static RecursiveSpinLock s_process_lock; static RecursiveSpinLock s_process_lock;
struct futex_t BAN::HashMap<paddr_t, BAN::UniqPtr<Process::futex_t>> Process::s_futexes;
{ Mutex Process::s_futex_lock;
ThreadBlocker blocker;
uint32_t waiters { 0 };
uint32_t to_wakeup { 0 };
};
static BAN::HashMap<paddr_t, BAN::UniqPtr<futex_t>> s_futexes;
static Mutex s_futex_lock;
static void for_each_process(const BAN::Function<BAN::Iteration(Process&)>& callback) static void for_each_process(const BAN::Function<BAN::Iteration(Process&)>& callback)
{ {
@ -3107,12 +3101,9 @@ namespace Kernel
return BAN::Error::from_errno(EINVAL); return BAN::Error::from_errno(EINVAL);
const bool is_realtime = (op & FUTEX_REALTIME); const bool is_realtime = (op & FUTEX_REALTIME);
const bool is_private = (op & FUTEX_PRIVATE);
op &= ~(FUTEX_PRIVATE | FUTEX_REALTIME); op &= ~(FUTEX_PRIVATE | FUTEX_REALTIME);
// TODO: possibly optimize private futexes?
LockGuard _(s_futex_lock);
auto* buffer_region = TRY(validate_and_pin_pointer_access(addr, sizeof(uint32_t), false)); auto* buffer_region = TRY(validate_and_pin_pointer_access(addr, sizeof(uint32_t), false));
BAN::ScopeGuard pin_guard([&] { if (buffer_region) buffer_region->unpin(); }); BAN::ScopeGuard pin_guard([&] { if (buffer_region) buffer_region->unpin(); });
@ -3122,17 +3113,26 @@ namespace Kernel
switch (op) switch (op)
{ {
case FUTEX_WAIT: case FUTEX_WAIT:
{ break;
if (BAN::atomic_load(*addr) != val) case FUTEX_WAKE:
return BAN::Error::from_errno(EAGAIN); abstime = nullptr;
break;
default:
return BAN::Error::from_errno(ENOSYS);
}
const uint64_t wake_time_ns = const uint64_t wake_time_ns =
TRY([abstime, is_realtime, this]() -> BAN::ErrorOr<uint64_t> TRY([abstime, is_realtime, this]() -> BAN::ErrorOr<uint64_t>
{ {
if (abstime == nullptr) if (abstime == nullptr)
return BAN::numeric_limits<uint64_t>::max(); return BAN::numeric_limits<uint64_t>::max();
const uint64_t abs_ns =
TRY([abstime, this]() -> BAN::ErrorOr<uint64_t>
{
LockGuard _(m_process_lock);
TRY(validate_pointer_access(abstime, sizeof(*abstime), false)); TRY(validate_pointer_access(abstime, sizeof(*abstime), false));
const uint64_t abs_ns = abstime->tv_sec * 1'000'000'000 + abstime->tv_nsec; return abstime->tv_sec * 1'000'000'000 + abstime->tv_nsec;
}());
if (!is_realtime) if (!is_realtime)
return abs_ns; return abs_ns;
const auto realtime = SystemTimer::get().real_time(); const auto realtime = SystemTimer::get().real_time();
@ -3142,20 +3142,32 @@ namespace Kernel
return SystemTimer::get().ns_since_boot() + (abs_ns - real_ns); return SystemTimer::get().ns_since_boot() + (abs_ns - real_ns);
}()); }());
auto it = s_futexes.find(paddr); auto& futex_lock = is_private ? m_futex_lock : s_futex_lock;
if (it == s_futexes.end()) auto& futexes = is_private ? m_futexes : s_futexes;
it = TRY(s_futexes.emplace(paddr, TRY(BAN::UniqPtr<futex_t>::create())));
LockGuard _(futex_lock);
switch (op)
{
case FUTEX_WAIT:
{
if (BAN::atomic_load(*addr) != val)
return BAN::Error::from_errno(EAGAIN);
auto it = futexes.find(paddr);
if (it == futexes.end())
it = TRY(futexes.emplace(paddr, TRY(BAN::UniqPtr<futex_t>::create())));
futex_t* const futex = it->value.ptr(); futex_t* const futex = it->value.ptr();
futex->waiters++; futex->waiters++;
BAN::ScopeGuard _([futex, paddr] { BAN::ScopeGuard _([&futexes, futex, paddr] {
if (--futex->waiters == 0) if (--futex->waiters == 0)
s_futexes.remove(paddr); futexes.remove(paddr);
}); });
for (;;) for (;;)
{ {
TRY(Thread::current().block_or_eintr_or_waketime_ns(futex->blocker, wake_time_ns, true, &s_futex_lock)); TRY(Thread::current().block_or_eintr_or_waketime_ns(futex->blocker, wake_time_ns, true, &futex_lock));
if (BAN::atomic_load(*addr) == val || futex->to_wakeup == 0) if (BAN::atomic_load(*addr) == val || futex->to_wakeup == 0)
continue; continue;
futex->to_wakeup--; futex->to_wakeup--;
@ -3164,8 +3176,8 @@ namespace Kernel
} }
case FUTEX_WAKE: case FUTEX_WAKE:
{ {
auto it = s_futexes.find(paddr); auto it = futexes.find(paddr);
if (it == s_futexes.end()) if (it == futexes.end())
return 0; return 0;
futex_t* const futex = it->value.ptr(); futex_t* const futex = it->value.ptr();
@ -3178,8 +3190,6 @@ namespace Kernel
futex->blocker.unblock(); futex->blocker.unblock();
return 0; return 0;
} }
default:
return BAN::Error::from_errno(ENOSYS);
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();

View File

@ -884,9 +884,11 @@ int pthread_mutex_unlock(pthread_mutex_t* mutex)
mutex->lock_depth--; mutex->lock_depth--;
if (mutex->lock_depth == 0) if (mutex->lock_depth == 0)
{ {
const int op = FUTEX_WAKE | (mutex->attr.shared ? 0 : FUTEX_PRIVATE);
BAN::atomic_store(mutex->futex, 0, BAN::memory_order_release); BAN::atomic_store(mutex->futex, 0, BAN::memory_order_release);
if (BAN::atomic_load(mutex->waiters)) if (BAN::atomic_load(mutex->waiters))
futex(FUTEX_WAKE, &mutex->futex, 1, nullptr); futex(op, &mutex->futex, 1, nullptr);
} }
return 0; return 0;

View File

@ -28,8 +28,9 @@ int sem_getvalue(sem_t* __restrict sem, int* __restrict sval)
int sem_post(sem_t* sem) int sem_post(sem_t* sem)
{ {
const auto old = BAN::atomic_fetch_add(sem->value, 1); const auto old = BAN::atomic_fetch_add(sem->value, 1);
const int op = FUTEX_WAKE | (sem->shared ? 0 : FUTEX_PRIVATE);
if (old == 0) if (old == 0)
futex(FUTEX_WAKE, &sem->value, 1, nullptr); futex(op, &sem->value, 1, nullptr);
return 0; return 0;
} }