Architecture
Motor OS is a microkernel system. The kernel manages memory, threads, scheduling, capabilities, and wait/wake objects. One userspace process, sys-io, owns the devices, the filesystem, and the network stack. A per-process runtime object, rt.vdso, implements the system interface that Rust's standard library calls.
+----------------------------------------------------------------------+
| programs: rush, russhd, httpd, red, rmux, your code ... |
| Rust std -> moto-rt -> rt.vdso (allocator, fs and net clients,|
| spawn, threads, futex, poll, stdio) |
+----------------------------------------------------------------------+
| io_channel (shared memory rings) | bounded IPC
v v
+----------------------------+ +-----------+ +--------------+ +--------+
| sys-io | | sys-init | | dns-resolver | | strobe |
| virtio-blk virtio-net | | sys-tty | +--------------+ +--------+
| Motor FS moto-netstack | +-----------+
+----------------------------+
| syscalls (4 of them)
v
+----------------------------------------------------------------------+
| kernel: address spaces, paging, threads, scheduler, capabilities, |
| wait/wake objects, shared memory, IRQ routing, serial log |
+----------------------------------------------------------------------+
Boot
Under QEMU the VM boots from an MBR boot sector; under Cloud Hypervisor and
Firecracker it boots via PVH. Either way a small loader brings the CPUs up in 64-bit mode
and loads the kernel from the initrd into high memory (hence "himem kernel"), at a fixed
address with no address randomization. The initrd also carries sys-io; the
kernel loads it as the first process, with every capability, and halts the machine if it
ever exits. sys-io initializes the VirtIO devices, mounts the filesystem, and starts
sys-init, which reads /system/cfg/sys-init.cfg and starts
strobe, the configured services, and the console.
The kernel
The kernel is about 18,600 lines of no_std Rust for x86-64, under
src/sys/kernel. It contains no drivers other than the serial port, no
filesystem, no network code, and no program loader. What it does:
- Address spaces and paging. 4 KiB pages for ordinary userspace; eager mappings by default, lazy heap segments on request, always-lazy guarded stacks, shared memory between two processes, and cross-address-space mappings that the loader uses to build a new process. W^X is enforced per mapping.
- Scheduling. In-kernel scheduling is cooperative: the kernel never blocks except in the wait syscall, so it does not need to be preemptible. Userspace is preemptible on a 10 ms timer tick. Round-robin SMP with per-CPU ready queues and one global queue for stealing; no priorities today. An idle CPU other than the boot CPU stops its tick. Two fast paths matter for I/O latency: an interrupt can return straight to the interrupted thread when nothing else is runnable, and a wake can hand the CPU directly to the woken thread.
- Processes and threads as kernel objects, with an immutable capability word per process (see Processes and roles).
- Wait/wake objects. Every kernel object a process holds a handle to can be waited on. A wake is queued on a lock-free list that the scheduler drains, and a missed wake is latched per handle so a waiter cannot lose it. Userspace futexes, pipes, and the I/O channels are built on this.
- Admission control for memory, so that no unprivileged process can exhaust physical memory (see Memory).
- Interrupt routing to userspace: a driver waits on an
irq_waithandle. - Timekeeping, metrics, and debugging: a self-describing metrics catalog, a per-CPU trace ring, and a debug interface that can pause a process and read its threads' registers and memory.
The syscalls
There are four syscalls, each with a small set of operations selected by an argument.
Userspace names kernel objects with a SysHandle; a few handles are
predefined (SELF, PARENT, KERNEL, and so on).
| Syscall | Operations |
|---|---|
SysObj | Create an object from a URL (address_space,
process, shared memory, ipc_pair, irq_wait,
serial_console); look up or connect to an object; drop or duplicate a handle;
query a handle (is it waitable, is the peer connected, the peer's PID and
capabilities). |
SysMem | Map and unmap memory: heap, reservations, shared and cross-address-space mappings, and, for the I/O manager only, MMIO and physically contiguous memory; query memory and admission statistics. |
SysCpu | Wait on up to N handles with an optional deadline; wake a handle or a thread; spawn a thread; exit; kill a child; query per-CPU usage; set thread affinity. |
SysRay | Observability: list processes, describe and read
metrics, write a log record (needs CAP_LOG), and the debugger
interface. |
The Rust definitions are in src/sys/lib/moto-sys. Most programs never see
them: they use Rust's standard library, and the runtime below it makes the calls.
sys-io
sys-io is the I/O manager. It is one process that owns every VirtIO
device, provides the filesystem service, and provides the network service. All of its
work runs as tasks on a single-threaded asynchronous runtime, moto-async, on
one thread pinned to CPU 0, where the device interrupts are routed; there are no locks in
its hot paths. Two small helper threads answer synchronous statistics queries.
- Drivers (
src/sys/lib/virtio-async): modern VirtIO over PCI with MSI-X. virtio-blk uses 4 KiB blocks and batched multi-block requests; virtio-net negotiates checksum offload and TSO on transmit and passes per-frame checksum verdicts up to the stack. Exactly one block device and any number of network devices are expected. - Motor FS, the journaling filesystem, with a 16 MiB block cache, readahead, and a lock manager for advisory locks (see Filesystem).
- moto-netstack, the networking stack, which grew out of smoltcp but has diverged far enough (congestion control, SACK and RACK-TLP loss recovery, timestamps, admission and safety hardening) that it no longer tracks upstream (see Networking).
Programs talk to sys-io over io_channel, a shared-memory channel inspired
by io_uring: a submission ring and a completion ring of 64 entries each, with 64 pages of
buffer space per direction split into four sub-channels, and kernel wait handles for
cross-process wakeups. The wire definitions live in moto-sys-io; the native
client library is moto-io, and rt.vdso uses them on behalf of std. Because
the kernel allows a process at most 1024 wait handles, sys-io serves at most 960
channels, at most 896 per kind (filesystem or network); at four sub-channels per network
channel that is roughly 3,500 concurrent sockets per machine.
If a client misbehaves on its channel, sys-io drops that client. If sys-io itself exits or is killed, the kernel logs its status and halts the VM: sys-io holds live filesystem and network state that cannot be rebuilt safely by a replacement, so a clear stop is preferred over a partial restart.
The services
| Service | Role |
|---|---|
sys-init | Reads /system/cfg/sys-init.cfg: a tty:
line (required) names the console program and its role, strobe: names the logging
service, and svc:CAPS:COMMAND lines start services with an explicit capability
mask. It starts strobe first, then the services, then the console. |
sys-tty | The serial console: a polled 16550 UART, driven as a byte pump.
It starts the console shell (rush) with the environment from /system/cfg/sys-tty.cfg. |
strobe | Logging and the metrics registry. Services send log records
over IPC through moto-log; strobe writes /system/logs/TAG.log and rotates
to .prev. Userspace metric providers register with it so that collectors such as
sysbox stats can find them. |
dns-resolver | A bounded IPC service that resolves names over UDP with TCP fallback; runs with the None role (see DNS resolver). |
russhd | The SSH server: password and public-key authentication, shells, command execution, and SFTP; runs with the Interactive role. |
rt.vdso and moto-rt
Motor OS has no dynamic linking, but it does have one shared component. The runtime,
rt.vdso, is a position-independent ELF object that the spawning process maps
into every new process at a fixed address, together with a one-page table of function
pointers. Rust's standard library on Motor OS is deliberately thin: it calls
moto-rt, a small crate that mostly forwards to that table. The runtime can
therefore be updated with the OS, and already-compiled programs pick up the new version
on their next start. The table is versioned (RT_VERSION, currently 17,
matching moto-rt 0.17.x).
rt.vdso is where most of what a Unix kernel would do in system calls actually happens:
- the heap allocator (frusa, a small no-std allocator written for Motor OS);
- process spawning: the ELF loader, argument and environment passing, capability
assignment, stdio setup, and detached processes (there is no
forkorexec; spawn builds the child address space directly); - threads, thread-local storage, futexes, sleeping, and time;
- the filesystem client (about thirty entry points over io_channel);
- the network client: TCP, UDP, socket options, name resolution through the DNS
service, and an edge-triggered readiness interface (
poll) that mio and Tokio are built on; - stdio, including the pipe relays that connect a child to its parent's terminal or to a file;
- panic output, backtraces, and Ctrl+C delivery.
Programs are statically linked x86-64 ELF executables with no interpreter and no ELF
TLS segment; thread-local storage is provided by the runtime. Anything that needs a
Motor-specific interface, for example a service that wants to know a peer's
capabilities, uses moto-sys or moto-rt directly.
Where things are in the source tree
| Path | Contents |
|---|---|
src/boot | MBR boot sector, second-stage loader, and the kernel loader. |
src/sys/kernel | The kernel. |
src/sys/sys-io | The I/O manager; netstack/ is the networking stack. |
src/sys/sys-init, sys-tty, strobe, dns-resolver | The services. |
src/sys/lib | System libraries: moto-sys (syscalls), moto-rt
and rt.vdso (the runtime), moto-ipc (io_channel, pipes), moto-io and
moto-sys-io (sys-io clients and wire formats), motor-fs and async-fs,
virtio-async, moto-async, moto-dns, moto-log,
moto-stats, moto-mpmc, frusa, moto-rt-cabi (the C ABI
shim under the C library). |
src/sys/tools | sysbox (the commands) and mdbg. |
src/sys/tests | The in-VM test suites. |
src/bin | User programs: rush, rmux, red, kibim, russhd, httpd, httpd-axum, curl, lorry, gears, rnetbench. |
src/imager | The image builder and the image descriptions. |
src/vm_scripts | Scripts to run VMs; copied next to the images. |
src/tests | The host-driven test scripts, including full-test.sh. |
img_files | Static files placed into the images: configuration, scripts, this website. |
docs | Design documents, plans, and recipes. |
Size
Lines of Rust, including tests, as of August 2026: the kernel about 18,600; rt.vdso about 10,700; sys-io about 13,900 plus about 73,900 in the networking stack; Motor FS about 10,300; the VirtIO drivers about 3,400; moto-async about 2,800.