From 8af9a09f890748e7ad4b63d74cdeb2a753ad4fb8 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 1 Sep 2026 20:57:22 +0200 Subject: [PATCH] feat: add PollFd, PrioQNode and PrioQueue --- include/poll.h | 34 ++++++++++++++++++++++++++ include/prioq.h | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ include/rbt.h | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ include/ring.h | 39 ++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 include/poll.h create mode 100644 include/prioq.h create mode 100644 include/rbt.h create mode 100644 include/ring.h diff --git a/include/poll.h b/include/poll.h new file mode 100644 index 0000000..7ad52bd --- /dev/null +++ b/include/poll.h @@ -0,0 +1,34 @@ +#ifndef FROSTBYTE_POLL_H +#define FROSTBYTE_POLL_H + +#include "types.h" + +/* + * Real poll(2): waits for one or more file descriptors to become ready + * without picking, ahead of time, which single one to block on the way + * userRead/userWrite's own retry-the-syscall path already does for exactly + * one fd at a time (vfs.h's vfsBlockingChannel). Bit values match real + * Linux (POLLIN=0x1, POLLOUT=0x4, POLLERR=0x8, POLLHUP=0x10, + * POLLNVAL=0x20) even though nothing here reads a Linux binary's struct -- + * there is no reason to invent different numbers for the same concept. + */ +#define pollIn 0x1 +#define pollOut 0x4 +#define pollErr 0x8 +#define pollHup 0x10 +#define pollNval 0x20 + +typedef struct PollFd { + int32 fd; + int16 events; + int16 revents; +} PollFd; + +/* Bounded the same way maxOpenFiles (vfs.h) bounds one process's own fd + table -- sysPoll (syscall.c) copies the caller's array into a fixed + kernel-stack buffer of exactly this size, so a request for more entries + than any one process could ever have open fds for is simply rejected + rather than silently truncated. */ +#define maxPollFds 16 + +#endif diff --git a/include/prioq.h b/include/prioq.h new file mode 100644 index 0000000..bd42b5c --- /dev/null +++ b/include/prioq.h @@ -0,0 +1,62 @@ +#ifndef FROSTBYTE_PRIOQ_H +#define FROSTBYTE_PRIOQ_H + +#include "types.h" +#include "rbt.h" + +/* + * Prioq: an ephemeral min-vruntime priority queue built on top of the + * intrusive red-black tree (rbt.h) -- the "run queue" half of a CFS-lite + * (Completely Fair Scheduler, weighted-fair) scheduler. The caller owns the + * PrioQNode storage (typically embedded in a scheduler's thread struct, + * next to the vruntime counter it mirrors) and is expected to rebuild the + * queue from scratch every time a scheduling decision is needed: insert + * every currently-eligible candidate, pop the minimum, discard the rest. + * That "ephemeral" usage pattern (rather than a queue that persists node + * membership across calls) sidesteps a real hazard for a preemptive kernel + * scheduler: a thread can become eligible again from code paths other than + * the scheduler itself (e.g. a wait-queue wakeup), and keeping a + * long-lived queue in sync with every one of those paths is exactly the + * kind of dual-source-of-truth bug this design avoids by never trying. + * + * Ordering key is (vruntime, id): id is a mandatory, unique tiebreaker + * because rbtInsert silently rejects a node whose key compares equal to one + * already present (no duplicates in the underlying tree) -- two candidates + * that happen to share a vruntime would otherwise cause one of them to be + * dropped from the queue entirely rather than merely deprioritised. + */ + +typedef struct PrioQNode { + RbtNode node; + uint64 vruntime; + uint32 id; +} PrioQNode; + +typedef struct PrioQueue { + RbTree tree; +} PrioQueue; + +void prioqInit(PrioQueue* q); + +/* (Re)inserts n into the queue, keyed by (vruntime, id). n's storage is + owned by the caller and may be reused across unrelated queue lifetimes -- + every field the tree needs is reset unconditionally by the underlying + rbtInsert. id must be unique among nodes simultaneously in the same + queue. */ +void prioqInsert(PrioQueue* q, PrioQNode* n, uint64 vruntime, uint32 id); + +/* Returns the minimum-vruntime node without removing it, or nullptr if the + queue is empty. */ +PrioQNode* prioqPeekMin(const PrioQueue* q); + +/* Removes and returns the minimum-vruntime node, or nullptr if the queue is + empty. */ +PrioQNode* prioqPopMin(PrioQueue* q); + +/* Removes an arbitrary node that is currently a member of q. */ +void prioqRemove(PrioQueue* q, PrioQNode* n); + +usize prioqCount(const PrioQueue* q); +bool prioqIsEmpty(const PrioQueue* q); + +#endif diff --git a/include/rbt.h b/include/rbt.h new file mode 100644 index 0000000..9b2f067 --- /dev/null +++ b/include/rbt.h @@ -0,0 +1,63 @@ +#ifndef FROSTBYTE_RBT_H +#define FROSTBYTE_RBT_H + +#include "types.h" + +/* + * Rbt: an intrusive red-black tree, Linux-kernel style. Embed an RbtNode in + * your own struct (next to a key field) and use rbtEntry() (mirrors + * listEntry from list.h) to recover the containing struct. No allocation, + * no element copying -- the tree only ever relinks the nodes you give it. + * + * Ordering is supplied by the caller as plain function pointers rather than + * baked into the node, so the same node type can be indexed by different + * keys in different trees (compare two full nodes for rbtInsert; compare a + * raw key against a node for rbtFind, same convention as bsearch). + */ + +typedef struct RbtNode { + struct RbtNode* left; + struct RbtNode* right; + struct RbtNode* parent; + bool red; +} RbtNode; + +#define rbtEntry(ptr, type, member) \ + ((type*)((uint8*)(ptr) - __builtin_offsetof(type, member))) + +/* Returns <0, 0, >0 as a compares below/equal/above b. Equal means "already + present" -- rbtInsert rejects the new node in that case (no duplicates; + encode a tiebreaker into the comparison if you need a multiset). */ +typedef int (*RbtCompareFn)(const RbtNode* a, const RbtNode* b); + +/* Same convention as bsearch: <0 if key sorts before node, >0 if after. */ +typedef int (*RbtLookupFn)(const void* key, const RbtNode* node); + +typedef struct RbTree { + RbtNode* root; + usize count; +} RbTree; + +void rbtInit(RbTree* t); + +/* Links node into the tree according to compare. Returns false (node left + untouched) if a node comparing equal is already present. */ +bool rbtInsert(RbTree* t, RbtNode* node, RbtCompareFn compare); + +/* Unlinks node from the tree. node must currently be a member of t. */ +void rbtRemove(RbTree* t, RbtNode* node); + +RbtNode* rbtFind(const RbTree* t, const void* key, RbtLookupFn lookup); + +RbtNode* rbtFirst(const RbTree* t); /* leftmost (minimum) */ +RbtNode* rbtLast(const RbTree* t); /* rightmost (maximum) */ + +/* In-order successor/predecessor of node. nullptr past either end. Walking + rbtFirst()..rbtNext()..nullptr visits every node in ascending order. */ +RbtNode* rbtNext(const RbtNode* node); +RbtNode* rbtPrev(const RbtNode* node); + +usize rbtCount(const RbTree* t); +bool rbtIsEmpty(const RbTree* t); + +#endif diff --git a/include/ring.h b/include/ring.h new file mode 100644 index 0000000..731acfd --- /dev/null +++ b/include/ring.h @@ -0,0 +1,39 @@ +#ifndef FROSTBYTE_RING_H +#define FROSTBYTE_RING_H + +#include "types.h" + +/* + * RingBuffer: a fixed-capacity byte circular buffer over a caller-supplied + * backing array. No heap dependency, so it is safe to use for early + * interrupt-driven queues (keyboard scancodes, serial RX) that need to + * exist before the kernel heap is initialised. + */ + +typedef struct RingBuffer { + uint8* data; + usize capacity; + usize head; /* next byte to read */ + usize count; /* bytes currently queued */ +} RingBuffer; + +void ringInit(RingBuffer* r, uint8* backingBuffer, usize capacity); +void ringClear(RingBuffer* r); + +bool ringIsEmpty(const RingBuffer* r); +bool ringIsFull(const RingBuffer* r); +usize ringCount(const RingBuffer* r); +usize ringCapacity(const RingBuffer* r); +usize ringFreeSpace(const RingBuffer* r); + +/* Single-byte push/pop. Push returns false (buffer unchanged) when full; + pop returns false (out untouched) when empty. */ +bool ringPush(RingBuffer* r, uint8 byte); +bool ringPop(RingBuffer* r, uint8* out); + +/* Bulk transfer. Writes/reads as many bytes as fit/are available (may be + fewer than requested) and returns the count actually transferred. */ +usize ringWrite(RingBuffer* r, const uint8* data, usize len); +usize ringRead(RingBuffer* r, uint8* data, usize len); + +#endif