Kernel

A single arch-neutral scheduler runs on all five targets. It is fixed-priority, preemptive and per-core, implementing the BMP model: every task is bound to one core and never migrates.

A frame-free context switch

The scheduler never sees a register frame. The trap boundary is narrowed to a single function:

onTrap(sp: usize) usize

The arch trap stub pushes all registers onto the interrupted stack, calls onTrap with the interrupted stack pointer, and the scheduler saves that SP on the current task and returns the next task's SP. The stub then pops and returns — iretq on x86, sret on riscv, eret on aarch64, an exception return on Cortex-M.

This one narrowing is what lets the same scheduler run on five targets. The register layout never leaks into neutral code.

Per-core state, pinned tasks

Run-state — the current task, the round-robin cursor, the tick count — is indexed by a dense logical core id. Real hardware can hand out non-contiguous APIC ids, so a topology table maps physical ↔ logical. Each task records the core it belongs to and never moves between cores.

Multi-core bring-up

All five targets bring up their secondary cores.

ArchSecond-core startThen
x86_64INIT-SIPI-SIPI to a trampoline at physical 0x8000real → protected → long mode; up to four cores from the ACPI MADT
ARMv6-M (RP2040)the inter-core SIO FIFO launch handshakea second Cortex-M0+, indexing its per-core state by the core-id register
RV32 (RP2350)the SIO FIFO launch handshakea second Hazard3 core joins the scheduler, verified on real silicon
riscv64SBI HSM hart_start to the M-mode firmwaresecondary harts enter S-mode and join the scheduler
aarch64PSCI CPU_ON to the firmwaresecondary cores enter EL1 and join the scheduler

On x86 the number of cores is discovered, not assumed: a core that never answers is reported as a real failure, and offline cores have their pinned tasks re-homed to live cores before the scheduler goes live.

A new actor spawned at runtime is placed on the least-loaded core — on x86_64, riscv64 and aarch64. The MCU flight builds pin their tasks to cores explicitly.

Memory model

The kernel owns its own page tables. They differ per architecture.

The physical frame allocator is x86's. It manages the largest free RAM region the firmware memory map reports.

riscv64 and aarch64 draw their intermediate page tables from a small fixed pool instead.

On the MMU-less MCU profiles there is no MMU at all: the address space is flat and physical (VA == PA), the MMU module is a no-op stub, and isolation is enforced by PMP or MPU regions instead of page tables.

The VM as the kernel's payload

The kernel's whole reason to exist is to host Lambda C VM instances as scheduled, isolated actors.

Each task is a self-contained VM with its own stack, global area and heap; the scheduler saves and restores the VM's process-wide run-state on every context switch, so a VM frame is fully preemptible with no interrupt-disabled section around it.

The details of that contract are on the Actors page. How each actor is confined to its own memory is on the Isolation page. The hard-real-time primitives the scheduler carries are on the Real-Time page.