Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions include/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,8 @@
ID(beginthreadex) \
ID(endthread) \
ID(nsleep) \
ID(phMutexCreate) \
ID(phMutexLock) \
ID(mutexTry) \
ID(mutexUnlock) \
ID(phCondCreate) \
ID(phCondWait) \
ID(condSignal) \
ID(condBroadcast) \
ID(resourceDestroy) \
ID(interrupt) \
ID(phResourceDestroy) \
ID(phInterrupt) \
ID(portCreate) \
ID(portDestroy) \
ID(portRegister) \
Expand Down Expand Up @@ -124,5 +116,8 @@
ID(sys_mprotect) \
\
ID(sys_statvfs) \
ID(sys_uname)
ID(sys_uname) \
\
ID(phFutexWait) \
ID(phFutexWakeup)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: You remove syscalls, so it doesn't matter if you add new ones in a backward compatible manner (as last) or not, because it is already a breaking change which requires a rebuild. phFutexWait etc. can be placed by similar primitive syscalls, e.g. phInterrupt.

/* clang-format on */
2 changes: 1 addition & 1 deletion proc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Author: Pawel Pisarczyk
#

OBJS += $(addprefix $(PREFIX_O)proc/, proc.o threads.o process.o name.o resource.o mutex.o cond.o userintr.o ports.o)
OBJS += $(addprefix $(PREFIX_O)proc/, proc.o threads.o process.o name.o resource.o mutex.o cond.o userintr.o ports.o futex.o)

ifneq (, $(findstring NOMMU, $(CPPFLAGS)))
OBJS += $(PREFIX_O)proc/msg-nommu.o
Expand Down
216 changes: 216 additions & 0 deletions proc/futex.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
*
* Operating system kernel
*
* Futex
*
* Copyright 2025 Phoenix Systems
* Author: Kamil Kowalczyk
*
* This file is part of Phoenix-RTOS.
*
* %LICENSE%
*/

#include <stdatomic.h>
#include <stdbool.h>
#include "hal/hal.h"
#include "lib/lib.h"
#include "include/errno.h"
#include "include/time.h"
#include "process.h"
#include "threads.h"
#include "futex.h"


static u32 _proc_futexTableHash(addr_t address)
{
u32 key;

// hash the address
key = address >> 3;
key ^= key >> FUTEX_SLEEPQUEUES_BITS;
return key & FUTEX_SLEEPQUEUES_MASK;
}


static futex_sleepqueue_t *_proc_allocFutexSleepQueue(process_t *process, addr_t address)
{
u32 idx, i;

idx = _proc_futexTableHash(address);

/* Find a free slot using linear probing */
i = idx;
do {
if (process->futexSleepQueues[i].address == 0) {
process->futexSleepQueues[i].address = address;
return &process->futexSleepQueues[i];
}
i = (i + 1) % FUTEX_SLEEPQUEUES_SIZE;
} while (i != idx);

return NULL;
}


futex_sleepqueue_t *_proc_getFutexSleepQueue(process_t *process, addr_t address)
{
u32 idx, i;

idx = _proc_futexTableHash(address);

/* Find a taken slot with the same address using linear probing */
i = idx;
do {
if (process->futexSleepQueues[i].address == address) {
return &process->futexSleepQueues[i];
}
else if (process->futexSleepQueues[i].address == 0) {
return NULL;
}
i = (i + 1) % FUTEX_SLEEPQUEUES_SIZE;
} while (i != idx);

return NULL;
}


static bool proc_futexUnwait(futex_sleepqueue_t *sq, futex_waitctx_t *wc)
{
bool r;
spinlock_ctx_t sc;

hal_spinlockSet(&sq->spinlock, &sc);
r = atomic_load(&wc->thread) != NULL;
if (r) {
LIST_REMOVE(&sq->waitctxs, wc);
}
hal_spinlockClear(&sq->spinlock, &sc);
return r;
}


int proc_futexWait(_Atomic(u32) *address, u32 value, time_t timeout, int clockType)
{
spinlock_ctx_t sc, sqSc;
futex_sleepqueue_t *sq;
futex_waitctx_t wc;
thread_t *current;
int err = EOK;
time_t waitTime = 0, offs;


if (timeout != 0) {
switch (clockType) {
case PH_CLOCK_REALTIME:
proc_gettime(&waitTime, &offs);
if (waitTime + offs > timeout) {
return -ETIME;
}
waitTime = timeout - offs;
break;
case PH_CLOCK_MONOTONIC:
proc_gettime(&waitTime, NULL);
if (waitTime > timeout) {
return -ETIME;
}
waitTime = timeout;
break;
case PH_CLOCK_RELATIVE:
proc_gettime(&waitTime, NULL);
waitTime += timeout;
break;
default:
return -EINVAL;
}
}

current = proc_current();

hal_spinlockSet(&current->process->futexSqSpinlock, &sqSc);
sq = _proc_getFutexSleepQueue(current->process, (addr_t)address);
if (sq == NULL) {
sq = _proc_allocFutexSleepQueue(current->process, (addr_t)address);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, basically, once we allocate a sleep queue, we never free it.

I assume that you wanted to mimic futex syscall from other OSes, but can't we actually have a futexCreate syscall which will allocate a sleep queue (kernel side of futex, not including the userspace context) on idtree and release it via resourcesDestroy()?

}
hal_spinlockClear(&current->process->futexSqSpinlock, &sqSc);
if (sq == NULL) {
return -ENOMEM;
}
atomic_init(&wc.thread, NULL);

atomic_store(&wc.thread, current);
hal_spinlockSet(&sq->spinlock, &sc);
LIST_ADD(&sq->waitctxs, &wc);
hal_spinlockClear(&sq->spinlock, &sc);

if (atomic_load(address) != value) {
err = -EAGAIN;
proc_futexUnwait(sq, &wc);
}
else {

if (atomic_load(&wc.thread) != NULL) {
hal_spinlockSet(&sq->spinlock, &sc);
err = proc_threadWaitInterruptible(&sq->threads, &sq->spinlock, waitTime, &sc);
hal_spinlockClear(&sq->spinlock, &sc);
}

if (err != EOK || atomic_load(&wc.thread) != NULL) {
if (proc_futexUnwait(sq, &wc) == 0) {
err = EOK;
}
}
}
return err;
}


int proc_futexWakeup(process_t *process, _Atomic(u32) *address, u32 wakeCount)
{
futex_sleepqueue_t *sq;
futex_waitctx_t *wc = NULL, *wakeupList = NULL, *tmpwc;
int i = 0, woken = 0;
spinlock_ctx_t sc, sqSc;
thread_t *tmp = NULL;

if (wakeCount == 0) {
return 0;
}

hal_spinlockSet(&process->futexSqSpinlock, &sqSc);
sq = _proc_getFutexSleepQueue(process, (addr_t)address);
hal_spinlockClear(&process->futexSqSpinlock, &sqSc);
if (sq == NULL) {
return 0;
}

hal_spinlockSet(&sq->spinlock, &sc);
while ((wc = sq->waitctxs) != NULL) {
LIST_REMOVE(&sq->waitctxs, wc);
LIST_ADD(&wakeupList, wc);
i++;
if (i == wakeCount && wakeCount != FUTEX_WAKEUP_ALL) {
break;
}
}
hal_spinlockClear(&sq->spinlock, &sc);

if (wakeupList != NULL) {
wc = wakeupList;
do {
LIB_ASSERT(wc != NULL, "wc == NULL");
tmpwc = wc;
tmp = atomic_load(&tmpwc->thread);
wc = wc->next;
atomic_store(&tmpwc->thread, NULL);
hal_spinlockSet(&sq->spinlock, &sc);
if (proc_threadWakeupOne(tmp)) {
woken++;
}
hal_spinlockClear(&sq->spinlock, &sc);
} while (wc != NULL && wc != wakeupList);
}

return woken;
}
51 changes: 51 additions & 0 deletions proc/futex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Phoenix-RTOS
*
* Operating system kernel
*
* Futex
*
* Copyright 2025 Phoenix Systems
* Author: Kamil Kowalczyk
*
* This file is part of Phoenix-RTOS.
*
* %LICENSE%
*/

#ifndef _PROC_FUTEX_H_
#define _PROC_FUTEX_H_

#include "include/types.h"


/* Implementation inspired by: https://github.com/openbsd/src/blob/master/sys/kern/sys_futex.c */


#define FUTEX_SLEEPQUEUES_BITS 6
#define FUTEX_SLEEPQUEUES_SIZE (1U << FUTEX_SLEEPQUEUES_BITS)
#define FUTEX_SLEEPQUEUES_MASK (FUTEX_SLEEPQUEUES_SIZE - 1)
Comment on lines +25 to +27

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The size of the futex sleep queue hash table is fixed at 64 entries per process. If a process uses more than 64 distinct futexes concurrently, _proc_allocFutexSleepQueue will fail, and futex operations will return -ENOMEM. While this might be sufficient for many use cases, it could be a limitation for complex applications with many synchronization objects.

Consider if this size should be larger or configurable to avoid this potential resource exhaustion.

#define FUTEX_WAKEUP_ALL ((u32) - 1)


typedef struct _futex_waitctx_t {
struct _futex_waitctx_t *prev, *next;
_Atomic(struct _thread_t *) thread;
} futex_waitctx_t;


typedef struct {
struct _thread_t *threads;
spinlock_t spinlock;
addr_t address;
futex_waitctx_t *waitctxs;
} futex_sleepqueue_t;


int proc_futexWait(_Atomic(u32) *address, u32 value, time_t timeout, int clockType);


int proc_futexWakeup(struct _process_t *process, _Atomic(u32) *address, u32 wakeCount);


#endif
1 change: 1 addition & 0 deletions proc/proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "cond.h"
#include "userintr.h"
#include "ports.h"
#include "futex.h"


extern int _proc_init(vm_map_t *kmap, vm_object_t *kernel);
Expand Down
10 changes: 10 additions & 0 deletions proc/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ static void process_destroy(process_t *p)
if (imapp != NULL) {
vm_mapDestroy(p, imapp);
}
for (int i = 0; i < FUTEX_SLEEPQUEUES_SIZE; i++) {
hal_spinlockDestroy(&p->futexSleepQueues[i].spinlock);
}
hal_spinlockDestroy(&p->futexSqSpinlock);

proc_resourcesDestroy(p);
proc_portsDestroy(p);
Expand Down Expand Up @@ -216,6 +220,12 @@ int proc_start(void (*initthr)(void *), void *arg, const char *path)

proc_changeMap(process, NULL, NULL, NULL);

hal_memset(&process->futexSleepQueues, 0, sizeof(process->futexSleepQueues));
hal_spinlockCreate(&process->futexSqSpinlock, "futex_sq.spinlock");
for (int i = 0; i < FUTEX_SLEEPQUEUES_SIZE; i++) {
hal_spinlockCreate(&process->futexSleepQueues[i].spinlock, "futex_sleepqueue.spinlock");
}

/* Initialize resources tree for mutex and cond handles */
_resource_init(process);
process_alloc(process);
Expand Down
4 changes: 4 additions & 0 deletions proc/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "vm/amap.h"
#include "syspage.h"
#include "lib/lib.h"
#include "futex.h"

#define MAX_PID MAX_ID

Expand Down Expand Up @@ -70,6 +71,9 @@ typedef struct _process_t {

void *got;
hal_tls_t tls;

spinlock_t futexSqSpinlock;
futex_sleepqueue_t futexSleepQueues[FUTEX_SLEEPQUEUES_SIZE];
} process_t;


Expand Down
25 changes: 25 additions & 0 deletions proc/threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,19 @@ static int _proc_threadWakeup(thread_t **queue)
}


static int _proc_threadWakeupOne(thread_t *thread)
{
int ret = 1;
if (thread != NULL && thread != wakeupPending) {
_proc_threadDequeue(thread);
}
else {
ret = 0;
}
return ret;
}


int proc_threadWakeup(thread_t **queue)
{
int ret = 0;
Expand All @@ -1209,6 +1222,18 @@ int proc_threadWakeup(thread_t **queue)
}


int proc_threadWakeupOne(thread_t *thread)
{
int ret = 0;
spinlock_ctx_t sc;

hal_spinlockSet(&threads_common.spinlock, &sc);
ret = _proc_threadWakeupOne(thread);
hal_spinlockClear(&threads_common.spinlock, &sc);
return ret;
}


static int _proc_threadBroadcast(thread_t **queue)
{
int ret = 0;
Expand Down
Loading
Loading