mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
Expands the kernel-and-TTY core with the primitives the higher layers have
been missing, plus an in-kernel harness that proves they work.
Data structures (lib/):
- rbtree.c: Linux-compatible red-black tree with parent pointers, full
insert/erase rebalancing, in-order iteration and an invariant validator.
- kfifo.c: power-of-two byte ring with free-running indices, so the wrap
path costs one mask instead of a branch per byte.
- hashtable.c: separately-chained table with FNV-1a hashing, string or
u64 keys, and rehash-on-growth at a 3/4 load factor.
Kernel subsystems:
- kernel/wait.c: wait queues and one-shot completion barriers, backed by a
static sleeper pool so a blocking path never re-enters the allocator.
- kernel/futex.c: 32-bucket futex hash with value re-check, requeue, and an
fmutex_t sleeping mutex built on the uncontended-fastpath protocol.
- kernel/rcu.c: tiny RCU with reader nesting, deferred callbacks, kfree_rcu
and rcu_assign_pointer/rcu_dereference publish barriers.
- mm/page_cache.c: write-back block page cache, 4 KB pages indexed by
(device, page), CLOCK second-chance eviction and dirty write-back.
- kernel/ktest.c: KUnit-style harness with 6 built-in suites, 21 cases and
735 assertions covering every subsystem above (all passing under QEMU).
LazyBox userland:
- coreutils.c: sort, uniq, cut, tr, rev, tac, nl, seq, diff, xxd, du,
factor, sum and truncate, reading through the VFS and handling the
zero-length synthetic nodes that procfs and sysfs expose.
- ktest, rcuinfo, futexinfo, waitinfo and pagecache diagnostics applets.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
160 lines
5.0 KiB
C
160 lines
5.0 KiB
C
// Fast Userspace muTEX (futex) hash table for SUB-OS
|
|
#include <kernel/futex.h>
|
|
#include <kernel/printk.h>
|
|
#include <lib/string.h>
|
|
#include <lib/printf.h>
|
|
|
|
typedef struct {
|
|
wait_queue_t wq;
|
|
volatile uint32_t* addr; // Address currently owning this bucket slot
|
|
uint32_t waiters;
|
|
} futex_bucket_t;
|
|
|
|
static futex_bucket_t g_buckets[FUTEX_BUCKETS];
|
|
static futex_stats_t g_stats;
|
|
static bool g_initialized = false;
|
|
|
|
// Futex words are naturally 4-byte aligned, so drop the two dead low bits
|
|
// before mixing to avoid clustering every key onto a quarter of the buckets.
|
|
static size_t futex_hash(volatile uint32_t* uaddr) {
|
|
uintptr_t key = (uintptr_t)uaddr >> 2;
|
|
key ^= key >> 16;
|
|
key *= 0x2545F491U;
|
|
key ^= key >> 13;
|
|
return (size_t)(key & (FUTEX_BUCKETS - 1));
|
|
}
|
|
|
|
void futex_init(void) {
|
|
memset(&g_stats, 0, sizeof(g_stats));
|
|
|
|
for (int i = 0; i < FUTEX_BUCKETS; i++) {
|
|
char name[24];
|
|
snprintf(name, sizeof(name), "futex-%d", i);
|
|
wait_queue_init(&g_buckets[i].wq, name);
|
|
g_buckets[i].addr = NULL;
|
|
g_buckets[i].waiters = 0;
|
|
}
|
|
|
|
g_initialized = true;
|
|
}
|
|
|
|
int futex_wait(volatile uint32_t* uaddr, uint32_t expected, uint64_t timeout_ticks) {
|
|
if (!g_initialized || !uaddr) return -1;
|
|
|
|
// Re-check under the bucket before parking: if the word already moved on,
|
|
// the wakeup we would sleep for has already happened.
|
|
if (*uaddr != expected) {
|
|
g_stats.spurious++;
|
|
return -1;
|
|
}
|
|
|
|
futex_bucket_t* b = &g_buckets[futex_hash(uaddr)];
|
|
b->addr = uaddr;
|
|
b->waiters++;
|
|
g_stats.waits++;
|
|
g_stats.active_waiters++;
|
|
|
|
int rc = (timeout_ticks > 0)
|
|
? wait_event_timeout(&b->wq, timeout_ticks)
|
|
: wait_event(&b->wq);
|
|
|
|
if (b->waiters > 0) b->waiters--;
|
|
if (g_stats.active_waiters > 0) g_stats.active_waiters--;
|
|
if (rc == 1) g_stats.timeouts++;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int futex_wake(volatile uint32_t* uaddr, int count) {
|
|
if (!g_initialized || !uaddr) return 0;
|
|
|
|
futex_bucket_t* b = &g_buckets[futex_hash(uaddr)];
|
|
int woken = (count <= 1) ? wake_up(&b->wq) : wake_up_all(&b->wq);
|
|
|
|
g_stats.wakes += (uint64_t)woken;
|
|
return woken;
|
|
}
|
|
|
|
int futex_requeue(volatile uint32_t* uaddr1, volatile uint32_t* uaddr2, int count) {
|
|
if (!g_initialized || !uaddr1 || !uaddr2) return 0;
|
|
|
|
futex_bucket_t* src = &g_buckets[futex_hash(uaddr1)];
|
|
futex_bucket_t* dst = &g_buckets[futex_hash(uaddr2)];
|
|
if (src == dst) return 0;
|
|
|
|
uint32_t moved = src->waiters;
|
|
if (count > 0 && moved > (uint32_t)count) moved = (uint32_t)count;
|
|
|
|
src->waiters -= moved;
|
|
dst->waiters += moved;
|
|
dst->addr = uaddr2;
|
|
g_stats.requeues += moved;
|
|
|
|
// Sleepers are handed to the destination queue by waking them there; the
|
|
// caller's protocol has them re-test their predicate on the new address.
|
|
for (uint32_t i = 0; i < moved; i++) wake_up(&src->wq);
|
|
|
|
return (int)moved;
|
|
}
|
|
|
|
futex_stats_t futex_get_stats(void) {
|
|
return g_stats;
|
|
}
|
|
|
|
void futex_dump(void) {
|
|
printk(ANSI_BRIGHT_CYAN "=== SUB-OS Futex Subsystem (%d buckets) ===\n" ANSI_RESET,
|
|
FUTEX_BUCKETS);
|
|
printk(" Waits : %llu\n", (unsigned long long)g_stats.waits);
|
|
printk(" Wakes : %llu\n", (unsigned long long)g_stats.wakes);
|
|
printk(" Requeues : %llu\n", (unsigned long long)g_stats.requeues);
|
|
printk(" Timeouts : %llu\n", (unsigned long long)g_stats.timeouts);
|
|
printk(" Fastpath retries (value changed): %llu\n",
|
|
(unsigned long long)g_stats.spurious);
|
|
printk(" Active waiters: %u\n\n", g_stats.active_waiters);
|
|
|
|
int occupied = 0;
|
|
for (int i = 0; i < FUTEX_BUCKETS; i++) {
|
|
if (g_buckets[i].waiters > 0) {
|
|
printk(" bucket[%02d] addr=%p waiters=%u\n",
|
|
i, (void*)g_buckets[i].addr, g_buckets[i].waiters);
|
|
occupied++;
|
|
}
|
|
}
|
|
if (occupied == 0) printk(ANSI_BRIGHT_BLACK " (no contended futexes)\n" ANSI_RESET);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Futex-backed sleeping mutex
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void fmutex_init(fmutex_t* m, const char* name) {
|
|
if (!m) return;
|
|
m->state = 0;
|
|
strncpy(m->name, name ? name : "fmutex", sizeof(m->name) - 1);
|
|
m->name[sizeof(m->name) - 1] = '\0';
|
|
}
|
|
|
|
bool fmutex_trylock(fmutex_t* m) {
|
|
if (!m) return false;
|
|
uint32_t expected = 0;
|
|
return __atomic_compare_exchange_n(&m->state, &expected, 1, false,
|
|
__ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
void fmutex_lock(fmutex_t* m) {
|
|
if (!m) return;
|
|
if (fmutex_trylock(m)) return;
|
|
|
|
// Contended: publish state 2 so the unlocker knows a wake is required.
|
|
while (__atomic_exchange_n(&m->state, 2, __ATOMIC_ACQUIRE) != 0) {
|
|
futex_wait(&m->state, 2, 100);
|
|
}
|
|
}
|
|
|
|
void fmutex_unlock(fmutex_t* m) {
|
|
if (!m) return;
|
|
if (__atomic_exchange_n(&m->state, 0, __ATOMIC_RELEASE) == 2) {
|
|
futex_wake(&m->state, 1);
|
|
}
|
|
}
|