feat: add PollFd, PrioQNode and PrioQueue

This commit is contained in:
dualfroz committed 2026-09-01 20:57:22 +02:00
1 parent 25f50f156a
commit 8af9a09f89
4 files changed
+198

No files matched your search

+34
View File
@@ -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 <poll.h> (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
+62
View File
@@ -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
+63
View File
@@ -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
+39
View File
@@ -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