Memory
Motor OS uses 4 KiB pages, eager mappings by default, and guarded lazy stacks. Instead of an OOM killer it has admission control: a memory-growing operation is refused before it starts if it would push the machine below a safety floor, and a shared "memory pressure" flag lets sys-io shed load before it runs out.
Pages and mappings
- Ordinary userspace memory is mapped in 4 KiB pages. 2 MiB pages exist for the I/O manager's device memory only.
- Mappings are eager by default. A process can ask for a lazy heap segment, which is backed on first touch. Stacks are always lazy and guarded, so a stack overflow faults instead of corrupting a neighbor.
- Shared memory between two processes is created by URL and is the basis of the I/O channels; a spawning process also maps pages into a child's address space directly while loading it.
- Writable and executable is never granted together for one mapping.
- A per-process memory limit exists in the kernel but is not exposed yet and is unlimited by default.
The heap allocator in every process is frusa, a small no-std allocator
written for Motor OS that can grow and give memory back on demand.
Why admission control
Allocating N pages of data also costs page descriptors, page tables, and occasionally a new slab for kernel bookkeeping, about 2–2.5% on top of the data in measurements. Before admission control the kernel's low-memory check counted only the requested data pages and was separate from the allocation, so concurrent requests could all pass it, and one unprivileged process could empty the physical page pool and panic the kernel. Admission control is a stability mechanism, not a fairness policy: it does not pick a victim, does not promise that every lazy page will get backing, and does not attribute shared kernel objects to processes.
Floors
Two compile-time low-water marks over free small pages divide memory into zones:
available above USER_FLOOR (256 pages, 1 MiB):
any process may start a memory-growing operation
SYS_IO_FLOOR (128 pages, 512 KiB) < available <= USER_FLOOR:
only sys-io may
available <= SYS_IO_FLOOR:
nobody may; what is left is for kernel work already in flight
The floors are constants, not configuration: an operator must not be able to configure the machine back into physical exhaustion. If measurements ever show they are too small, the floors are raised rather than a new allocation class added.
How an operation is admitted
Every userspace operation that grows memory (eager, lazy, shared, and contiguous mappings, page faults on lazy memory, stacks, address-space, process, and thread creation, kernel objects, IPC) is charged a conservative worst case, and a reservation for that charge is published in a global counter before the operation runs:
charge = data_pages + ceil((data_pages + descriptors) / 32) + 64
admit iff (available - reservations) - charge >= floor
The reservation is held until the operation completes, which double-counts pages the operation has already allocated; that can refuse a request slightly early but can never admit too much. Fixed charges: a process 256 pages, a thread 64, a kernel object or IPC 16. A remote operation (sys-io loading a program into a new process) is charged against the target process's class, so a privileged loader does not widen an ordinary process's allowance.
What a refusal means:
- A syscall returns
E_OUT_OF_MEMORYbefore anything happens; there are no side effects, and the caller can back off. - A refused page fault on lazy memory cannot return an error to the faulting instruction, so the faulting thread is killed, which kills its process. This is the accepted cost of having no OOM killer: the victim is whichever process faults while availability is below its floor.
Admission costs nothing measurable: in a release A/B test, eager 16-page allocate-and-free and warm lazy faults were within noise of the pre-admission build, and boot time was unchanged.
Pressure mode
The floors alone cannot keep sys-io alive, because sys-io's heap grows through the
same allocator and a failed allocation aborts the process. Pressure mode stops sys-io's
demand before the kernel has to refuse it. The kernel maintains a
memory_pressure flag in a read-only page mapped into every process, with
hysteresis so a single allocation or free cannot flip service on and off:
raise when free-for-admission <= 512 pages (2 MiB)
clear when free-for-admission >= 768 pages (3 MiB)
Any process can read the flag with one memory load. While it is up:
- the runtime refuses to spawn new processes; the parent gets a recoverable
E_OUT_OF_MEMORYbefore any work is done; - sys-io refuses new TCP listeners, outbound connects, UDP binds, and ICMP echo;
- sys-io refuses every filesystem request except releasing a lock (reads and stats too, because they walk the block cache and allocate on a miss), and frees the pages the refused request had donated;
- sys-io drops new client connections at accept, for both the filesystem and the network service: a new client is exactly the load being shed;
- listener pools stop refilling; a recovery task re-arms them after the flag clears.
Nothing that must run during an episode may allocate; the refusal paths are allocation-free by construction. Established TCP connections keep working, because serving them allocates nothing. The flag clears from the page-free path as well, so it drops when the process that caused the pressure exits even if nobody else allocates.
Observability
Kernel metrics: mem.admission_refused_user,
mem.admission_refused_sys_io, mem.admission_reserved_pages,
mem.small_pages_low_water, mem.phys_small_pages_low_water, and
the two floor gauges. sys-io metrics: net.pressure_active,
net.pressure_entries, net.pressure_refused,
net.pressure_refused_clients, net.pressure_deferred_replenish,
fs.pressure_refused, and fs.pressure_refused_clients. A single
syscall (F_QUERY_ADMISSION_STATS) reports availability, reservations, floors,
and watermarks in one call. See Logs and diagnostics for
how to read metrics.
Deliberately not built
- An OOM killer, or any recovery class that may allocate the pool to zero.
- Exact per-process attribution of page tables and shared kernel objects.
- A configurable kernel reserve.
- Retrying refused allocations. An actual allocator failure is always a defect in a charge or in a bounded-work assumption, and production code must not paper over it with a retry or a longer timeout.
The full argument, including the measurements that validated the floors, is in
docs/oom-handling.md in the repository.