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:
parent
9e6d8dd4e3
commit
d3c37f82b6
21 changed files with 548 additions and 336 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
||||
# The website
|
||||
|
||||
Three files and a folder of marks. No build step, no dependencies, no
|
||||
Five files and a folder of marks. No build step, no dependencies, no
|
||||
framework — open `index.html` and it works, including from a `file://` URL.
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@
|
|||
*/
|
||||
var LINES = [
|
||||
{ t: "[ 0.000]", k: "SchIFT", s: " 64-bit, 2 cpus" },
|
||||
{ t: "[ 0.004]", k: "cpu0", s: ": lapic timer 999949260 Hz, tsc 3792 MHz" },
|
||||
{ t: "[ 0.061]", k: "virtio-gpu", s: ": 1280x800, 4000 KB framebuffer, irq 11" },
|
||||
{ t: "[ 0.004]", k: "cpu0", s: ": lapic timer 999941980 Hz, tsc 3792 MHz" },
|
||||
{ t: "[ 0.049]", k: "virtio-gpu", s: ": 1280x800, 4000 KB framebuffer" },
|
||||
{ t: "[ 0.088]", k: "e1000", s: ": 52:54:00:12:34:56, link up, irq 11" },
|
||||
{ t: "[ 0.143]", k: "schfs0", s: ": mounted, generation 41" },
|
||||
{ t: "[ 0.143]", k: "schfs", s: ": mounted 'schos', generation 2" },
|
||||
{ t: "[ 0.169]", k: "net", s: ": eth0 10.0.2.15/255.255.255.0" },
|
||||
{ t: "[ 0.204]", k: "schinit", s: ": 4 jobs", g: true },
|
||||
{ t: "[ 0.204]", k: "schinit", s: ": ready (pid 1)" },
|
||||
{ t: "[ 0.212]", k: "schinitd", s: ": 2 job(s) loaded, generation 1", g: true },
|
||||
{ t: "", k: "", s: "# " }
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* read without leaving the page and without a build step. No dependency: the
|
||||
* parser below handles exactly what docs/ actually uses -- headings, fenced
|
||||
* code, tables, lists, quotes, and inline marks. Pulling in a 40 KB Markdown
|
||||
* library to render 29 files would outweigh every other asset here combined.
|
||||
* library to render 30 files would outweigh every other asset here combined.
|
||||
*
|
||||
* Everything is escaped before any tag is emitted. The only HTML that reaches
|
||||
* the page is HTML this file wrote.
|
||||
|
|
@ -46,7 +46,6 @@
|
|||
["plan/README.md", "What plan/ is for"],
|
||||
["plan/roadmap.md", "Roadmap"],
|
||||
["plan/foundations.md", "Foundations"],
|
||||
["plan/init.md", "schinit"],
|
||||
["plan/wayland.md", "Wayland"],
|
||||
["plan/hybrid.md", "Hybrid kernel"],
|
||||
["plan/schapi.md", "SchAPI"]
|
||||
|
|
@ -268,6 +267,18 @@
|
|||
});
|
||||
}
|
||||
|
||||
/* A heading's own Markdown, as text. The contents panel is a list of
|
||||
destinations, not a place to render code and emphasis -- a heading written
|
||||
`proc_reincarnate()` should read as proc_reincarnate() there rather than
|
||||
carrying its backticks across. Marks are stripped, not escaped: the result
|
||||
goes through textContent. */
|
||||
function plain(s) {
|
||||
return s.replace(/`([^`]+)`/g, "$1")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
||||
.replace(/\*([^*]+)\*/g, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
|
||||
}
|
||||
|
||||
function buildToc(headings) {
|
||||
tocEl.innerHTML = "";
|
||||
var real = headings.filter(function (h) { return h.level >= 2; });
|
||||
|
|
@ -283,7 +294,7 @@
|
|||
li.className = "lvl" + h.level;
|
||||
var a = document.createElement("a");
|
||||
a.href = "#" + h.id;
|
||||
a.textContent = h.text;
|
||||
a.textContent = plain(h.text);
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -203,8 +203,12 @@ header.top {
|
|||
line-height: 1.85;
|
||||
color: var(--fg);
|
||||
overflow-x: auto;
|
||||
min-height: 16.5rem;
|
||||
}
|
||||
/* The hero's console reserves its height so the typing animation does not
|
||||
push the page around as it fills. A static transcript sizes to its own
|
||||
content instead, which is why this is not on .console pre. */
|
||||
.hero .console pre { min-height: 16.5rem; }
|
||||
.console.transcript { margin-top: 3rem; }
|
||||
.console .t { color: var(--dim); }
|
||||
.console .k { color: var(--accent); }
|
||||
.console .g { color: var(--ok); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue