Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ToyOS

ToyOS is a general-purpose operating system written from scratch in Rust: a desktop and development system its authors use every day and can change quickly, held to a production bar of correct, efficient and minimal.

This manual is the intended design: what the system must deliver and how it works or is meant to work. It does not track progress; where the system does not yet match a page, the difference is staged work in the project’s issues. It names no source files and quotes no code, so it cannot drift when code moves. A change to the design or a requirement updates its page in the same change.

Overview is the shortest way in.

Targets

  • Modern x86-64 (2020 and later) and AArch64, as equal targets.
  • UEFI only: no BIOS, no 32-bit code, no backwards compatibility.
  • Real machines are first-class; QEMU is supported.

The north star

ToyOS rebuilds itself on its own hardware and reproduces, byte for byte, what the host built.

Purpose and scope

ToyOS is a Rust-first desktop and development OS for its authors and their friends: responsive under load, efficient with resources, secure through explicit authority, and easy to understand and change. Technical early adopters are the audience. There is no formal launch trial or external audit, but correctness, security and performance still need evidence.

Release one

  • Develop ToyOS on ToyOS: edit, review, build, test and debug, including rebuilding the Rust and C/C++ toolchain. A downloaded compiler seed and a second physical test machine are acceptable. Native terminal tools are enough, and development works offline once sources and dependencies are prepared.
  • AI-assisted coding uses ordinary applications and permissions; paid cloud APIs are acceptable. The coding agent is chosen last, and none may require a closed application without a maintainable native port.
  • Browsing and media come from Chromium, without DRM. The reference is YouTube at a smooth 1080p60 while a full build keeps making progress.
  • Bluetooth playback, with AirPods Pro 3 as the reference headset. Headset calls and vendor ecosystem features are not implied.
  • Passkeys on websites, with USB security keys and credentials stored on the laptop. Transfer and cloud synchronisation can wait.
  • Native programs: ordinary Rust programs, and C/C++ ports through libc. SQLite is in scope; PostgreSQL and Nginx are later targets whose needs constrain the architecture now. There is no compatibility layer for another OS’s binaries.

Performance

  • Build throughput is at least Linux’s on matched hardware and work.
  • Applications open as fast as on Linux, launch to usable state, cold and warm, with at most imperceptible differences.
  • Hot paths are optimised even when today’s workloads hide their cost. Simplicity is the default; a sizeable measured speedup can justify extra complexity. Meeting a ratio is no reason to keep avoidable overhead.
  • Battery life is broadly comparable with Linux for agreed everyday workloads.

Reproducibility and change

Published OS contents are reproducible from pinned inputs, excluding signatures and machine-specific installation data. Fast incremental rebuilds and targeted restarts suffice; a session restart or reboot is acceptable when needed.

Deferred

Hibernation, snapshots, compression, multi-drive pooling and mirroring, independent per-user encryption, Linux binaries, hosted virtual machines, native containers, additional package catalogues, browser calls, DRM, VPNs, captive portals, GPU rendering and video acceleration, translations, screen readers, adjustable text size, built-in file search, general desktop automation and transparent app recovery after a desktop failure. Pooling, mirroring and containers remain architecturally possible without being built now.

The desktop and its hardware

Machines

  • Release hardware is the two bench laptops: a ThinkPad T14 Gen 2 (Intel Core i5-1135G7) and a Lenovo 82A2 (AMD Ryzen 7 4800U).
  • Every device fitted to them works once the applicable packages are installed.
  • AArch64 is a development target, not a physical release gate.

Displays

  • The display engine is release-one scope on both laptops: the internal panel and external outputs, modesetting, hot-plug, EDID, and power management.
  • Several outputs, extended or mirrored, each with its own mode and refresh rate, including high refresh and variable refresh where the display offers them.
  • The firmware’s boot display and an unaccelerated virtual GPU remain supported.
  • Rendering is on the CPU. Video and render acceleration are not in release one; GPU rendering comes later through display and Vulkan drivers, not a CPU implementation of Vulkan.

Power

  • Ordinary whole-machine sleep and configurable lid-close behaviour.
  • Visible, overridable keep-awake requests from apps.
  • Idle CPU sleep is a separate mechanism (Scheduling and power).

The desktop

  • It is part of ToyOS, shipped in the image, never an installable package: desktop shell, settings, file manager and terminal.
  • Graphical resource inspection and app-stop controls stay usable under supported resource pressure.
  • Look: a taskbar holding pinned and running apps together, one start menu with search, clear window chrome, snapping to an edge or half the screen. Minimal and discoverable; no glass or blur, since rendering is on the CPU.
  • Window management: floating and tiling.
  • Toolkit: ToyOS’s own desktop software uses iced, on plain upstream iced, and in release one iced is the one toolkit the platform supports. ToyOS writes its own desktop environment rather than porting another.
  • Language: English and keyboard navigation at first. Language, keyboard layout, regional formats and time zone are independent choices; compact data is bundled, larger extras are downloaded.

Apps in a session

  • Programs may run without windows.
  • An app can be launched to outlive the shell that started it while staying in the login session. Ordinary commands keep helper semantics; independent launch is explicit.
  • Starting an app at sign-in needs a remembered approval.
  • Users choose default apps for files and links.
  • The file manager deletes into a recoverable Trash; programmatic deletion is unchanged.
  • Read/write USB exchange with Windows and macOS through a common format, with exFAT as the candidate subject to implementation and licensing.

More than one user

  • Switching users keeps earlier sessions running.
  • Signing out ends that session’s work; blocked or unsaved work offers cancel or an explicit forced sign-out.
  • Only the active session controls the screen, keyboard, camera and microphone. An inactive session’s capture is suspended while its computation continues, and its playback is paused or muted by default until the user returns.

Installation and updates

Installing

Installation media boot to the desktop, and a desktop entry starts a graphical installer. Installation and first-account setup work offline. A dedicated drive is enough; nothing erases unrelated storage without authorisation. The installer leaves every partition it did not create untouched and adds one boot entry; on a shared EFI system partition it writes only its own directory, never another OS’s, so that OS keeps booting through the firmware’s menu.

Documented firmware key enrolment is acceptable; after it, boot is verified. Recovery from a broken installation may initially be manual.

The image

The image is small: the desktop, settings, file manager, terminal, installer, package tools, and the drivers essential for booting, installing and networking on the release machines. It bundles no editor, SSH, browser, coding agent, development toolchain or microcode payload; those are downloaded when wanted. Compatible driver and microcode packages are recommended, and installing one is not a grant of device authority.

Packages

  • A package is a program, whole: self-contained, fixed at build time, with its own libraries. There are no library packages, no dependency solver and no install, update or uninstall scripts. A fix in what a package carries is a rebuild of that package.
  • An installed version is immutable. It is unpacked privately and committed in one step; nothing edits a committed package.
  • App setup and private-data migration happen when the app runs. A failed migration stops with a clear error and keeps the remaining data for repair.
  • Sources: one ToyOS catalogue, verified by a signed repository whose root is pinned in the image, plus manually installed package files. An unsigned package needs explicit approval and shows an unverified publisher. A native unpackaged program may run; approvals it receives last one run.
  • Who installs: a user approves installing for themselves; installing for every user and changing the system need system authority.
  • Removal: quit the app first. Its saved permissions are forgotten, the user chooses to keep or delete its private data, and documents held elsewhere are untouched.

Updates

Verified updates are prepared automatically. An OS update activates at a voluntary reboot, which the user may postpone; an app update activates while the app is stopped, with a per-app pause. There is one stable OS release. A failed download or staging leaves the existing installation intact. Snapshots of app updates and a built-in backup product are not provided; backups belong to separately installed applications.

How the machine boots an update and refuses an image it should not run is Boot and updates.

Access, privacy and data

These are the user-visible rules. The mechanisms behind them are Processes and authority and Sessions and permissions.

Permissions

  • Programs ask for a right at run time, when they need it, and the answer, approval or denial, can be remembered. There is no up-front permission list.
  • Choosing a file grants that file, not its folder. Remembered access does not follow renames. Writable access to a document includes replacing it safely, scoped to that file.
  • Network use can prompt on first use. Incoming and outgoing access are separate per-app choices, local connections included. Listeners and refused attempts are visible; there are no per-port approvals. Without an approval interface a request fails promptly; explicit commands can grant beforehand.
  • A program may pass on a capability it holds without a new prompt, never with wider rights. Delegated access shows where it came from. Denying an app directly does not cancel access another program delegated to it.
  • Removing a permission affects future grants only; stopping the app is a separate action.
  • A verified update keeps an app’s decisions; an unverified replacement does not inherit them.
  • Clipboard contents reach an app when the user pastes; reading in the background needs approval. Screen capture is chosen per session and ends when sharing stops. Camera and microphone have separate remembered approvals and visible use. Notifications need a remembered approval.

Storage and encryption

  • bcachefs is the native filesystem. Other modern formats are designed for, with ext4 and btrfs as the architectural cases, and built when needed.
  • Personal storage is shared and encrypted. Any regular user can unlock it after a reboot through their own enrolled method; OS permissions separate users. Contents and file names are protected; sizes and timestamps visible on the raw disk are acceptable.
  • Secure passwordless login is preferred where the hardware supports it. Passwords are allowed; a passwordless account needs a recovery route. A stored recovery key is offered; exporting it needs separate system authority. There is no automatic administrator escrow, so losing every recovery method can mean losing data.
  • The screen lock protects a session while its work continues. Storage stays unlocked through sleep and sign-out until shutdown or restart. This is not protection against every physical attack on powered memory.
  • Apps can keep their own tokens in built-in local credential storage; a chosen third-party password manager does not face competing prompts.
  • Apps and users share free space, with a reserve for the OS. The system warns early and shows what is growing; it never deletes caches by itself.
  • Writes are buffered, with explicit durable saving. Native file names are case-sensitive. Crash recovery and the format’s own integrity protections are required.

Diagnostics

Diagnostics stay on the machine until explicitly exported. Reports are small by default; memory dumps need an opt-in. Local recovery tools work without AI.

Overview

  applications          editor, browser, games, tools ...
  ------------------------------------------------------------------
  SDK and runtimes      Rust std, the native SDK, libc   (in each process)
  ------------------------------------------------------------------
  session owners        one per login session: launch, lifetime, sign-out
  desktop               windows, input, trusted interaction
  resource services     files, disks, network, audio, display, memory, logs
  drivers               one process per device function, behind the IOMMU
  supervisor            starts every program, holds the system's authority
  ==================================================================
  kernel                threads, memory protection, capabilities, IPC,
                        interrupt routing, DMA isolation
  loader                every firmware call, before the handover

Who does what

ComponentResponsibility
KernelScheduling, memory protection, IPC, capabilities, interrupt routing, DMA isolation
SupervisorStarts system services and session owners from the system manifest; holds and narrows the system’s authority
Session ownerOne login session’s process lifetime and independent app launch
DesktopWindows, input, trusted interaction and system controls
Permission serviceSaved decisions, runtime consent and authorising fresh grants
AuthenticationVerifying users, unlocking storage, protected credentials and passkeys
Package managementVerifying and installing immutable versions; app identity
Resource servicesFiles, disks, network, audio, input, logs and the firmware’s ACPI events, and enforcing access to them
Memory serviceReclaim policy, pressure reports and encrypted swap
DiagnosticsLocal inspection, failure evidence and authorised recovery
DriversOperating a device, its firmware, queues and device-specific recovery
SDK and runtimesTyped native interfaces; ordinary Rust behaviour; C compatibility
ApplicationsWorkflows and private state under granted authority

A responsibility is not necessarily its own process. Filesystem formats and names, service lookup, network protocols, audio mixing, login and consent all stay out of the kernel.

Rules every boundary follows

  • Userland first. The kernel owns only what userland cannot. It creates no thread except each CPU’s idle loop. Kernel work is bounded, runs on the thread or interrupt that caused it, and is charged to it. Long-running work that no one owns belongs in a userland service.
  • Authority is explicit. A process receives capabilities only through authorised creation or transfer. Names, paths and process ids grant nothing. Rights can be narrowed and never widened by a holder.
  • A server never blocks on a client. Accepting a connection and receiving its first message are separate events; a message is taken whole without waiting; a reply that cannot be sent at once drops that client by name. A client that stalls harms only itself.
  • Bulk data travels in shared memory, with authority and lifetime still explicit. Routine data access stays off central control services’ hot paths.
  • Core machinery is ToyOS’s own. The scheduler, cryptography and allocators are written for ToyOS and held to security, correctness and measured performance evidence. This is not a ban on dependencies or a demand for novel algorithms.
  • Recovery has a bounded path. Reclaim, writeback, device I/O and service recovery never depend on the memory or the service being recovered.

Trust

   trusted                         isolated
   -------------------------       ------------------------------------
   CPU, microcode                  every program, including system ones
   firmware: UEFI, AML, SMM        users and sessions
   the signed image, with its      the network, file contents
   pinned toolchain and crates     devices: DMA only into granted memory

ToyOS trusts only what it cannot avoid trusting:

  • the CPU and chipset vendor, with its signed microcode;
  • the platform firmware, which already controls the whole machine;
  • the signed OS image, with its pinned toolchain and crates.

Everything else is isolated, including external and hot-plugged devices, whose DMA stays behind the IOMMU.

Privileges beyond a device claim

  • A privilege beyond an ordinary device claim, such as a reset line or a firmware register window, is granted only by the signed image’s system manifest, to a program that image ships.
  • A package installed later never receives one, whoever signed it.

Machines without an IOMMU

  • The same driver runs with and without an IOMMU; there is never a second driver.
  • Where the hardware genuinely has no isolation, any driver the signed image ships may claim its device with its ordinary claim, and no other driver may. The machine states plainly that it runs without device isolation.
  • External DMA-capable devices always need isolation.
  • Unknown isolation, unsupported isolation hardware and a failed setup are errors, not evidence that isolation is absent. There is no machine-specific exception image.

Device firmware

  • The IOMMU does not stop a driver reflashing its own device’s firmware.
  • Device firmware is loaded by its owning driver through its IOMMU domain. CPU microcode loading is privileged kernel work. Neither payload need be embedded in the kernel binary.

Checks inside the trusted base

  • A check there is a bug fence: kept where it is cheap and catches ToyOS’s own mistakes, such as a write landing in RAM, the IOMMU, an interrupt controller or another driver’s device.
  • It never withholds a feature for lack of a vendor document, and ToyOS does not reimplement firmware to second-guess it.

Kernel

The kernel is small, fast and safe, and does what userland cannot:

  • schedules threads and accounts their CPU time and memory to jobs;
  • protects memory and supplies generic paging and sharing mechanisms;
  • holds kernel objects and the handles that name them, and moves handles between processes;
  • routes interrupts to the processes that wait for them;
  • confines each device’s DMA to the memory granted to it, through the IOMMU;
  • reads the firmware’s static tables for CPUs, interrupt controllers, timers and IOMMUs, and enumerates the device functions it hands out as claims;
  • loads CPU microcode and applies the CPU’s control registers;
  • keeps its own log record ring and its panic console.

It holds no filesystem, no storage, input, network, audio or USB driver, no network protocol, no audio mixing, no login and no naming policy, and parses nothing one program writes for another. The firmware’s AML is interpreted by a userland ACPI service, which tells the kernel the little it needs, such as how to power off.

Interrupts post, threads wait

   device --irq--> handler: acknowledge, post the event, return
                                   |
                                   v
              driver thread (userland) wakes, does the work
  • The handler posts to whatever the driver waits on; the driver’s own thread does the work.
  • No device wait, spin or poll happens under a spinlock or inside a scheduling pass.
  • Interrupts are masked no longer than a register access needs.

One way to wait

Every object that can become ready reports readiness the same way, and a thread waits for any mix of them through one mechanism. A wait never holds a lock on the object it waits for.

One declaration of CPU state

The CPU’s control registers come from one declaration, applied and asserted on every CPU.

Failure

  • A broken kernel invariant stops the machine with a report that survives the reset (Logs and diagnostics).
  • A device that DMAs outside its grant is recorded and stopped, never a crash.

Programs reach the kernel through its system-call ABI (The program interface).

Processes and authority

   process A                         process B
   +-----------------+               +-----------------+
   | 3 -> pipe  (R)  |   transfer    | 7 -> pipe  (R)  |
   | 4 -> conn (RWT) | ------------> |                 |
   +-----------------+   rights      +-----------------+
                         unchanged or narrowed, never widened

Handles are the only authority

  • A process holds a table of handles; each names one kernel object and carries a set of rights.
  • A handle number means something only in its own table. A number copied from another process, or counted up from zero, names that process’s own slot or nothing.
  • Every system call states the right it needs on each handle it takes.
  • Nothing else grants authority: process and thread ids are identities, object ids for diagnostics reach nothing, and a service name means something only inside a namespace someone handed you.

Rights only narrow

  • A holder can duplicate a handle with the same or fewer rights, if the handle allows duplication, never with more.
  • A move carries the rights unchanged.
  • Some handles, such as a device claim, cannot be duplicated, so passing one on means giving it away.

Kernel objects

  • A closed set of kinds, among them pipes, connections, service ports, namespaces, shared memory, paged memory supplied by a userland pager, events, inboxes for asynchronous requests, processes, jobs, device claims and the system capability.
  • An object crosses a process boundary in exactly two ways: in a message over a connection, or endowed to a new child.
  • Threads are not objects; nothing outside a process names its threads until a debugger or another real caller needs it.

Communication

  • A connection carries bounded, whole messages, each holding bytes and handles together, so a message and the authority it passes arrive atomically.
  • Notifications coalesce, and polling for readiness does not consume one; signalling and consuming each need their own right.
  • Pipes remain for real byte streams; bulk data stays in shared memory.
  • What a message means belongs to the userland protocol that defines it.

Creating a process

  • A child starts by spawn, never by fork, with an empty handle table.
  • It receives exactly one set of endowments, moved out of the parent, all or nothing (see The program interface).
  • The parent receives a handle to the child’s process, with which it waits for the child’s end and can stop it.
  • Without any handle a process can act only on itself and read machine facts: run its threads, manage its address space, read its own record, the clock, the CPU count, randomness and overall memory, and create new pipes, ports and shared memory, which confer nothing over what already exists.

The process tree

  • Every process has one parent. A parent’s end, whether exit, crash or kill, ends its whole subtree at once; quitting gracefully is the parent’s job.
  • A child’s end is readiness on its process handle, and says how: an exit code, a kill, or a fault of a named class.
  • There are no process groups, POSIX sessions, controlling terminals, signals, wait-any or calls addressed by process id. The process handle is the only control.

The system capability

  • One per machine, held by the supervisor.
  • Every privileged right, such as claiming a device, real-time scheduling, reading the log, reading counters, listing processes or powering off, is a narrowed copy the supervisor hands to the programs its manifest grants.

Namespaces

  • A namespace is an immutable kernel object mapping service names to connectors; a narrower one is a new object.
  • A process connects to a service only by looking its name up in a namespace it holds. There is no global registry.

Files

Files are reached through directory capabilities, never a global path space (Files and views).

Sessions and permissions

   supervisor ─┬─ system services and drivers
               └─ session owner (one per login session; its launcher)
                     ├─ desktop or remote shell, and their helpers
                     └─ apps launched to outlive their launcher

Session owners

  • One per login session, started by the supervisor. It owns its desktop or terminal tree and the apps detached from it, and is the session’s launcher.
  • There is no separate session or launcher daemon.
  • A session owner’s failure may end its session, never another session or a system service.

Helpers and launches

  • A helper is an ordinary spawn. It inherits only what its parent passes it and lives in its parent’s subtree and job. Running an installed app’s code as a helper does not acquire that app’s saved permissions.
  • A launch asks the session owner to start an app under its own identity, with its own saved decisions and grants, outliving the launcher.
  • A program may launch only the programs its manifest row lists; the caller’s row is the approval. A direct spawn never inherits a launcher.
  • Detaching gives a longer life, never more access. Jobs, identity and lifetime are distinct.

Users

  • A user is a home tree and a login row. There is no password file, no ambient “current user” and no root user.
  • No numeric id is authority. libc maps the authenticated identity to user and group ids for programs that ask.
  • A login authority, such as the SSH server or a local greeter, holds the right to ask for a session. The session’s view is the user’s home, the system read-only and the session’s own temporary directory.
  • A program in one user’s session cannot name another user’s files.
  • The local desktop is a login session like any other: the local greeter asks for it, and it has the same per-session view and isolation. SSH authentication starts the user’s session, whose shell inherits no host key and no SSH service authority.

Ending a session

  • Sign-out asks the session’s apps to finish, allows cancel or force, then ends the session’s subtree.
  • A dropped connection or closed terminal sends its tree a hang-up, reports what remains and for how long, and keeps waiting.
  • No timer ends a process. Only an explicit force does: a sign-out’s force, scoped to the user’s own sessions, or the system controls’ stop. A second quit request ends a process that ignored the first only when a person or an explicit force sends it.

Permissions

  • Authentication, authorisation and releasing an encryption key are separate duties. Consent approves a bounded action; an administrator’s approval does not give every child of that administrator system power. No shell or AI agent is special.
  • The permission service keeps saved decisions, asks for consent through the trusted interface, and authorises fresh grants. It is off the path of routine access: a granted capability works without asking it again. Granted capabilities survive its crash, and its parent restarts it.
  • A file grant is an unforgeable badge on a connection, written by whoever minted the connection; the kernel vouches for what was granted, never for who connects.
  • Session-sensitive capture and sharing are enforced by the resource service that owns them, and transfers cannot bypass the session’s controls. Ending a capture is different from changing the policy for future grants.
  • What any installed app may be granted is bounded by the image’s ceiling, which names rights, never apps, so installing an app never edits the image.
  • One authenticated app identity ties together installation, independent launch, saved permissions and private credentials. An immutable version identifies the code being run. A display name or an executable path is never authority.

Files and views

A view per program

Every process has its own view of the file tree, built by its parent. A view is a set of directory capabilities, each a connection to a file server that serves one subtree with read-only or read-write access. Paths keep working: a path names something only if it lies inside the caller’s view, and the client library routes it to the directory capability with the longest matching prefix. An empty view names nothing.

  • A child’s view is at most its parent’s. Nothing widens a view after spawn. A launch is the exception, and the launched program receives its own row’s view (see Sessions and permissions).
  • Resolution cannot leave the view. The file server resolves every path relative to the directory the connection is bound to, by node identity, so .. at that root, a symbolic link, a rename racing a lookup or a held directory moved elsewhere never reach outside it. An absolute link is resolved again in the client’s own view, so it reaches nothing the client does not hold.
  • Sharing is granted, never reached. A file outside a program’s view reaches it only through the file picker, which hands over that one file.
  • No special cases. The boot partition is in the updater’s view and nobody else’s; login data is in the login authority’s view alone; /apps is writable only in the installer service’s view, and every other program sees it read-only.

The working directory is the client’s: a spawn states the child’s starting directory, which the child resolves in its own view.

The tree

/system          read-only and signed: programs, libraries, defaults, shared
                 data (fonts, icons, themes, sounds, backgrounds, licences, docs)
/config          machine settings; a file here overrides the system default
/state/<program> a system service's private persistent data
/apps/<name>     installed packages, one directory each, no layout required
/home/<user>     Desktop Documents Downloads Music Pictures Videos Fonts,
                 and Apps/<name>/ for each app's private data
/boot            boot loader, kernel and boot parameters; the updater's alone
/log             logs
/media/<label>   foreign volumes
/tmp             one per session; each service and app has its own
  • There are no drive letters and no /usr, /var, /opt, /dev, /proc or /sys: devices and processes are capabilities, not files.
  • There are no dotfiles or hidden folders in the layout. A third-party program that insists on ~/.something may create one only inside its own app folder.
  • Names are English and never translated.
  • A path means the same file in every view; /tmp is the one exception. A view hides names and never renames them.
  • A program learns a location from environment variables its starter sets, such as HOME. A location grants nothing; the view decides what is reachable. An app’s HOME is its private folder in the user’s home, inside which config, data, cache and state are the visible Config, Data, Cache and State.
  • An app finds its own files next to its executable.
  • Fonts: the system’s under /system, a user’s in their Fonts folder. sans-serif and system-ui are Open Sans, monospace is JetBrains Mono, and serif stays empty until a licence-clean face is chosen.
  • The time zone is a machine setting in /config. Keyboard layout and language are machine defaults there, which a user may override.

Files

  • An open file is identified by its node, not its path, and lookup is relative to a directory. Deleting a name keeps existing handles and mappings working until their last release; recreating the name makes a distinct file.
  • Each format owns its on-disk structures, allocation and crash recovery. Common operations, authority, file lifetimes, asynchronous I/O and mapping integration are shared in userland. An unsupported feature fails clearly; there is no forced emulation and no format policy in the kernel.
  • Ordinary I/O failure returns an error. A file server’s crash invalidates that server’s open files, which then answer that they are gone; a fresh open reaches the restarted server. Unsaved changes may be lost; data already confirmed durable stays durable.
  • Per-session temporary storage is an ordinary file server holding its contents in RAM.

Storage

   path        client library: views, longest prefix
     |
   filesystem  one file server per role: SYSTEM DATA LOG BOOT MEMORY
     |
   volume      a role names a filesystem by UUID; its members may span disks
     |
   block       one driver per controller; the block service grants partitions
     |
   device      NVMe, USB mass storage, ...

All four layers run in userland.

Block

  • One driver process per disk controller reads its disk’s GPT, the one partition scheme, and offers the partitions to the block service.
  • The block service checks a client’s grant, opens the partition and hands the session to the driver that serves it; data never passes through the block service.
  • A partition has one holder at a time, every request is bounded to it before the device sees it, and a flush answers for its own writer’s writes.
  • Caching belongs to file servers, never to the block layer.
  • Partition editing and storage initialisation are userland operations shared by disk tools and the installer. Editing the table, reading payloads and formatting are distinct authorities; the storage owner enforces stable target identity and excludes conflicting use. Being logged in or running a signed utility grants none of them.

Volume

  • A partition’s type GUID says what it is, so roles come from the disk and no configuration names a device.
  • A role names a filesystem by UUID; its members are partitions on any number of disks. The file server mounts the set, mounts it degraded or refuses; a member vanishing is never silent.
  • Two filesystems claiming one unique role are refused, not guessed.
  • A blank volume joins by a designation stamp; a used one is never reformatted.
PartitionFilesystemServesWritable
ESPFAT32, as firmware requires/bootonly by the updater
ROOTbcachefs image, built and signed per release/systemno
DATAbcachefs, formatted on first boot/apps /config /home /stateyes
LOGits own partition/logyes
  • /tmp lives in RAM.
  • On a development stick LOG is FAT32, so another computer can read a boot’s log; whether LOG folds into DATA on an installed machine is not decided.
  • Development machines keep ESP and ROOT on a stick and DATA internal; an installed machine carries all of them on one disk, through the same code.

Filesystems

  • Each role is served by its own file server, linking its format’s whole implementation, so a crash loses that role alone and no filesystem parser runs in the kernel.
  • /system is immutable. The installer and updater write it offline and leave it clean; a server that finds it needing journal replay refuses it as a broken installation.
  • Replicas, tiering, snapshots, checksums, scrubbing, compression, encryption and per-user or per-app subvolumes are properties of the bcachefs format, not a block-layer subsystem. LVM- and md-style aggregation is refused: it cannot tell metadata from data, place a file’s replicas on different disks or snapshot.
  • File pages are described in Memory.

Foreign filesystems

  • A foreign volume appears under /media/<label>.
  • A common format for USB exchange with Windows and macOS is required.
  • NTFS, when it comes, is read-only and refuses a volume Windows left hibernated or dirty; BitLocker volumes are reported unreadable.
  • ext4 and btrfs are the architectural cases for further formats.

Memory

Pages and mappings

  • Process memory uses 4 KiB pages; 2 MiB pages remain where they pay: large mappings, DMA grants, the IOMMU and the kernel’s own mappings.
  • A large virtual mapping never needs contiguous physical memory.
  • Reservation, commitment and population are separate. Reserving address space costs nothing, committing backing is strict, and pages are populated lazily.
  • A successful anonymous commitment covers every later ordinary fault on it. When capacity runs out, new commitments are refused; nothing is killed to make room.
  • Each thread’s stack capacity is committed before the thread starts, populated lazily and guarded, with caller-chosen sizes and ordinary creation failure.
  • Programs may generate executable code without a prompt; write and execute stay separate and executable pages are shared safely.
  • Shared memory may be installed at a requested address in a child before it starts; conflicts are refused. There is no address-space cloning.

Who owns what

   kernel           faults, page tables, accounting, reverse records,
                    paging mechanisms; no policy, no filesystem, no crypto
   memory service   reclaim policy, pressure figures, swap placement,
                    sealing and opening swapped pages
   file servers     file identity, contents, access and writeback
  • One authoritative page cache serves both file I/O and file mappings. A file’s pages are a paged memory object its file server supplies and its holders read without a request; the server’s own cache holds only volume structures.
  • Mapped files may be larger than RAM, partially mapped, and coherently shared and written; their length is never committed in RAM.
  • Clean pages are reclaimed; dirty pages are written back first, without losing concurrent writes. Dirty data growth is bounded, and writeback starts ahead of pressure.
  • Clean executable pages may be dropped and reloaded from the same executable version. An update or uninstall never substitutes different contents for a running program.

Accounting

  • Every allocation has one owner, the job it is charged to. Sharing keeps the charge with its owner, and the record outlives the creator until the backing is released; a copy is a new charge.
  • Each mapper pays for its own page tables and mapping metadata.
  • Shared immutable file backing is counted once, in a shared account.
  • Services own their private state and bound what clients can make them grow; client-owned bulk buffers are preferred.
  • Apps share memory without fixed per-app quotas. A reserve protects system inspection and stop controls.
  • A program may lock a modest, bounded amount of memory; more needs explicit resource authority, and helpers do not multiply an allowance.

Swap

Anonymous memory may leave RAM for an encrypted, authenticated swap partition, preallocated on internal storage, with keys that live for one boot.

  • Commitment counts usable RAM plus reserved swap, never hoped-for compression or growth. Swap adds capacity, not responsiveness.
  • Swapped memory behaves like RAM: it may make a load, a store or a copying system call wait on storage. The wait advances with storage progress and is reported when overdue; a timeout never turns it into an error, and a non-blocking descriptor does not cover the caller’s own buffer.
  • A page is lost only when a running memory service establishes the loss: a failed read after the driver’s recovery, a failed authentication or a device that is gone.
    • Lost anonymous memory ends its process at the next touch, with a diagnostic; contents are never invented.
    • A buffer inside a file mapping answers an I/O error instead.
    • A futex wait on a swapped or lost word returns as a spurious wake.
  • With the memory service stopped or past its restart budget, nothing fails: its faults wait, reported as such, until an authorised restart. The user may stop any waiting program.
  • If backing shrinks below commitment, new commitments are refused and a fault past the remaining backing waits, reported with the deficit.
  • Swapped memory survives a crash and authorised restart of the memory service. Swap does not survive a reboot.
  • Executable and read-only image pages never go to swap.
  • One program’s swapped memory delays only that program; no service waits on one client’s memory in a path that serves others.

Pressure

  • Reclaim balances access history and refault feedback, and reclaims and writes back in advance.
  • The paging, storage and recovery paths it depends on stay resident and prepared.
  • Pressure is reported and the user keeps control. Pressure, slowness or a hang never justifies ending, suspending or killing a workload.

Allocators

The heap allocators share machinery where useful, with separate kernel and userland policies, size classes, bounded local caches, kernel metadata kept apart from data and efficient cross-thread frees. Unused backing returns under pressure, and ordinary Rust and C allocation contracts hold without Rust depending on libc.

Scheduling and power

Jobs share the CPU

   CPU n:  EEVDF over job groups ──> EEVDF over that group's threads
           weight(group) = weight(job) × runnable here / runnable in job
  • CPU time is shared by jobs, not threads. A process’s and its helpers’ threads share one job, so helpers buy no extra share; authorised launchers create independent jobs.
  • Nothing reserves CPU time: no program is promised a budget, and nothing is admitted against one.
  • Every job has equal weight, across users too. Window focus changes responsiveness, not sustained entitlement.
  • Each CPU runs EEVDF over the job groups it holds, and again among each group’s threads. A thread that sleeps and runs briefly gets the next slice without more total time, so an editor answers a key while a compiler keeps its share of the hour.
  • Placement balances group weight across CPUs and steals work for idle CPUs. There is no cross-CPU service accounting; aggregate fairness is as close as placement makes it.

Real time

  • Audio runs at bounded real-time priority, independent of window focus.
  • Each CPU has an aggregate real-time allowance, replenished each interval, that protects ordinary progress. Once it is exhausted, that CPU’s real-time workers run only when no ordinary work wants the CPU, until it refills.
  • New streams are accepted best-effort; genuine overload may cause dropouts, and a late client contributes silence without holding back the others.

Power

  • Useful work runs promptly; no power target holds it back.
  • Idle cores sleep within their wake-latency constraints. Placement and idle-state selection account for wake cost and known upcoming work.
  • Ordinary load never starves audio.
  • The kernel declares the CPU’s performance and power bounds as part of its one CPU declaration and reports them back; the CPU chooses frequencies within them.

Devices and drivers

Every driver is a userland process. The kernel hands out only what the IOMMU can tell apart, PCI functions, ISA port and interrupt rows and platform devices, and confines each to the memory granted to it.

   kernel: enumerate functions ──> claim (no copy, only a move)
                                     │
   supervisor: match the manifest ───┘──> spawn the driver with its claim
                                                │
   driver process ── DMA only into its grants ──┘
       │
       └─ serves a client protocol: block, input, network, audio, display

Claims

  • A device claim cannot be duplicated, so giving it to a driver is the only way to pass it on, and the giver provably no longer holds it.
  • Each call that drives a device presents the claim, and the kernel checks the device class as well as the handle.
  • A driver reaches only the memory its grants map: the IOMMU domain is the gate, not the descriptor the driver writes. A device address outside a grant is a recorded DMA fault and the device is stopped.
  • A device is reused only once the old holder’s DMA is contained and cleaned up. A contained device that failed may stay unavailable until reboot.

The supervisor brokers devices

  • The system manifest says which program drives which kind of device.
  • The supervisor is the persistent waiter: a function present at boot and one arriving later are the same event, answered by the same spawn. The device set is a level-triggered snapshot it compares, not an event stream.
  • One driver process per kernel-handed function: a disk driver per NVMe controller, a NIC driver per function, a USB service per USB controller.
  • One shared service per client-facing function: file servers, the network stack, the owner of input, the compositor, the sound server.
  • A driver reaches its consumer as a client of the consumer’s port; it never publishes a name for others to look up.
  • Discovery happens in userland, and device-provided names confer no authority.

Removal and return

  • Removing a device invalidates its claim, which then answers that it is gone.
  • A returning device is a fresh arrival with a new handle.
  • Device identity, such as a serial number or a partition GUID, belongs to the driver and the file server, never the kernel.

Buses

  • A bus service owns everything behind its controller. The USB service keeps device classes such as HID and mass storage inside its process and speaks the client protocols directly: input to the owner of input, disks to the block service. The supervisor never sees a USB device, and short USB glitches stay inside the USB service.
  • PCIe hot-plug uses the native mechanism only.

Distinct devices, distinct owners

  • Distinct devices have distinct owners, even when they run one driver’s code.
  • A driver exposes device functions to separately authorised services rather than probing for roles.
  • A transport is trusted with the data it carries without inheriting every consumer’s rights.

Displays

  • One driver process per GPU function brings its outputs to the compositor, the way an audio driver brings outputs to the sound server.
  • Intel’s display driver is ToyOS’s own Rust, written from Intel’s published documentation. AMD’s adapts AMD’s maintained display code, by the owner’s ruling for that device.
  • The display driver registers a panic surface with the kernel.

Input

  • Input translation belongs in userland, per surface.
  • Keyboard and pointer sources from every bus reach the owner of input, which merges them.
  • A dead kernel takes no input; there is no emergency input path.

Boot and updates

  firmware
     └─> loader       makes every UEFI call; reads the chosen slot into
         │            memory and checks it against the signed header
         └─> kernel   keeps those images read-only; starts one program
             └─> supervisor
                 ├─> SYSTEM file server, serving ROOT from memory
                 └─> everything else, from the system manifest

The loader

  • It does only what must happen before the handover.
  • Every UEFI call ToyOS makes is the loader’s, before it exits boot services, through its own UEFI bindings; the kernel calls no firmware service.
  • It loads the slot’s kernel, boot parameters, the two boot programs and ROOT through the firmware’s block I/O, so no storage driver is needed to boot.
  • It hands the kernel a typed description of what it loaded and which slot it chose.
  • The hardware clock keeps UTC.

Signed images

  • An image’s header names its security version and the SHA-256 of each part, by role and length, and is signed with Ed25519 in the OpenSSH signature format, so standard SSH tools sign and verify it.
  • The signature covers hashes, never bytes.
  • A slot whose image fails verification is refused by name: an unsigned image, another key’s, a wrong byte, or a security version below the floor.

Slots and updates

  • The disk carries two slots, each a boot volume and a ROOT, named by a slot table. An update is written to the slot the machine is not running.
  • Updates fix forward. The loader boots the slot marked to boot next and no other; a slot that is refused or fails is reported, never replaced by the previous one automatically. The remedy is a newer image.
  • Updating is an ordinary program: a signed image on standard input, written to the idle slot and marked to boot next. The signature is its whole authority, and how the bytes arrived does not matter.
  • Updates are pulled from a release server, as content-addressed chunks of which only those the idle slot lacks cross the network.
  • User data lives on DATA, which no slot includes; apps update separately through the package manager.

The verified system

  • /system is the signed ROOT and nothing else; nothing the machine runs is outside an image or a package.
  • UEFI Secure Boot with the owner’s key covers the loader, the loader covers every part of the slot it boots, and a failing link is refused by name.
  • An anti-rollback floor per signing key counts the signed security version, raised only by a release that fixes a security hole, and holds even against someone with the machine in hand.

Services and failure

The supervisor

  • The one program the kernel starts, and the holder of the system capability.
  • It starts every program with what that program’s manifest row grants (The system manifest), and creates every service port before any server runs, so a client’s first connection works whether or not its server has started.
  • It never dies and stays small: its decisions are pure, host-tested logic, and the process only holds handles, spawns and runs its loop.
  • It owns the machine’s orderly stop, derived from the manifest’s dependencies, and replaces a running service’s binary when an authorised caller in a login session asks.

The other services are the components in the Overview. Each is an ordinary process holding only what its row grants.

Failure

   service crashes ──> parent keeps its endpoint ──> restart (bounded attempts)
                                                      │
                     clients: what was durable survives; open state answers "gone"
  • Parents supervise. A crashed service is restarted by its parent within a bounded number of attempts; a feature may be interrupted meanwhile.
  • A service’s identity outlives its process. The parent keeps the endpoint while recovery is owed and gives the replacement only its authorised capabilities.
  • Recovery restores durable state, not sessions. File servers recover what was on disk, not old opens; network sessions may fail; the memory service keeps recoverable swapped contents.
  • Nothing is silently replayed. An operation with an uncertain outcome is reported, and retried only where safe; a write whose acknowledgement was lost is never replayed behind the client’s back.
  • A restart refused for lack of resources stays visibly pending and retries when resources return, without busy-looping. Recovery state is consistent across a crash; memory merely kept alive does not establish that.
  • No restart because time passed. Stalled progress is reported; manual action remains available.
  • A compositor crash may end its session, never unrelated work.

Logs and diagnostics

ToyOS answers what it is doing, what it did and what it is made of from inside itself, with programs it ships. Diagnostics stay on the machine until someone exports them.

The log

  program ──> its own log ring ─┐
  program ──> its own log ring ─┼──> log keeper ──> /log, one file per boot
  kernel  ──> kernel record ring┘          └──> console
  • Every program the supervisor starts or launches writes records into a log ring of its own, which the supervisor names to the log keeper. The name travels beside the ring, so a line’s origin is structure, not a claim the program makes. Writing never waits, allocates or enters the kernel; a full ring becomes a count the log keeper reports.
  • The kernel’s logging responsibility is its own record ring and its console. Userland output never enters the kernel ring. Reading the machine’s log needs a right on the system capability.
  • The log keeper owns the log files. It holds each program to an allowance, keeps every boot’s beginning, and syncs on an alert or an interval. Severity is an ordered ladder.
  • A slow sink drops and counts; it never blocks a producer and fails alone.
  • The kernel waits on the log keeper neither at a panic nor at a stop.

Crashes

A kernel panic halts the other CPUs, paints its report on the panel and seals its tail in a black-box page that survives the reset, which the next boot carries into /log. A machine with no serial port can therefore always be read afterwards. A killed program is reported as its file, offset and build id; one userland naming service turns those into function names using the same lookup the kernel uses for itself.

Inspecting a running machine

  • Inspect: each owner of state, the kernel and the services, answers questions about itself, and one reader renders the answers.
  • Counters: the kernel’s counters are read on demand. Reading them needs a right; the revealing ones, such as power and per-device interrupts, need a stricter one.
  • The diary: per-CPU trace records of scheduling and system calls, always on in the shipped kernel, including every system call slower than a threshold.
  • Process figures: what each process uses, for those holding the right to list processes.

Ordinary programs receive none of these rights by default; administrative tools do.

Applications and portability

Existing Rust just works

  • Rust programs build and run on ToyOS unchanged: ToyOS is a real Rust target with a real standard library.
  • Ecosystem crates that need platform support, such as windowing, presentation, audio and asynchronous I/O, get a proper ToyOS backend in their own upstream projects. No program is asked to use a replacement crate written only for ToyOS.
  • ToyOS is a first-class platform and never pretends to be Linux: a crate that assumes every other OS is Linux gets a native ToyOS branch.
  • ToyOS is a normal target for third-party software: its SDK crates are on crates.io, and its toolchain is a release a developer can install.

The same app everywhere

  • Apps use the same source on ToyOS, Linux under Wayland, macOS and Windows.
  • A program that runs only on ToyOS does a job only ToyOS has, such as driving a ToyOS device or serving a ToyOS protocol.
  • X11 is legacy.

C and C++

C and C++ programs build with the toolchain’s clang against ToyOS’s libc. libc is built on ToyOS’s native interface, never the other way round, and POSIX compatibility lives there, never in the kernel. Where POSIX conflicts with ToyOS’s model, libc implements the focused part programs need and refuses the rest visibly:

  • children start by spawn, as every process does; there is no fork or exec;
  • a child receives its standard streams and exactly the descriptors its spawn names;
  • signals come only from native events: a child’s end, an alarm, and interrupt, hang-up and terminate requests. Any other signal is refused, and no unsupported operation appears to succeed.

Loading programs

The kernel constructs a protected process and maps executable segments. Dynamic linking and library loading are userland work shared by the Rust and C runtimes. A program’s image is read by userland through its own view and handed to the kernel as memory, never named by a path the kernel resolves.

Desktop apps

A window’s pixels reach the compositor through shared memory. The toolkit and rendering are in The desktop and its hardware.

Packages

An installed app is one directory under /apps, immutable once committed, holding everything the app needs. Its view holds its own directory read-only, its private folder in the user’s home read-write, /system read-only, its own /tmp, and whatever the user grants. See Installation and updates.

Building and running

One command

  • From a clean clone, cargo run builds everything and boots the system in QEMU, and cargo test boots whole systems to run the integration suite.
  • The build system is an ordinary Rust program; it names anything missing before it starts.
  • Linux and macOS are supported hosts.

The toolchain is ToyOS’s

  • ToyOS carries its own forks of the Rust compiler and of LLVM, where the ToyOS targets live.
  • One LLVM is both rustc’s code generator and the clang that builds C programs, and its linker links the UEFI loader, the kernel and every program.
  • Nothing installed on the host goes into anything that boots.
  • The first build bootstraps the toolchain once per machine; later builds rebuild only what changed.

Images

Every build produces GPT-partitioned, UEFI-only disk images, ready to write to a USB stick. One kernel and loader serve three boot configurations:

ImagePurpose
The systemThe whole desktop and its services; what QEMU boots
DiagnosticNothing may claim the display, so the kernel’s log and panics stay readable on a machine with no serial port
ConsoleA shell on the raw framebuffer, without the compositor

Development images are signed with a key generated for the checkout; an image for an owner’s machine is signed with the owner’s key.

The program interface

A ToyOS program sees three layers, and an ordinary program uses only the top one.

   Rust std  /  libc            portable programs: unchanged source
   ─────────────────────────
   the native SDK               ToyOS-specific programs: typed handles,
                                services, views, devices, windows
   ─────────────────────────
   the kernel ABI               system calls on handles; unstable

The kernel ABI

Every system call is one of these families:

FamilyWhat it does
Own executionExit, threads, futex waits, sleep, thread names
Own address spaceReserve, commit, map, protect and release memory
HandlesDuplicate with narrower rights, close, move
WaitingWatch any set of objects for readiness, and wait
ProcessesSpawn from an image with endowments; wait for and stop a child
CommunicationPipes, service ports, connections carrying bytes and handles, inboxes
Shared and paged memoryCreate, map and lend memory; supply pages as a pager
DevicesClaim through the system capability, map device memory, grant DMA, wait for interrupts
Machine factsClock, CPU count, randomness, overall memory
PrivilegedListing processes, reading the log and counters, real-time scheduling, power: each a right on the system capability

The ABI changes freely for a cleaner design; programs rebuild against the matching SDK. Its errors are the kernel’s, and are translated at the SDK’s boundary: protocols between programs define their own.

The native SDK

The SDK is the Rust interface to ToyOS: typed handles and rights, endowments a program was started with, namespaces and service connections, framed messages, pollers, shared memory, files through the program’s view, spawning and launching, device claims for drivers, logging, and windows for programs that draw. Each service’s protocol is a library of its own that owns its message types and errors.

Rust’s standard library

std behaves as on any platform: files resolve through the program’s view, threads, thread-locals, unwinding, backtraces and dynamic loading work, networking reaches the network stack and processes start by spawn. A command’s working directory and environment are passed to the child, and the child’s HOME and temporary directory are those its starter set.

libc

libc provides C and C++ programs with standard C and the POSIX subset described in Applications and portability. It is a userland library over the SDK.

How a program starts

   endowments ──> standard streams (descriptors 0, 1, 2 and those named)
              ──> a namespace of services it may connect to
              ──> directory capabilities: its view
              ──> environment: HOME, the starting directory, ...
              ──> anything else its row or its parent gave it

A program holds nothing it was not given. It asks for more only by connecting to a service in its namespace, whose server decides.

The system manifest

The signed image carries one system manifest: the whole statement of what the system runs and what each program holds. The supervisor builds every program’s starting authority from it and from nothing else, and the build refuses a manifest that contradicts itself, such as two programs claiming one device.

   manifest row ──> supervisor ──> spawn with:  namespace (services it receives)
                                                acceptors (services it serves)
                                                view (directories)
                                                device claims
                                                narrowed system rights

What a row grants

  • Services: the ports it serves and the ports it may connect to.
  • Devices: the devices it claims at start, or the kinds of device it drives, one process per matching device.
  • Launch: the programs it may launch, the only way to launch one.
  • Login: whether it is a login authority that may ask for a session.
  • System rights: the narrowed rights it holds on the system capability.

Which programs start at boot is a list without order, since every port exists first. Every installed app receives one shared set of services the manifest names; a package carries no row of its own that could grant it devices, system rights or directories. Only the image grants privileges (Trust).

Commands

The commands that embody a piece of the design. Each is an ordinary program holding only what its manifest row grants; a command that changes the system asks the service that owns the change, which decides.

CommandPurpose
launch <app>Start an app under its own identity and saved decisions, outliving the shell
pkgInstall, list and remove packages: verify, ask, unpack, commit
updateInstall a signed image from standard input into the idle slot
swapReplace a running service’s binary with the one on standard input
logoutList login sessions; sign one out, gracefully or by force

The SSH server ships in the image but does not start by default, since it listens on every interface. It accepts public keys only, and a remote login is a login session like the desktop’s.