SchOS/docs/Website/index.html
Nexoniarz d3c37f82b6 docs: the tree moved and the documentation did not
The site and several documents still described the system as it was before
virtual terminals, the new schinit, the hybrid work and descriptor passing.

docs/architecture/init.md described the wrong program. It documented a single
schinit that parses manifests and supervises jobs, which has not been true since
PID 1 was split: PID 1 forks a supervisor, spawns jobs on its behalf and reaps;
the job table lives in /proc/schinit; a heartbeat runs both ways; and
proc_reincarnate() re-execs PID 1 in place rather than panicking. Rewritten
around what exists, keeping the reasoning that is still load-bearing -- the five
failures of the sibling-init scheme, the escalation ladder, and the three bugs in
the reincarnation path that each had to happen before they were obvious.

docs/plan/init.md is deleted, which is the tree's own rule for finished work and
had not been applied. Fourteen source comments referenced it by part number, so
they are repointed at the architecture doc by section name rather than left
dangling -- a deleted document nobody notices is worse than a stale one.

docs/architecture/devices.md gained the section it has been missing since
/dev/pci existed: what a driver needs from a kernel, why mmap is checked against
the caller's claims, why DMA is a call and not a mapping, and why the write back
after an interrupt is not optional. Also that a released device does not come
back by itself, which is true and was nowhere.

docs/architecture/networking.md gained a descriptor-passing section, and
docs/architecture/README.md's stack diagram gained the two layers and the third
seam it was missing. "Two seams hold the whole thing together" has not been true
since /dev/pci.

docs/plan/README.md said the hybrid work was deliberately last, on the argument
that an interface with no consumer is wrong in ways nobody can see. The argument
was right; the conclusion was not, and the document now says why it changed --
the alternative to a driver compatibility layer is writing a driver for every
device that exists, and the original objection is answered rather than ignored
because SchOS's own drivers are the consumers.

The site: a Hybrid section with the eviction transcript, cards for virtual
terminals, schinit and drivers outside the kernel, and the numbers remeasured
rather than estimated -- 46,961 lines, 207 syscalls. The boot log in app.js is
from a boot taken today, and the lines are trimmed to what the console can show
without clipping, each still a verbatim prefix of real output.

Two small rendering fixes found by looking at the pages rather than assuming:
.console pre's min-height is now scoped to the hero, so a static transcript sizes
to its content instead of reserving eight lines it does not have; and the docs
reader's contents panel strips inline marks from headings, so a heading written
`proc_reincarnate()` no longer carries its backticks into the sidebar.

Every intra-doc link checked; none broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 20:05:06 +02:00

436 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SchOS — a 64-bit operating system written from scratch</title>
<meta name="description" content="SchOS is a from-scratch x86-64 operating system: the SchIFT kernel, the SchFS copy-on-write filesystem, a UEFI loader, and a userland that runs unmodified musl and toybox.">
<link rel="icon" href="Assets/shift.svg" type="image/svg+xml">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="top">
<div class="wrap">
<a class="brand" href="#top">
<img src="Assets/shift.svg" alt="">
SchOS
</a>
<a class="pride" href="#pride-note" title="SchOS stands with the LGBTQIAP+ community — every month of the year">
<img src="Assets/pride.svg" alt="Progress Pride flag">
</a>
<nav>
<a href="#origin">Origin</a>
<a href="#stack">The stack</a>
<a href="#what">What it has</a>
<a href="#hybrid">Hybrid</a>
<a href="#numbers">Numbers</a>
<a href="docs.html">Docs</a>
</nav>
</div>
</header>
<main id="top">
<section class="hero">
<div class="wrap">
<div>
<h1><img class="mark" src="Assets/shift.svg" alt="">SchOS</h1>
<p>
A 64-bit operating system written from scratch — its own kernel, its own
filesystem, its own bootloader. It boots on UEFI, drives real hardware,
and runs unmodified <code>musl</code> and <code>toybox</code> on top.
</p>
<div class="cta">
<a class="btn primary" href="#what">What it has</a>
<a class="btn" href="#origin">Where it started</a>
</div>
</div>
<div class="console" aria-label="Boot log">
<div class="console-bar">
<i class="dot live"></i> ttyS0
<span>SchIFT</span>
</div>
<pre id="bootlog" aria-live="polite"></pre>
</div>
</div>
</section>
<section id="origin" class="reveal">
<div class="wrap">
<p class="eyebrow">Origin</p>
<h2>It started as a teaching kernel and stopped being one</h2>
<p class="lede">
SchOS began as <a href="https://github.com/mit-pdos/xv6-public">xv6</a>, MIT's
teaching operating system: 32-bit, single core, one filesystem, a few dozen
syscalls. Almost none of that survives. What follows is what replaced it.
</p>
<div class="origin-grid">
<div class="step">
<span class="when">First</span>
<h3>64-bit, and more than one core</h3>
<p>
Four-level paging, a real SMP bring-up, per-CPU state and a trap path
that survives both. This is the part that invalidates most of what a
32-bit uniprocessor kernel assumes.
</p>
</div>
<div class="step">
<span class="when">Then</span>
<h3>Its own filesystem</h3>
<p>
xv6's log-structured filesystem was replaced by <b>SchFS</b>: a
copy-on-write B-tree with an uberblock ring, extents and checksummed
metadata. A crash leaves either the old tree or the new one.
</p>
</div>
<div class="step">
<span class="when">Then</span>
<h3>A Linux-shaped ABI</h3>
<p>
The syscall table adopted Linux numbering, which is what makes an
unmodified musl and an unmodified toybox run. The table is one
<code>.inc</code> file, and it is the seam the whole system is built around.
</p>
</div>
<div class="step">
<span class="when">Then</span>
<h3>Hardware, not emulation</h3>
<p>
A UEFI loader, xHCI and USB storage, an e1000 with a real TCP/IP stack,
virtio-gpu and VBE framebuffers, and a device model where a driver never
names its consumer.
</p>
</div>
<div class="step">
<span class="when">Now</span>
<h3>Drivers that are not in the kernel</h3>
<p>
A process can be handed a PCI device — its registers, its memory, its
interrupts — and drive it. The GPU driver already runs that way. Nobody
writes a driver for every device that exists; the answer is to stop
requiring that they live inside.
</p>
</div>
</div>
</div>
</section>
<section id="stack" class="reveal">
<div class="wrap">
<p class="eyebrow">Architecture</p>
<h2>Three seams hold the whole thing together</h2>
<p class="lede">
Above the syscall table, software is ordinary and portable. Below the device
model, a driver never names its consumer and a consumer never names a driver.
And at <code>/dev/pci</code>, a driver stops needing to be in the kernel at
all. Everything else is arrangement.
</p>
<div class="stack">
<div class="layer"><span class="name">userland</span><span class="what">toybox, mksh, zsh, musl, and the first-party programs in <code>cmd/</code></span></div>
<div class="layer"><span class="name">userspace drivers</span><span class="what"><code>vgpud</code> — a driver that is an ordinary process</span></div>
<div class="layer"><span class="name">schinit</span><span class="what">PID 1 — job manifests, supervision, reincarnation</span></div>
<div class="layer seam"><span class="name">syscall table</span><span class="what">Linux numbering, one <code>.inc</code> file — the ABI seam</span></div>
<div class="layer"><span class="name">kernel services</span><span class="what">VFS, sockets and <code>SCM_RIGHTS</code>, signals, mmap, poll, procfs</span></div>
<div class="layer"><span class="name">subsystems</span><span class="what">SchFS, lwIP, virtual terminals, graphics, the buffer cache</span></div>
<div class="layer seam"><span class="name">device model</span><span class="what">class vtables and bus binding — the driver seam</span></div>
<div class="layer seam"><span class="name">/dev/pci</span><span class="what">registers, DMA and interrupts to a process — the eviction seam</span></div>
<div class="layer"><span class="name">drivers</span><span class="what">xHCI, e1000, virtio-gpu, VBE, IDE, PS/2</span></div>
<div class="layer"><span class="name">SchIFT core</span><span class="what">scheduler, paging, traps, IRQ, SMP, DMA</span></div>
<div class="layer"><span class="name">boot</span><span class="what">the UEFI loader in <code>boot/uefi</code></span></div>
</div>
<p class="seam-note">
<code>uname -s</code> reports <b>SchIFT</b> — the kernel — the way every Unix
reports its kernel rather than its distribution.
</p>
</div>
</section>
<section id="what" class="reveal">
<div class="wrap">
<p class="eyebrow">What it has</p>
<h2>Built and running, and honest about the rest</h2>
<p class="lede">
Every card marked <b>runs</b> is in the tree and works today. The tags are
not aspirational; the things that are not finished say so.
</p>
<div class="cards">
<div class="card">
<span class="tag runs">runs</span>
<h3><img class="cardmark" src="Assets/uberblock.svg" alt="">SchFS</h3>
<p>
Copy-on-write B-tree, a 128-slot uberblock ring, extents with refcounts
and reflink, CRC32c on metadata, and free space derived from the tree
rather than stored beside it — so there is no second structure to
disagree with the first. There is nothing to repair after a crash.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>SMP x86-64 kernel</h3>
<p>
Four-level paging, multiple cores, preemptive scheduling, a calibrated
LAPIC timer, real signal delivery and job control. 205 syscalls on
Linux numbering.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>Networking</h3>
<p>
An e1000 driver under lwIP, with sockets for AF_INET, AF_UNIX and
AF_NETLINK. DHCP brings the interface up at boot; <code>ping</code>
reaches the outside world.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>UEFI boot</h3>
<p>
A PE/COFF loader built with clang and lld-link that sets up its own page
tables and hands the kernel a framebuffer. No BIOS path, no chainloading.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>USB</h3>
<p>
xHCI, hubs, HID and mass storage — a keyboard and a USB disk that the
kernel can mount, through the same device model as everything else.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>Virtual terminals</h3>
<p>
Six of them, each with its own cell grid, scrollback and foreground
job — switched with <code>AltGr+F1</code>…<code>F6</code>, which is the
one chord a Linux host does not take for itself. The console draws to
whichever framebuffer is present, from the kernel's first line.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>schinit</h3>
<p>
PID 1 does almost nothing: it forks a supervisor, spawns jobs on its
behalf, and reaps. The supervisor holds the manifests and the respawn
policy, and if it dies PID 1 replaces it and hands back the children it
was already watching. PID 1 can re-exec itself without dropping them.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>A real userland</h3>
<p>
musl and toybox, both unmodified, plus mksh as <code>/bin/sh</code>.
<b>fastfetch</b> builds from upstream source with an empty
<code>patches/</code> directory — when it could not detect something, the
answer was to give the kernel what it was looking for.
</p>
</div>
<div class="card">
<span class="tag runs">runs</span>
<h3>Drivers outside the kernel</h3>
<p>
<code>/dev/pci</code> hands a process a device: its registers by
<code>mmap</code>, physically contiguous memory it can point the device
at, and interrupts as something to <code>read()</code>. The virtio-gpu
driver already lives out there. <a href="#hybrid">How that works →</a>
</p>
</div>
<div class="card">
<span class="tag partial">in progress</span>
<h3>Schcom</h3>
<p>
The compositor that will own the display. v0 runs, and descriptor
passing — the one thing a Wayland client cannot do without — now
exists. What a buffer still needs is a shareable memory object.
</p>
</div>
<div class="card">
<span class="tag planned">designed</span>
<h3>SchAPI</h3>
<p>
The native programming interface — specified, not built, and deliberately
so. It should be designed <i>from</i> the compositor's experience rather
than before it.
</p>
</div>
</div>
</div>
</section>
<section id="hybrid" class="reveal">
<div class="wrap">
<p class="eyebrow">Hybrid</p>
<h2>A driver can leave the kernel</h2>
<p class="lede">
Nobody writes a driver for every device that exists — not a project this
size, and not any project. So the interesting question is not how to write
more of them, but what a driver actually needs from a kernel, and whether it
can have that from outside.
</p>
<div class="origin-grid">
<div class="step">
<span class="when">It needs</span>
<h3>Its registers</h3>
<p>
<code>mmap</code> on <code>/dev/pci</code>, where the offset is the
physical address of a BAR. The kernel refuses any offset that is not
inside a BAR of a device this process was given, which is what stops it
being <code>/dev/mem</code> with extra steps.
</p>
</div>
<div class="step">
<span class="when">It needs</span>
<h3>Memory the device can reach</h3>
<p>
Physically contiguous, and the process is told the physical address —
which is the whole reason it cannot do this for itself. The arena is
reserved at boot, before the general allocator gets anything.
</p>
</div>
<div class="step">
<span class="when">It needs</span>
<h3>To be told when to look</h3>
<p>
Interrupts arrive by <code>read()</code>, so they compose with
<code>poll</code>. The kernel masks the line and leaves it masked until
the driver writes back — because a PCI interrupt is level-triggered, and
nobody but the driver knows how to clear it at the device.
</p>
</div>
<div class="step">
<span class="when">It needs</span>
<h3>A descriptor to arrive</h3>
<p>
<code>sendmsg</code> with <code>SCM_RIGHTS</code>. Handing a process the
device it is allowed to touch and handing a compositor a buffer turned
out to be the same act, which is why one primitive answered both.
</p>
</div>
</div>
<div class="console transcript" aria-label="Evicting the GPU driver to userspace">
<div class="console-bar">
<i class="dot live"></i> tty1
<span>vgpud</span>
</div>
<pre><span class="t">$ </span>vgpud
<span class="k">vgpud</span>: virtio-gpu at 00:03.0, currently held by virtio-gpu
<span class="k">virtio-gpu</span>: released to userspace; screen is the VBE framebuffer
<span class="k">vgpud</span>: claimed
<span class="k">vgpud</span>: display is 1280x800
<span class="k">vgpud</span>: scanout is ours, framebuffer at physical 0x3eceb000
<span class="g">vgpud</span>: <span class="g">drew a test pattern from userspace</span>
</pre>
</div>
<p class="seam-note">
<b>No syscall in that transcript knows it is talking about a GPU.</b> The
kernel handed over a PCI device; the process decided what it was. That is
the property that makes the interface general — and the reason SchOS's own
drivers are being moved out through it first, rather than shaping it around
something foreign and hoping it fits.
</p>
</div>
</section>
<section id="numbers" class="reveal">
<div class="wrap">
<p class="eyebrow">Numbers</p>
<h2>The size of it</h2>
<div class="figures">
<div class="figure"><b>46,961</b><span>lines in <code>kernel/</code> and <code>include/</code></span></div>
<div class="figure"><b>207</b><span>syscalls, Linux numbering</span></div>
<div class="figure"><b>128</b><span>uberblock ring slots</span></div>
<div class="figure"><b>0</b><span>patches carried against fastfetch</span></div>
<div class="figure"><b>1</b><span>driver already running outside the kernel</span></div>
</div>
</div>
</section>
<section class="crash reveal">
<div class="wrap">
<img src="Assets/guru.svg" alt="Guru, the SchOS crash-screen icon">
<div>
<p class="eyebrow">When it stops</p>
<h2>PSOGM</h2>
<p>
When the kernel panics it does not scroll a stack trace past you. It draws
a full-screen report — what failed, where, and the state that mattered —
and encodes the whole thing as a QR code so it can leave the machine
without being retyped.
</p>
<p>
This purple is <code>#4B1D9A</code>, and the theme reserves it for exactly
this. Normal states are calm and dark; only failure is loud. It is the one
colour in the system that means something, which is why it appears once on
this page and nowhere else.
</p>
</div>
</div>
</section>
<section id="docs" class="reveal">
<div class="wrap">
<p class="eyebrow">Reading</p>
<h2>Where to look</h2>
<p class="lede">
The documentation is written to outlive the work. Anything finished is
described in <code>architecture/</code>; anything intended lives in
<code>plan/</code>; and the bugs worth remembering are kept in
<code>history/</code>, in full, including the wrong turns.
</p>
<p class="readerlink"><a class="btn primary" href="docs.html">Open the docs reader</a></p>
<div class="links">
<a class="link-card" href="docs.html?doc=architecture/README.md">
<b>Architecture</b><span>What the system is, layer by layer</span>
</a>
<a class="link-card" href="docs.html?doc=plan/1.0.md">
<b>The 1.0 definition</b><span>What is in, what is out, and why</span>
</a>
<a class="link-card" href="docs.html?doc=history/kernel.md">
<b>Kernel history</b><span>The bugs, and what each one taught</span>
</a>
<a class="link-card" href="docs.html?doc=guides/building.md">
<b>Building it</b><span>Toolchain, ports, and running under QEMU</span>
</a>
</div>
</div>
</section>
</main>
<footer>
<div class="wrap">
<span>SchOS — kernel <b>SchIFT</b>, filesystem <b>SchFS</b>.</span>
<span class="mono">Apache-2.0, with the xv6-derived core under MIT.</span>
<span class="pride-note" id="pride-note">
<img src="Assets/pride.svg" alt="">
Built by, and for, people of every orientation and gender identity.
The flag stays up all year.
</span>
<span class="note" id="ai-note">
<b>On AI.</b>
Parts of SchOS were written with AI assistance, and that is not the measure
of anything. A project is good when the people building it understand what
was built, and bad when nobody does — whoever, or whatever, typed it. Vibe
your way through and you get something no one can fix. Stay in the loop and
you get a system you can explain line by line. The bug writeups in
<a href="docs.html?doc=history/kernel.md">history/</a> are where that gets
checked, including the wrong turns.
</span>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>