feat: extend slab with KmemCache and SpinLock

This commit is contained in:
dualfroz committed 2026-09-02 16:45:11 +02:00
1 parent 8d2b1df100
commit 98e09f13f8
4 files changed
+199

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
#ifndef FROSTBYTE_SLAB_H
#define FROSTBYTE_SLAB_H
#include "types.h"
/*
* Slab: a fixed-size object cache backed directly by physical frames from
* pmm, bypassing the general-purpose heap. Each slab is exactly one frame;
* the Slab descriptor lives at the start of that same frame (so freeing a
* pointer is O(1): mask it down to the enclosing frame to recover the
* descriptor, no list walk needed), and the rest of the frame is carved
* into objSize-aligned objects threaded onto a singly-linked free list
* through their own (currently-unused) storage.
*
* Intended for kernel-internal fixed-size allocations (list/tree nodes,
* PCB-sized structs, ...) where slab's O(1) alloc/free and per-slab
* locality beat the heap's general-purpose free-list search.
*/
typedef struct KmemCache {
const char* name;
usize objSize; /* rounded up to at least sizeof(void*), 8-aligned */
usize objsPerSlab;
struct Slab* slabs; /* every slab owned by this cache, newest first */
usize slabCount;
usize usedObjs;
} KmemCache;
/* Returns false if objSize (after rounding) cannot fit at least one object
per frame alongside the slab header -- the cache is left zeroed either
way, so kmemCacheDestroy is always safe to call. */
bool kmemCacheInit(KmemCache* cache, const char* name, usize objSize);
/* Releases every frame the cache ever grew into back to pmm. The cache is
left in a freshly-initialized (empty, growable-again) state. */
void kmemCacheDestroy(KmemCache* cache);
/* Grows the cache (allocates a new frame from pmm) on demand. nullptr only
if pmm is out of frames. */
void* kmemCacheAlloc(KmemCache* cache);
/* obj must have come from kmemCacheAlloc on this exact cache and not
already be free. */
void kmemCacheFree(KmemCache* cache, void* obj);
usize kmemCacheUsed(const KmemCache* cache);
/* Objects across every slab the cache has grown into so far -- 0 until the
first kmemCacheAlloc, since slabs are only ever grown lazily on demand. */
usize kmemCacheCapacity(const KmemCache* cache);
usize kmemCacheSlabCount(const KmemCache* cache);
/* How many objects a single slab (frame) holds for this cache -- fixed at
kmemCacheInit time, independent of how many slabs have been grown. */
usize kmemCacheObjsPerSlab(const KmemCache* cache);
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef FROSTBYTE_SPINLOCK_H
#define FROSTBYTE_SPINLOCK_H
#include "types.h"
/*
* SpinLock: an interrupt-safe mutual-exclusion lock for state shared between
* thread context and interrupt handlers (irq0/irq1 currently run with
* interrupts re-enabled per pic.c, so a naive "just disable interrupts"
* critical section is not enough once two handlers can nest).
*
* Acquire disables interrupts and spins on an atomic test-and-set before
* returning; release restores whatever the interrupt flag was at the
* matching acquire. This is correct and SMP-ready even though frostbyte is
* currently single-core: on one core, acquiring an already-held lock is a
* bug (it would spin forever with interrupts off), so spinLockTryAcquire is
* provided for call sites -- and tests -- that need to observe contention
* without deadlocking.
*/
typedef struct SpinLock {
volatile uint32 locked;
} SpinLock;
void spinLockInit(SpinLock* lock);
/* Disables interrupts, then spins until the lock is acquired. Returns the
interrupt flag as it was immediately before this call, to be passed back
to spinLockRelease unchanged (callers must not interleave two different
locks' flags). */
uint64 spinLockAcquire(SpinLock* lock);
/* Non-blocking. On success (true), interrupts are disabled and *savedFlags
holds the pre-call flag word, exactly as spinLockAcquire would leave
things. On failure (false) nothing is touched -- interrupts are left
exactly as the caller had them. */
bool spinLockTryAcquire(SpinLock* lock, uint64* savedFlags);
/* flags must be the value spinLockAcquire/spinLockTryAcquire handed back. */
void spinLockRelease(SpinLock* lock, uint64 flags);
bool spinLockIsLocked(const SpinLock* lock);
#endif
+54
View File
@@ -15,5 +15,59 @@
#define sysGetdents 11 #define sysGetdents 11
#define sysMkdir 12 #define sysMkdir 12
#define sysUnlink 13 #define sysUnlink 13
#define sysPipe 14
#define sysSymlink 15
#define sysReadlink 16
#define sysLink 17
#define sysExec 18
#define sysSpawn 19
#define sysWait 20
#define sysPipeCreate 21
#define sysPipeAttach 22
#define sysKill 23
#define sysMkfifo 24
#define sysSigSend 25
#define sysSigAction 26
#define sysSigProcMask 27
#define sysSigReturn 28
#define sysAlarm 29
#define sysSetpgid 30
#define sysGetpgid 31
#define sysSetFgPgrp 32
#define sysGetFgPgrp 33
#define sysPoll 34
#define sysChdir 35
#define sysGetcwd 36
#define sysGetEnv 37
#define sysSetEnv 38
#define sysNice 39
#define sysGetPriority 40
#define sysTime 41
#define sysUnsetEnv 42
#define sysRmdir 43
#define sysChmod 44
#define sysGetuid 45
#define sysSetuid 46
#define sysChown 47
#define sysGetgid 48
#define sysSetgid 49
#define sysChgrp 50
#define sysPidfdOpen 51
#define sysPidfdSend 52
#define sysPidfdWait 53
#define sysMmap 54
#define sysMunmap 55
/* Sentinel pid for sysWait (syscall.c)/userWaitAny (ulib.c): real
waitpid(-1)-style "reap whichever child happens to already be dead,
without caring which specific pid" instead of the ordinary "wait for
exactly this one" contract every other sysWait id value has.
(unsigned int)-1 can never collide with a real pid, since pid 0 (idle)
and every id returned by threadCreate/threadCreateUser are always <
maxThreads (sched.c). Deliberately spelled with the plain C type, not
sched.h's uint32, so this header stays usable from userland (ulib.h/
ulib.c) exactly like fs.h/signalnum.h's own shared definitions already
are -- see fs.h's FbStat for the same convention. */
#define waitAnyPid ((unsigned int)-1)
#endif #endif
+46
View File
@@ -0,0 +1,46 @@
#ifndef FROSTBYTE_VEC_H
#define FROSTBYTE_VEC_H
#include "types.h"
/*
* Vec: a growable, type-erased array backed by the kernel heap. Elements
* are stored by value (memcpy'd in and out), sized by elemSize so a single
* implementation serves every element type without void* indirection or
* per-type code generation.
*/
typedef struct Vec {
uint8* data;
usize elemSize;
usize length;
usize capacity;
} Vec;
void vecInit(Vec* v, usize elemSize);
void vecFree(Vec* v);
/* Ensures capacity for at least newCapacity elements. Returns false only
if the underlying allocation failed (existing contents are untouched). */
bool vecReserve(Vec* v, usize newCapacity);
bool vecPush(Vec* v, const void* elem);
/* Removes and copies out the last element. False (and *outElem untouched)
when the vector is empty; outElem may be nullptr to just drop it. */
bool vecPop(Vec* v, void* outElem);
/* Pointer into the backing storage; nullptr if index is out of range.
Invalidated by any push/insert/remove/reserve that reallocates. */
void* vecAt(Vec* v, usize index);
bool vecSet(Vec* v, usize index, const void* elem);
bool vecInsert(Vec* v, usize index, const void* elem);
bool vecRemove(Vec* v, usize index, void* outElem);
void vecClear(Vec* v);
usize vecLen(const Vec* v);
usize vecCap(const Vec* v);
bool vecIsEmpty(const Vec* v);
#endif