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
This commit is contained in:
Nexoniarz 2026-09-13 20:05:06 +02:00
commit d3c37f82b6
21 changed files with 548 additions and 336 deletions

View file

@ -23,6 +23,7 @@
<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>
@ -95,7 +96,7 @@
</p>
</div>
<div class="step">
<span class="when">Now</span>
<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,
@ -103,6 +104,16 @@
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>
@ -110,20 +121,23 @@
<section id="stack" class="reveal">
<div class="wrap">
<p class="eyebrow">Architecture</p>
<h2>Two seams hold the whole thing together</h2>
<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.
Everything else is arrangement.
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, musl, and the first-party programs in <code>cmd/</code></span></div>
<div class="layer"><span class="name">schinit</span><span class="what">PID 1 — job manifests and supervision</span></div>
<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, signals, mmap, poll, procfs</span></div>
<div class="layer"><span class="name">subsystems</span><span class="what">SchFS, lwIP, text and graphics, the buffer cache</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>
@ -190,6 +204,26 @@
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>
@ -200,12 +234,23 @@
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. The desktop above it
is the next large piece of work, and it is deliberately Wayland-shaped.
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">
@ -221,15 +266,93 @@
</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>43,701</b><span>lines in <code>kernel/</code> and <code>include/</code></span></div>
<div class="figure"><b>205</b><span>syscalls, Linux numbering</span></div>
<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>