RTOS
Tasks, scheduling, mutexes, semaphores and queues — how a real-time operating system keeps dozens of things happening on time, on hardware with no room for a full OS.
Why an RTOS?
An RTOS is an operating system built for applications where correctness depends not only on the right answer, but on getting that answer within a required time limit. A single monolithic loop can't guarantee that — if one function runs long, everything behind it in the loop simply waits, no matter how urgent it is.
The common misconception "Real-time" does not mean "very fast." It means the system's response is bounded by a deadline. A 1 ms response against a 100 ms deadline is real-time; a 10 ms response against a 5 ms deadline has failed, no matter how quick 10 ms sounds in isolation.
Types of Real-Time Systems
Why the distinction matters It shapes engineering priorities. A hard real-time deadline miss is treated as a system failure and demands worst-case guarantees; a soft real-time miss is a quality trade-off. Knowing which category a requirement falls into changes how much margin, testing and formal analysis it needs.
RTOS vs Other Approaches
RTOS vs bare-metal Bare-metal means the developer manually sequences everything in code. It's fine for small, simple systems — a sensor, a GPIO controller, a small bootloader. Once you need CAN + SPI + UART + multiple periodic tasks + diagnostics + UI running concurrently, an RTOS's scheduler earns its overhead.
RTOS vs AUTOSAR OS "RTOS" is the broad category — FreeRTOS, Zephyr, QNX, VxWorks, ThreadX are all implementations. AUTOSAR OS is an automotive standard within AUTOSAR Classic, built on the OSEK/VDX model, with its own vocabulary: Tasks, Events, Alarms, Resources, Counters, Schedule Tables. Don't equate "RTOS" with any one specific product.
RTOS Components
Everything in an RTOS falls into a small number of building blocks that show up again and again through the rest of this page.
Scheduler, task priorities, preemption Tasks, context switching, interrupts/ISRs Semaphores, mutexes, event flags
Queues and message passing between tasks Software timers, the system tick, delays
Tasks & Task States
A task is an independent unit of execution the RTOS manages — its own function, its own stack, a priority, a current state and a saved execution context.
A typical periodic task Blocked, specifically A task is BLOCKED when it's waiting for something it can't yet have — a CAN message, a semaphore, queue data, a timer. It stays out of the scheduler's consideration entirely until that event occurs, at which point it becomes READY again.
The Scheduler
The scheduler's one job: decide which READY task runs next. The core mental model — the highest-priority task that is currently READY gets the CPU.
Priority direction is implementation-specific Higher priority usually means "more urgent, gets the CPU first" — but whether a bigger number or a smaller number represents "higher priority" depends entirely on the specific RTOS. State this as a caveat rather than assuming a universal convention.
A subtlety worth remembering Even the highest-priority task in the system won't run if it's currently BLOCKED. "Highest priority" only competes among tasks that are actually READY — a blocked Task A with priority 5 loses the CPU to a READY Task B with priority 4.
Preemptive Scheduling
In a preemptive RTOS, a running lower-priority task can be interrupted the instant a higher-priority task becomes READY — it doesn't have to wait for the running task to finish or yield voluntarily.
Why this matters for real-time behavior Without preemption, an urgent task could sit READY for as long as a long-running lower-priority task takes to finish — an unbounded, unpredictable wait. Preemption is what lets an RTOS bound that wait and make deadlines achievable.
Context Switching
When the CPU moves from one task to another, the RTOS must preserve exactly where the outgoing task was — program counter, stack pointer, CPU registers, status flags — so it can resume later as if nothing happened.
Context switching consumes real CPU time. More context switches doesn't automatically mean a better design — excessive switching (e.g. from overly fine-grained tasks or unnecessary preemption) can itself eat into the timing budget it's meant to protect.
Interrupts and ISRs
Keep the ISR short The task then does the real work: Wait_For_CAN_Event() → Process_CAN_Message(). This keeps interrupt latency low and predictable.
Semaphores & Mutexes
Two states — available / taken — typically used to signal that an event occurred. Classic pattern: an ISR gives the semaphore, a task waiting on it wakes up.
Represents N available instances of a resource or event. Tasks take from the count; releasing a resource increments it back — useful for a pool of identical resources.
Mutual exclusion — protects a shared resource so only one task accesses it at a time. Unlike a plain semaphore, a mutex generally has ownership: the task that locked it is the one expected to unlock it, which is what makes priority inheritance possible (see Priority Inversion).
Mutex vs semaphore — don't conflate them Purpose — Mutual exclusion — Synchronization / resource counting
Ownership — Yes — the locker unlocks it — Generally no ownership concept
Can be signaled by another context? — Not the intended use — Yes — e.g. an ISR gives it
Priority inheritance — Often supported — Not inherently applicable
They can look similar internally, but "a mutex is just a binary semaphore" glosses over exactly the ownership semantics that matter.
Race Conditions & Deadlock
Race condition — the classic counter++ example counter++ looks atomic in source code but is really read-modify-write — three separate steps that can interleave between tasks.
The code between lock and unlock is the critical section — only one task should be inside it at a time.
Priority Inversion
Low-priority task L holds a mutex that high-priority task H needs. So far that's normal — H simply waits. The problem appears when a medium-priority task M becomes ready: M has no interest in the mutex at all, but because M outranks L, M preempts L repeatedly — and L can never finish its critical section to release the mutex. H ends up waiting on M, despite having higher priority than M.
Priority inheritance — the fix While H is blocked waiting on L's mutex, L temporarily inherits H's priority. Now M can no longer preempt L, so L runs uninterrupted, finishes its critical section, releases the mutex, and its priority drops back to normal — at which point H immediately runs. The unbounded wait becomes a bounded one.
Inter-Task Communication
A queue moves structured data between tasks (or from an ISR to a task) without either side needing to know the other's internal timing.
A set of bits a task can wait on — e.g. bit 0 = CAN received, bit 1 = SPI complete, bit 2 = ADC complete. Depending on the RTOS's API, a task can wait for any one of several events (OR) or require all of them together (AND) before proceeding.
Ticks, Delays & Timers
A periodic timer interrupt (e.g. every 1 ms) that drives delays, timeouts and scheduling decisions. Some modern RTOSs support tickless operation to save power when nothing needs to happen.
Delay vs busy-waiting Timeout handling, periodic operations, debouncing and communication-loss detection are common uses — e.g. start a 100 ms timer on a CAN message, reset it on each new message, and treat expiry as "communication lost."
Determinism, Latency & Jitter
Predictable timing within known bounds. A task consistently taking 5 ms is deterministic. A task ranging unpredictably from 2 ms to 100 ms is not — and that unpredictability is what makes strict real-time control difficult to guarantee, regardless of average-case speed.
Interrupt latency — event to ISR execution Scheduling latency — task becoming READY to actually running
Response time — event to the required system response Variation in timing — a task meant to run every 10 ms actually running at 0, 11, 19, 32, 39 ms has significant jitter. Control systems in particular are sensitive to it.
Stacks & Memory
Each task typically owns its own stack, holding local variables, call/return information and its saved context. Overrunning that allocation can corrupt adjacent memory and cause unpredictable behavior — many RTOSs offer stack-overflow detection for exactly this reason.
Static vs dynamic allocation Static (compile-time) allocation is predictable, has no runtime failure mode and no fragmentation — preferred in safety-critical systems. Dynamic allocation (malloc()) is more flexible but introduces fragmentation risk, possible allocation failure, and less deterministic timing.
RTOS Architecture
The core that manages tasks, scheduling, context switching, synchronization, inter-task communication, timers and interrupt-related mechanisms — and, depending on the RTOS, memory management.
Automotive Example
A representative ECU: CAN reception is event-driven, vehicle speed updates every 10 ms, diagnostics run every 100 ms, and a health monitor runs every second.
A watchdog timer resets the MCU if the software stops behaving correctly — the application must periodically "kick" it to prove it's alive. A robust design doesn't let one healthy, low-stakes task blindly kick the watchdog while a critical task is actually stuck — a supervisor should confirm that the tasks that matter are genuinely making progress before the watchdog gets serviced.