Preemptive RTOS · C11 · GPL-3.0

The Wise Lord
of Small Machines

One public header. Every feature a compile-time switch that removes code, RAM and API surface. Not one dynamic allocation inside the kernel. What a build costs is settled before it runs.

Cortex-M0 → M85  ·  RV32 Hazard3  ·  dual-core SMP

self-test · stm32h503 · 250 MHz on silicon
context switch, task to task3341.34 µs
mutex lock + unlock3521.41 µs
timer start + stop3191.28 µs
critical section, enter + exit430.17 µs
os_tick_get100.04 µs
RESULT: 394 passed, 0 failed. Every enabled feature.
Ports today M0M0+M3 M4M7M23 M33M55M85 RV32 / Hazard3

What you get

Decided at compile time, visible in the map file.

Nothing you switch off costs you a byte, and nothing the kernel needs is hidden from you.

Every feature is a switch

OS_CONFIG_<FEATURE>_ENABLE removes the code, the RAM and the API surface, not just the runtime behaviour. One config file, owned by you, and no kernel file you are ever meant to open.

/* your os_config.h */
#define OS_CONFIG_MUTEX_ENABLE  1U
#define OS_CONFIG_MSG_ENABLE    1U
#define OS_CONFIG_ALLOC_ENABLE  0U  /* heap gone */

O(1) scheduling

One FIFO ready list per priority plus a 32-bit bitmap. The next task is a count-leading-zeros and a list head, whatever the task count.

30application priorities

Nothing allocates

Task blocks, ready lists, timer objects, the log ring and the optional heap are all static arrays. The kernel never calls malloc.

0dynamic allocations

It proves itself on your board

A built-in self-test exercises every enabled feature over printf and finishes with a cycle-accurate benchmark table. It needs no application code and no board support beyond a working printf. Flash it, read the console, then start writing firmware.

The whole integration contract

One exception vector, PendSV on Cortex-M and the machine software interrupt on RISC-V, plus one call from your application. No HAL, no CMSIS dependency, no linker-script edit, and SVC stays entirely yours.

/* the entire application side */
os_tick_handler();  // from any timer ISR

os_init();
os_start();     // never returns

Features

Everything the kernel does, and the line that removes it.

The right-hand column is the whole argument: turn one off and its code, its RAM and its API surface leave the build together.

Tasks and the scheduler

always in

Preemptive and priority-based: 32 levels - 30 for the application, one reserved for the idle task and one for the kernel's service tasks - with one FIFO ready list per priority and round-robin between equals over a slice you set.

Critical sections & scheduler lock

always in

Two preemption barriers that are not interchangeable. One masks interrupts, the other stops only the scheduler.

Delays and time

always in

Blocking delays that yield, and cycle-counted microsecond waits that do not.

Mutexes

OS_CONFIG_MUTEX_ENABLE

Priority inheritance always on, plus deadlock detection in development builds.

Counting semaphores

OS_CONFIG_SEM_ENABLE

Initial and maximum count, ISR-safe to give, with the usual timeout convention.

Queues

OS_CONFIG_QUEUE_ENABLE

Fixed-size items between tasks or from an ISR. Static storage, your own array, or a run-time geometry from the heap. A full queue refuses by default, or drops its oldest item so the newest reading always gets through.

Message buffers

OS_CONFIG_MSG_ENABLE

Whole messages whose length varies, out of one byte budget they share. Each receive returns exactly one, whole and in order.

Event groups

OS_CONFIG_EVENT_ENABLE

Wait on a set of bits, for all or any of them, clearing what matched on the way out.

Task notifications

OS_CONFIG_NOTIFY_ENABLE

A single-value mailbox inside every task's own control block. The lightest signal the kernel has.

Timers and deferred calls

OS_CONFIG_TIMER_ENABLE

One-shot or periodic, with callbacks on a service task rather than in the tick interrupt, so they may use kernel APIs.

Atomics

OS_CONFIG_ATOMIC_ENABLE

A full 32-bit operation set, lock-free where the instruction set allows it and a critical section where it does not.

Kernel heap

OS_CONFIG_ALLOC_ENABLE

A coalescing first-fit allocator over a static array. Nothing is taken from the linker heap and the kernel never calls malloc.

Stack checks and diagnostics

OS_CONFIG_STACK_CHECK_ENABLE

Overflow detection on every switch away, worst-case stack headroom, and idle-versus-busy CPU sampling.

Logging and assertions

OS_CONFIG_LOG_ENABLE

Log calls format into a ring buffer that a service task drains, so a logging task is never blocked on the transport.

Multi-core (SMP)

OS_CONFIG_CORE_COUNT

Per-task core affinity over shared ready lists. Verified on silicon on three dual-core targets, Arm and RISC-V.

TrustZone

OS_CONFIG_TRUSTZONE

Secure, non-secure or disabled per build on ARMv8-M. Compiles and its callbacks are wired, but it has not yet run on a part with the Security Extension.

Run on silicon

Six boards,
two instruction sets.

Every row below is a real part that has run the full suite to completion , including the dedicated cross-core stress section on the dual-core ones.

BoardCoresSelf-test
Raspberry Pi Pico 22 × Cortex-M33passed
Raspberry Pi Pico 22 × Hazard3 RV32passed
Raspberry Pi Pico2 × Cortex-M0+passed
NUCLEO-H743ZICortex-M7passed
NUCLEO-H503RBCortex-M33passed
NUCLEO-G431RBCortex-M4passed

How it is put together

Three layers, three questions.

A change lands in exactly one of them. That is why M0 through M85 is covered by three shared port files, and why RISC-V was a new port rather than a kernel change.

kernel/

The portable core: scheduler, sync and IPC, timers, memory, log. Plain C11 with no CPU knowledge anywhere in it. Never changes with the target.

arch/

Everything a CPU actually decides: the context switch, the tick, the critical-section mask, the atomics, the low-power hooks. Changes with the instruction set.

soc/

Optional per-silicon glue: a non-CMSIS vector name, inter-core signalling, hardware spinlocks. Leave it out and the kernel builds exactly as it always has.

Getting started

One command.

It reads your board, picks the matching SoC package, copies the files you own and wires up the build. It prints the exact diff first and asks before it writes anything, and running it twice is free.

Read the install guide
$ curl -fsSL .../install_rpi_online.py | python3 - $ curl -fsSL .../install_stm32_online.py | python3 -

Python 3.8+ and nothing else. Offline twins read a local copy and import no networking module at all.