feat: add PollFd, PrioQNode and PrioQueue
This commit is contained in:
1 parent
25f50f156a
commit
8af9a09f89
4 files changed
+198
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user