diff --git a/include/rtc.h b/include/rtc.h new file mode 100644 index 0000000..b964c1d --- /dev/null +++ b/include/rtc.h @@ -0,0 +1,65 @@ +#ifndef FROSTBYTE_RTC_H +#define FROSTBYTE_RTC_H + +#include "types.h" + +/* + * CMOS real-time clock driver (MC146818-compatible, ports 0x70/0x71) -- + * the first source of real wall-clock date/time this kernel has ever had. + * Every other notion of "time" in frostbyte (pit.h's tick counter, + * threadSleep, userAlarm) is relative to boot, not to the calendar; this + * is the piece that lets a process ask "what day is it" at all. + * + * Honest limitation, same spirit as syscall.c's own documented fork()-less + * design: the CMOS century register's index is not standardized (it is + * only discoverable via the ACPI FADT table, which this kernel does not + * parse), so rtcReadDateTime hardcodes the century to 20 -- correct for + * any date from 2000 through 2099, which covers every real boot this + * kernel will ever see, but not a documented general-purpose solution. + */ + +typedef struct RtcDateTime { + int64 year; /* full year, e.g. 2026 */ + int64 month; /* 1-12 */ + int64 day; /* 1-31 */ + int64 hour; /* 0-23, already normalized out of 12-hour/PM if needed */ + int64 minute; /* 0-59 */ + int64 second; /* 0-59 */ +} RtcDateTime; + +/* Converts a single CMOS BCD byte (e.g. 0x59) to its binary value (59). + Exposed on its own, like ata.c's ataModelString, because it is a real + piece of the conversion pipeline worth testing in isolation from any + hardware access. */ +uint8 rtcBcdToBinary(uint8 bcd); + +/* Reads the current date/time off the CMOS RTC. Polls Status Register A's + Update-In-Progress bit (bounded, like ata.c's ataPollTimeout) before + each of two back-to-back raw reads and only accepts the result once both + raw reads agree byte-for-byte, which rules out a read torn mid-tick by + the RTC's own once-a-second update. Returns false only if the two raw + reads never agreed within the bounded retry budget (never hangs). */ +bool rtcReadDateTime(RtcDateTime* out); + +/* Civil calendar date/time -> seconds since the Unix epoch + (1970-01-01T00:00:00Z), via Howard Hinnant's days_from_civil algorithm + (proleptic Gregorian, correct for any year the RtcDateTime fields above + can represent). Pure arithmetic, no hardware access -- deterministic and + testable without a real clock. */ +int64 rtcEpochSeconds(const RtcDateTime* dt); + +/* rtcReadDateTime + rtcEpochSeconds in one call, the usual entry point for + anything that just wants "now" as a plain integer (sysTime, syscall.c). + Returns 0 if the underlying read failed. */ +int64 rtcNow(void); + +/* Seconds since the Unix epoch -> civil calendar date/time, the exact + inverse of rtcEpochSeconds above (Howard Hinnant's civil_from_days, + proleptic Gregorian, same algorithm family as daysFromCivil in rtc.c). + Correct for any epochSeconds value representable in int64, including + negative ones (dates before 1970). Pure arithmetic, no hardware access + -- the piece sfs.h's on-disk mtime/ctime (raw epoch integers) needs to + become a human-readable date, e.g. for a shell `ls -l`. */ +void rtcCivilFromEpoch(int64 epochSeconds, RtcDateTime* out); + +#endif diff --git a/include/sched.h b/include/sched.h index e421e4b..a78f294 100644 --- a/include/sched.h +++ b/include/sched.h @@ -3,13 +3,78 @@ #include "types.h" #include "vfs.h" +#include "prioq.h" +#include "isr.h" +#include "signalnum.h" +#include "mmap.h" + +/* Default scheduling weight (xv6/CFS-style "nice" niceWeight -- higher runs + more often, not longer). threadSetPriority scales relative to this. */ +#define schedDefaultWeight 1024u + +/* Returned by threadCreate/threadCreateUser when the thread table is full + (see threadCapacity) -- never a valid thread id, since id 0 is always the + permanent idle thread. Callers must check for this before using the + result to index into anything thread-related. */ +#define threadInvalidId 0xFFFFFFFFu + +/* Capacity of Thread.name, including the terminator. Large enough for every + name this kernel currently produces (kernel thread literals, "/bin/..." + paths passed to threadCreateUser by spawnUser/doSpawn) with room to + spare; longer names are truncated, never overflowed. */ +#define threadNameMax 32 + +/* Capacity of Thread.env below: envMaxVars is how many "NAME=VALUE" slots + a process may hold at once, envEntryMax is the max size in bytes of a + single slot INCLUDING both the '=' separator and the nul terminator + (so "NAME=VALUE\0" must fit in envEntryMax bytes). Generous enough for + anything this kernel's own shell/demos actually set (see user/envprint.c) + with room to spare, the same "small fixed table, not a growable heap + allocation" choice Thread.cwd already made for a single path. */ +#define envMaxVars 8 +#define envEntryMax 64 + +/* One bit per signal number in Thread.sigPending/sigBlocked, and the size + of Thread.sigHandlers[] below -- see signal.h for the actual signal + subsystem this supports. Defined here, not signal.h, purely to break the + include cycle (signal.h includes sched.h for the Thread type). */ +#define sigMaxSignals 32 + +/* Depth of the per-thread nested-handler context stack (Thread.sigSavedRegs/ + sigSavedBlocked below) -- how many signal handlers may genuinely interrupt + each other (handler A running, a second signal arrives and is not blocked + by A's own sa_mask, so handler B runs nested inside A, then A resumes once + B returns) before sigConsumePending (signal.c) refuses to deliver a new + one and simply leaves it pending. A real kernel bounds this too (rlimit + on the signal-frame stack it pushes into user memory); this one has no + user-memory signal frame to overflow at all (see signal.h's file comment + -- the saved context lives in the Thread struct itself), so the bound + exists purely to keep that struct's size fixed and to fail a runaway + signal-storm safely (further deliveries just wait) instead of unboundedly + growing kernel memory per thread. 4 is generous headroom for anything + this kernel's own handlers actually do (none nest more than one deep + today -- see user/nesttarget.c) without being large enough to make + Thread noticeably bigger than sigMaxSignals-worth of handlers already + makes it. */ +#define sigMaxNestDepth 4 typedef enum { threadUnused = 0, threadRunnable = 1, threadRunning = 2, threadSleeping = 3, - threadDead = 4 + threadDead = 4, + threadBlocked = 5, + /* Job-control stop (signal.h's SIGSTOP/SIGTSTP): excluded from + schedule()'s ready-queue rebuild exactly like Sleeping/Blocked + already are (only threadRunnable is ever inserted), so a stopped + thread simply never gets picked until threadContinue moves it back + to threadRunnable. Distinct from threadBlocked because it is NOT + waiting on a wait-channel condition -- schedWakeChannel must never + treat it as a match (see schedWakeChannel), only threadContinue + (an explicit SIGCONT, not "the resource became available") ever + moves a thread out of this state. */ + threadStopped = 6 } ThreadState; typedef void (*ThreadEntry)(void); @@ -19,7 +84,41 @@ typedef struct Thread { uint64 stackBase; uint64 kernelStackTop; uint64 sleepUntil; + /* Absolute pitTicks() deadline for a pending alarm(2)-style SIGALRM + (0 == no alarm pending), set by threadSetAlarm and consulted once + per timer tick by schedule() -- the exact same shape as sleepUntil + above, checked in the exact same loop, except expiry raises a real + signal (sigRaise, signal.h) against the owning thread instead of + just flipping its own ThreadState back to Runnable. Deliberately a + plain deadline, not a wait-channel/ThreadState change: unlike + threadSleep, a thread with a pending alarm keeps running (or + sleeping, or blocked on something unrelated) exactly as it already + was right up until the deadline, exactly like real alarm()'s timer + ticking down in the background regardless of what the process is + doing. */ + uint64 alarmAt; + /* poll.h's sysPoll (syscall.c) uses the same restart-the-syscall park + pattern sysRead/sysWrite/sysWait already use, but a poll call can be + woken by any one of several unrelated fds, not one -- so instead of + parking on a single fd's own wait channel (vfsBlockingChannel), + sysPoll blocks every waiting thread on ONE shared channel + (schedPollChannel below) and simply re-checks its whole fd list from + scratch on every wake, spurious or not, exactly like the retry loop + every OTHER restartable syscall already has. pollActive marks "this + thread is mid a not-yet-satisfied sysPoll call" (so a retry entry + can tell itself apart from a genuinely fresh call); pollDeadline is + the absolute pitTicks() deadline schedule()'s own expiry scan forces + a wake at if no fd ever becomes ready first (0 == wait forever, + meaningful only while pollActive is true -- the same "0 reserved" + convention alarmAt above already uses, but here 0 is a real, + legal, requested state (a timeout of -1) rather than "nothing + pending"). */ + bool pollActive; + uint64 pollDeadline; uint64 brk; + uint64 vruntime; + uint64 exitCode; + uint32 weight; uint32 id; ThreadState state; const char* name;