Guru and SchCotie are gone. Shift is the only mark, and the crash screen draws it where the face used to be. Guru was an icon -- a pictogram with a face, denoting one state -- that lived on the crash screen and nowhere else. Its argument was identity: `:(` is recognisably Microsoft's, and a crash screen is the single screenshot most likely to leave the machine, so it is the worst place to be wearing somebody else's face. That part holds, and it is kept in the branding README because it is the argument against any second mark. What does not follow is that the answer is a *new* mark. Shift already signs the system, it is already what the machine drew on the splash it started with, and a reader who has seen it twice before the crash recognises it faster than a face they have never seen. SchCotie was a mascot, and never existed: the file said so itself under "what this does not cover". A brief for a character nobody is about to draw is not worth carrying. So `draw_guru()` and its SVG-space geometry leave panic.c, the two `guru.svg` files are deleted, and PSOGM calls gfx_draw_logo() -- the same include/logo.h mask the loader splash and the boot console composite. At its native 128 px: there is no resampler in the crash path, and the face was scalable in a place that only ever drew it at one size. The name stays. "Purple Screen of Guru Meditation" is the Amiga nod, which panic.c's own header already called out as a separate thing from the mark. **And taking the screenshot to check it caught a regression from the TTF console work.** `gfx_draw_char()` -- the pixel-positioned path, which only PSOGM uses -- still walked `font_data[c][row]` for `row < CHAR_HEIGHT`. font_data has exactly 16 rows per glyph; CHAR_HEIGHT is the *TrueType* cell height now, about 21. So every line of the crash report drew five rows of the next character's bitmap underneath itself, and it looked like each line had been printed twice. The grid path had been updated when the cell size stopped being a constant; this one had not, because nothing routine draws text in pixel coordinates. Both go through one draw_glyph_px() now, and the bitmap's own geometry is spelled BITMAP_W/BITMAP_H so it cannot be mistaken for a cell size again. Two stale claims corrected while in there, in panic.c's header and in graphics.md: PSOGM has not dimmed-and-floated over the old screen for a while -- it clears to full purple, and the file's own code comment says why -- and there is no longer "a face that is obviously a cartoon". The website's crash section shows the mark in flat white rather than its own violets, which is what the kernel actually draws: PSOGM composites a coverage mask in one colour, and those violets sit close enough to #4B1D9A to half-disappear against it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
453 lines
18 KiB
HTML
453 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="download.html">Download</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="download.html">Download it</a>
|
|
<a class="btn" href="#what">What it has</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>0</b><span>patches carried against fastfetch</span></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="crash reveal">
|
|
<div class="wrap">
|
|
<img src="Assets/shift.svg" alt="Shift, the SchOS mark, as the crash screen draws it">
|
|
<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>
|
|
|
|
<section class="getband reveal">
|
|
<div class="wrap">
|
|
<img src="Assets/usb.svg" alt="">
|
|
<div>
|
|
<p class="eyebrow">Try it</p>
|
|
<h2>One ISO, one stick, nothing written to your disks</h2>
|
|
<p class="lede">
|
|
It runs from RAM and leaves the machine as it found it. Or skip the
|
|
stick entirely — <code>make qemu</code> is the same system in a window.
|
|
</p>
|
|
<p class="readerlink">
|
|
<a class="btn primary" href="download.html">Download SchOS</a>
|
|
<a class="btn" href="docs.html?doc=guides/building.md">Build it instead</a>
|
|
</p>
|
|
</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>
|