Embedded

RTOS — Real-Time Operating System

A superloop works fine until a device has to do several genuinely independent, timing-sensitive things at once. An RTOS is what steps in at that point — not for extra features, but to guarantee that the things which absolutely must happen on time actually do, no matter what else the device is doing.

Quick reference
What it is
A scheduler-based kernel that runs multiple independent tasks, always giving the CPU to the highest-priority task that's ready to run.
Mental model
Not "an OS" in the desktop sense — a referee that instantly hands the CPU to whichever task matters most right now, every time that changes.
Key numbers
Each task has its own stack and priority; a higher-priority task preempts a lower one immediately, not at the next convenient moment.
Reach for it when
Multiple independent, timing-sensitive activities need to run concurrently and a superloop has become unmanageable.

Why not just a bigger superloop?

A bare-metal "superloop" (one big while(1) calling each subsystem in turn) works well for simple devices, but it has one structural weakness: everything runs at the same implicit priority, in whatever order it was written. As soon as one device has several genuinely independent timing requirements — a motor control loop that needs a hard 1 ms deadline, a UI that can tolerate 100 ms, a background upload that can wait entirely — a superloop forces the programmer to manually interleave all of it by hand. An RTOS takes that interleaving decision out of application code entirely and puts it in a scheduler that makes it correctly, every time, based on priority.

Tasks and task states

A task (sometimes "thread") is an independent unit of execution with its own stack, its own saved register context, and its own priority — from the programmer's point of view, each task can be written as if it had the CPU entirely to itself.

A task moves between Ready, Running, Blocked, and Suspended states as the scheduler and application code act on it Running Ready Blocked Suspended scheduled ⇄ preempted waiting on a semaphore/queue/delay event occurs / timeout suspend ⇄ resume
A task can also be suspended directly from Running in most RTOS implementations — simplified here to keep the diagram readable.

The scheduler: preemptive priority scheduling

The dominant RTOS scheduling model is simple to state: the highest-priority task that's ready to run, runs — always, immediately. If a low-priority task is running and a higher-priority task becomes ready (a semaphore it was waiting on is given, a timer expires), the scheduler preempts the running task on the spot, not at some future convenient checkpoint.

A low-priority task running, immediately preempted when a high-priority task becomes ready, then resuming once the high-priority task finishes Task A (low priority) running resumes running Task B (high priority) preempts A B becomes ready B finishes time →
The instant Task B becomes ready, it preempts Task A — Task A resumes exactly where it left off once B finishes, with no data lost, as long as A's state was protected correctly.

Equal-priority tasks are typically time-sliced (round-robin) so none of them starves the others — but between different priorities, it's never a fair split, by design.

Semaphores, mutexes, and queues

Semaphore
A counting signal used to notify another task an event happened — the standard way an interrupt hands work off to a task: the ISR gives a semaphore and returns immediately, a task blocks waiting to take it, and the real work happens in task context instead of inside the ISR.
Mutex
Mutual exclusion — protects a shared resource (a peripheral, a data structure) so only one task touches it at a time. Unlike a semaphore, a mutex has an owner and is specifically for protecting a resource, not general signaling.
Queue
A FIFO buffer for passing data between tasks (or from an ISR to a task) safely, without either side needing to know exactly when the other will read or write it.

Priority inversion

A classic RTOS bug class: a high-priority task blocks waiting for a mutex currently held by a low-priority task. If a medium-priority task then preempts the low-priority one (which has every right to, by priority rules), the high-priority task ends up waiting indefinitely — blocked not by the low-priority task directly, but by a medium-priority task that has nothing to do with the resource at all. The standard fix is priority inheritance: while holding a mutex a higher-priority task is waiting on, the low-priority task temporarily runs at the higher task's priority, so it can't be preempted by anything in between and finishes (and releases the mutex) as fast as possible.

Stacks, and why sizing them matters

Every task has its own stack, sized when the task is created — too small and a deep call chain (or interrupt nesting on top of it) silently corrupts adjacent memory, often manifesting as a bug that looks completely unrelated to the actual stack overflow. Most RTOSes provide stack high-water-mark checking specifically because guessing the right size up front is genuinely hard, and getting it wrong fails silently rather than loudly.

Bare-metal vs. RTOS vs. Linux

Bare-metalRTOSLinux
Scheduling goalWhatever the programmer wrote, in orderDeterministic — highest priority ready task always runsFair — good average throughput, not deadline guarantees
Memory footprintMinimal — no kernel at allSmall — kilobytes, not megabytesLarge — needs an MMU, typically megabytes
DeterminismFully deterministic (you control everything)Deterministic by designNot deterministic — general-purpose, best-effort
Typical useVery simple, single-purpose devicesMotor control, industrial, medical, automotiveGateways, anything needing a full OS/networking/filesystem stack

Real-world applications

Gotchas

Field notes