SchOS Progress Pride flag

SchOS

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 musl and toybox on top.

ttyS0 SchIFT

    

Origin

It started as a teaching kernel and stopped being one

SchOS began as xv6, 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.

First

64-bit, and more than one core

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.

Then

Its own filesystem

xv6's log-structured filesystem was replaced by SchFS: 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.

Then

A Linux-shaped ABI

The syscall table adopted Linux numbering, which is what makes an unmodified musl and an unmodified toybox run. The table is one .inc file, and it is the seam the whole system is built around.

Then

Hardware, not emulation

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.

Now

Drivers that are not in the kernel

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.

Architecture

Three seams hold the whole thing together

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 /dev/pci, a driver stops needing to be in the kernel at all. Everything else is arrangement.

userlandtoybox, mksh, zsh, musl, and the first-party programs in cmd/
userspace driversvgpud — a driver that is an ordinary process
schinitPID 1 — job manifests, supervision, reincarnation
syscall tableLinux numbering, one .inc file — the ABI seam
kernel servicesVFS, sockets and SCM_RIGHTS, signals, mmap, poll, procfs
subsystemsSchFS, lwIP, virtual terminals, graphics, the buffer cache
device modelclass vtables and bus binding — the driver seam
/dev/pciregisters, DMA and interrupts to a process — the eviction seam
driversxHCI, e1000, virtio-gpu, VBE, IDE, PS/2
SchIFT corescheduler, paging, traps, IRQ, SMP, DMA
bootthe UEFI loader in boot/uefi

uname -s reports SchIFT — the kernel — the way every Unix reports its kernel rather than its distribution.

What it has

Built and running, and honest about the rest

Every card marked runs is in the tree and works today. The tags are not aspirational; the things that are not finished say so.

runs

SchFS

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.

runs

SMP x86-64 kernel

Four-level paging, multiple cores, preemptive scheduling, a calibrated LAPIC timer, real signal delivery and job control. 205 syscalls on Linux numbering.

runs

Networking

An e1000 driver under lwIP, with sockets for AF_INET, AF_UNIX and AF_NETLINK. DHCP brings the interface up at boot; ping reaches the outside world.

runs

UEFI boot

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.

runs

USB

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.

runs

Virtual terminals

Six of them, each with its own cell grid, scrollback and foreground job — switched with AltGr+F1…F6, 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.

runs

schinit

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.

runs

A real userland

musl and toybox, both unmodified, plus mksh as /bin/sh. fastfetch builds from upstream source with an empty patches/ directory — when it could not detect something, the answer was to give the kernel what it was looking for.

runs

Drivers outside the kernel

/dev/pci hands a process a device: its registers by mmap, physically contiguous memory it can point the device at, and interrupts as something to read(). The virtio-gpu driver already lives out there. How that works →

in progress

Schcom

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.

designed

SchAPI

The native programming interface — specified, not built, and deliberately so. It should be designed from the compositor's experience rather than before it.

Hybrid

A driver can leave the kernel

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.

It needs

Its registers

mmap on /dev/pci, 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 /dev/mem with extra steps.

It needs

Memory the device can reach

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.

It needs

To be told when to look

Interrupts arrive by read(), so they compose with poll. 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.

It needs

A descriptor to arrive

sendmsg with SCM_RIGHTS. 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.

tty1 vgpud
$ vgpud
vgpud: virtio-gpu at 00:03.0, currently held by virtio-gpu
virtio-gpu: released to userspace; screen is the VBE framebuffer
vgpud: claimed
vgpud: display is 1280x800
vgpud: scanout is ours, framebuffer at physical 0x3eceb000
vgpud: drew a test pattern from userspace

No syscall in that transcript knows it is talking about a GPU. 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.

Numbers

The size of it

46,961lines in kernel/ and include/
207syscalls, Linux numbering
128uberblock ring slots
0patches carried against fastfetch
1driver already running outside the kernel
Guru, the SchOS crash-screen icon

When it stops

PSOGM

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.

This purple is #4B1D9A, 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.

Reading

Where to look

The documentation is written to outlive the work. Anything finished is described in architecture/; anything intended lives in plan/; and the bugs worth remembering are kept in history/, in full, including the wrong turns.