Files
subhobhai943andClaude Opus 5 f9400f6ac4 feat(kernel): Add RB-tree/kfifo/hashtable, wait queues, futex, tiny RCU, page cache and KTest harness
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
2026-08-24 23:38:59 +05:30

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);
}
}