Conslabs
Real-Time Systems & RTOS
intermediateMarch 17, 2025 · 3 min read

Task Scheduling & Priorities

How priority-based preemptive scheduling decides which task runs next, what priority inversion is, and how rate-monotonic analysis tells you whether a task set can meet its deadlines.

RTOS concepts introduced preemptive, priority-based scheduling: the highest-priority ready task always runs. The two things that go wrong with this in practice are priority inversion and simply assigning more work than the deadlines allow.

Priority inversion

Priority inversion happens when a high-priority task is blocked waiting on a resource held by a low-priority task — and a medium-priority task, unrelated to either, keeps preempting the low-priority task, indirectly blocking the high-priority one indefinitely.

Low-priority task:    locks mutex M, then gets preempted
Medium-priority task: runs (doesn't need M), keeps preempting the low task
High-priority task:   blocked waiting for M — but M's owner never gets to run

The fix most RTOSes implement is priority inheritance: while a low-priority task holds a mutex a higher-priority task is waiting on, the low-priority task is temporarily boosted to that higher priority — just long enough to finish and release the mutex, restoring forward progress for everyone waiting.

Rate-monotonic scheduling

For periodic tasks (sample a sensor every 10 ms, run a control loop every 5 ms), a common and analyzable policy is rate-monotonic scheduling: assign higher priority to tasks with shorter periods. It's provably optimal among fixed-priority policies — if any fixed-priority assignment can meet all deadlines, rate-monotonic can too.

Whether a given task set is schedulable at all can be checked with a utilization bound:

U = Σ (C_i / T_i)          for each task i, where C_i = worst-case execution time, T_i = period
U ≤ n × (2^(1/n) − 1)       sufficient condition for n tasks

Worked example: two tasks, one needing 2 ms of CPU time every 10 ms and another needing 3 ms every 20 ms:

U = (2/10) + (3/20) = 0.2 + 0.15 = 0.35
bound for n=2: 2 × (2^0.5 − 1) ≈ 0.83
 
0.35 ≤ 0.83 → schedulable

If utilization exceeds the bound, the task set might still happen to work, but it isn't guaranteed — exactly the kind of risk you don't want to discover in the field.

Why this matters in practice

Priority assignment isn't a tuning knob to fix later — get it wrong and a task can miss every deadline even though the CPU is mostly idle, simply because of how priorities interact. Treating scheduling as something to reason about up front, rather than something to debug under pressure, is the difference between firmware that's reliable and firmware that's "mostly fine."

The next sub-lesson, Interrupt-Safe Design, covers the other half of real-time correctness: making sure data shared between interrupt handlers and tasks stays consistent.