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
This commit is contained in:
subhobhai943andClaude Opus 5 committed 2026-08-24 23:38:59 +05:30
1 parent 492045b24d
commit f9400f6ac4
22 files changed
+3631 -5

No files matched your search

+333
View File
@@ -0,0 +1,333 @@
// Write-back block device page cache for SUB-OS
#include <mm/page_cache.h>
#include <mm/kmalloc.h>
#include <block/block.h>
#include <lib/hashtable.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/sync.h>
#include <kernel/printk.h>
#include <arch/arch.h>
static page_cache_entry_t g_pages[PAGE_CACHE_MAX_PAGES];
static hashtable_t* g_index = NULL;
static page_cache_stats_t g_stats;
static spinlock_t g_lock = SPINLOCK_INIT;
static uint32_t g_clock_hand = 0;
static bool g_initialized = false;
// The hash key encodes both the device and the page index so a single table
// serves every registered block device.
static void make_key(char* out, size_t out_len, const char* dev, uint64_t idx) {
snprintf(out, out_len, "%s:%llu", dev ? dev : "?", (unsigned long long)idx);
}
void page_cache_init(void) {
memset(g_pages, 0, sizeof(g_pages));
memset(&g_stats, 0, sizeof(g_stats));
spinlock_init(&g_lock);
g_clock_hand = 0;
g_index = hashtable_create(PAGE_CACHE_MAX_PAGES * 2, true);
g_initialized = (g_index != NULL);
}
static int page_read_from_disk(page_cache_entry_t* e) {
block_device_t* dev = block_get_device(e->dev_name);
if (!dev || !dev->read_sectors) return -1;
uint64_t sector = e->page_index * PAGE_CACHE_SECTORS;
if (sector + PAGE_CACHE_SECTORS > dev->total_sectors) {
// Reads past the end of the device yield zeroes rather than an error,
// matching how the VFS treats sparse tails.
memset(e->data, 0, PAGE_CACHE_BLOCK_SIZE);
return 0;
}
int rc = dev->read_sectors(dev, sector, PAGE_CACHE_SECTORS, e->data);
if (rc == 0) g_stats.read_ios++;
return rc;
}
static int page_write_to_disk(page_cache_entry_t* e) {
block_device_t* dev = block_get_device(e->dev_name);
if (!dev || !dev->write_sectors || dev->read_only) return -1;
uint64_t sector = e->page_index * PAGE_CACHE_SECTORS;
if (sector + PAGE_CACHE_SECTORS > dev->total_sectors) return -1;
int rc = dev->write_sectors(dev, sector, PAGE_CACHE_SECTORS, e->data);
if (rc == 0) {
g_stats.write_ios++;
g_stats.writebacks++;
}
return rc;
}
static void unindex(page_cache_entry_t* e) {
char key[64];
make_key(key, sizeof(key), e->dev_name, e->page_index);
hashtable_remove(g_index, key);
}
// CLOCK eviction: sweep the ring clearing reference bits until an unpinned,
// unreferenced page turns up. Dirty victims are written back first.
static page_cache_entry_t* evict_one(void) {
for (int sweep = 0; sweep < PAGE_CACHE_MAX_PAGES * 2; sweep++) {
page_cache_entry_t* e = &g_pages[g_clock_hand];
g_clock_hand = (g_clock_hand + 1) % PAGE_CACHE_MAX_PAGES;
if (!e->valid) return e;
if (e->pin_count > 0) continue;
if (e->referenced) {
e->referenced = false;
continue;
}
if (e->dirty) {
if (page_write_to_disk(e) != 0) continue; // Cannot flush: skip it
e->dirty = false;
if (g_stats.dirty_pages > 0) g_stats.dirty_pages--;
}
unindex(e);
e->valid = false;
if (g_stats.resident_pages > 0) g_stats.resident_pages--;
g_stats.evictions++;
return e;
}
return NULL;
}
static page_cache_entry_t* lookup_locked(const char* dev_name, uint64_t page_index) {
char key[64];
make_key(key, sizeof(key), dev_name, page_index);
return (page_cache_entry_t*)hashtable_get(g_index, key);
}
page_cache_entry_t* page_cache_get(const char* dev_name, uint64_t page_index) {
if (!g_initialized || !dev_name) return NULL;
spin_lock(&g_lock);
page_cache_entry_t* e = lookup_locked(dev_name, page_index);
if (e && e->valid) {
e->referenced = true;
e->last_access = pit_get_ticks();
e->pin_count++;
e->hits++;
g_stats.hits++;
spin_unlock(&g_lock);
return e;
}
g_stats.misses++;
e = evict_one();
if (!e) {
spin_unlock(&g_lock);
return NULL;
}
if (!e->data) {
e->data = (uint8_t*)kmalloc(PAGE_CACHE_BLOCK_SIZE);
if (!e->data) {
spin_unlock(&g_lock);
return NULL;
}
g_stats.memory_bytes += PAGE_CACHE_BLOCK_SIZE;
}
strncpy(e->dev_name, dev_name, sizeof(e->dev_name) - 1);
e->dev_name[sizeof(e->dev_name) - 1] = '\0';
e->page_index = page_index;
e->dirty = false;
e->referenced = true;
e->pin_count = 1;
e->hits = 0;
e->last_access = pit_get_ticks();
if (page_read_from_disk(e) != 0) {
memset(e->data, 0, PAGE_CACHE_BLOCK_SIZE);
}
e->valid = true;
g_stats.resident_pages++;
char key[64];
make_key(key, sizeof(key), dev_name, page_index);
hashtable_put(g_index, key, e);
spin_unlock(&g_lock);
return e;
}
void page_cache_release(page_cache_entry_t* entry) {
if (!entry) return;
spin_lock(&g_lock);
if (entry->pin_count > 0) entry->pin_count--;
spin_unlock(&g_lock);
}
void page_cache_mark_dirty(page_cache_entry_t* entry) {
if (!entry || !entry->valid) return;
spin_lock(&g_lock);
if (!entry->dirty) {
entry->dirty = true;
g_stats.dirty_pages++;
}
spin_unlock(&g_lock);
}
size_t page_cache_read(const char* dev_name, uint64_t offset, void* buf, size_t len) {
if (!g_initialized || !dev_name || !buf || len == 0) return 0;
uint8_t* out = (uint8_t*)buf;
size_t done = 0;
while (done < len) {
uint64_t page_index = (offset + done) / PAGE_CACHE_BLOCK_SIZE;
size_t page_off = (size_t)((offset + done) % PAGE_CACHE_BLOCK_SIZE);
size_t chunk = PAGE_CACHE_BLOCK_SIZE - page_off;
if (chunk > len - done) chunk = len - done;
page_cache_entry_t* e = page_cache_get(dev_name, page_index);
if (!e) break;
memcpy(out + done, e->data + page_off, chunk);
page_cache_release(e);
done += chunk;
}
return done;
}
size_t page_cache_write(const char* dev_name, uint64_t offset, const void* buf, size_t len) {
if (!g_initialized || !dev_name || !buf || len == 0) return 0;
const uint8_t* in = (const uint8_t*)buf;
size_t done = 0;
while (done < len) {
uint64_t page_index = (offset + done) / PAGE_CACHE_BLOCK_SIZE;
size_t page_off = (size_t)((offset + done) % PAGE_CACHE_BLOCK_SIZE);
size_t chunk = PAGE_CACHE_BLOCK_SIZE - page_off;
if (chunk > len - done) chunk = len - done;
page_cache_entry_t* e = page_cache_get(dev_name, page_index);
if (!e) break;
memcpy(e->data + page_off, in + done, chunk);
page_cache_mark_dirty(e);
page_cache_release(e);
done += chunk;
}
return done;
}
int page_cache_sync_dev(const char* dev_name) {
if (!g_initialized) return -1;
int flushed = 0;
spin_lock(&g_lock);
for (int i = 0; i < PAGE_CACHE_MAX_PAGES; i++) {
page_cache_entry_t* e = &g_pages[i];
if (!e->valid || !e->dirty) continue;
if (dev_name && strcmp(e->dev_name, dev_name) != 0) continue;
if (page_write_to_disk(e) == 0) {
e->dirty = false;
if (g_stats.dirty_pages > 0) g_stats.dirty_pages--;
flushed++;
}
}
spin_unlock(&g_lock);
return flushed;
}
int page_cache_sync(void) {
return page_cache_sync_dev(NULL);
}
int page_cache_invalidate(const char* dev_name) {
if (!g_initialized) return -1;
int dropped = 0;
spin_lock(&g_lock);
for (int i = 0; i < PAGE_CACHE_MAX_PAGES; i++) {
page_cache_entry_t* e = &g_pages[i];
if (!e->valid || e->dirty || e->pin_count > 0) continue;
if (dev_name && strcmp(e->dev_name, dev_name) != 0) continue;
unindex(e);
e->valid = false;
if (g_stats.resident_pages > 0) g_stats.resident_pages--;
dropped++;
}
spin_unlock(&g_lock);
return dropped;
}
void page_cache_drop_all(void) {
page_cache_sync();
page_cache_invalidate(NULL);
}
page_cache_stats_t page_cache_get_stats(void) {
return g_stats;
}
uint32_t page_cache_hit_percent(void) {
uint64_t total = g_stats.hits + g_stats.misses;
if (total == 0) return 0;
return (uint32_t)((g_stats.hits * 100) / total);
}
void page_cache_dump(void) {
printk(ANSI_BRIGHT_CYAN "=== SUB-OS Block Page Cache ===\n" ANSI_RESET);
if (!g_initialized) {
printk(ANSI_YELLOW " Page cache not initialized\n" ANSI_RESET);
return;
}
printk(" Page size : %d bytes (%d sectors)\n",
PAGE_CACHE_BLOCK_SIZE, PAGE_CACHE_SECTORS);
printk(" Resident : %u / %d pages (%llu KB allocated)\n",
g_stats.resident_pages, PAGE_CACHE_MAX_PAGES,
(unsigned long long)(g_stats.memory_bytes / 1024));
printk(" Dirty : %u pages\n", g_stats.dirty_pages);
printk(" Hits/Misses : %llu / %llu (" ANSI_BRIGHT_GREEN "%u%% hit rate" ANSI_RESET ")\n",
(unsigned long long)g_stats.hits,
(unsigned long long)g_stats.misses,
page_cache_hit_percent());
printk(" Evictions : %llu (CLOCK second-chance)\n",
(unsigned long long)g_stats.evictions);
printk(" Block I/O : %llu reads, %llu writes (%llu write-backs)\n\n",
(unsigned long long)g_stats.read_ios,
(unsigned long long)g_stats.write_ios,
(unsigned long long)g_stats.writebacks);
printk(ANSI_YELLOW " %-12s %10s %6s %6s %6s %8s\n" ANSI_RESET,
"DEVICE", "PAGE", "DIRTY", "PIN", "REF", "HITS");
int shown = 0;
for (int i = 0; i < PAGE_CACHE_MAX_PAGES && shown < 16; i++) {
page_cache_entry_t* e = &g_pages[i];
if (!e->valid) continue;
printk(" %-12s %10llu %6s %6u %6s %8llu\n",
e->dev_name, (unsigned long long)e->page_index,
e->dirty ? ANSI_RED "yes" ANSI_RESET : "no",
e->pin_count,
e->referenced ? "yes" : "no",
(unsigned long long)e->hits);
shown++;
}
if (shown == 0) printk(ANSI_BRIGHT_BLACK " (cache is empty)\n" ANSI_RESET);
printk("\n Index: %llu entries in %llu buckets, longest chain %llu\n",
(unsigned long long)hashtable_size(g_index),
(unsigned long long)hashtable_buckets(g_index),
(unsigned long long)hashtable_longest_chain(g_index));
}