In this lesson:
- preemptive priority scheduling, urgent work runs first
- round-robin, time-slicing among equal-priority tasks
- cooperative scheduling, tasks yield voluntarily
- how FreeRTOS combines priority preemption + round-robin
- the tradeoffs: responsiveness, fairness, and starvation
The previous lesson said "run the highest-priority Ready task." This lesson is how the scheduler actually arbitrates.
Three scheduling policies
| Policy | Who runs next | Switch trigger |
|---|---|---|
| Preemptive priority | the highest-priority Ready task, always | a higher-priority task becomes Ready β immediate switch |
| Round-robin | equal-priority tasks take turns | a time-slice (tick) expires |
| Cooperative | the running task, until it yields | the task voluntarily blocks/yields |
Real RTOSes blend these. FreeRTOS's default is preemptive priority with round-robin time-slicing among equal priorities, so priority decides who, and round-robin shares the CPU among ties.
Preemptive priority: responsiveness
The core rule: the highest-priority Ready task always runs, and the instant a higher-priority task becomes Ready it preempts the current one, even mid-function.
priorities: high = sensor(3) low = log(1)
log running βββββββββββ³ preempted! βββββββββββΆ log resumes
β²
ISR makes sensor Ready (data arrived)
sensor: ββruns immediatelyβββThis is what makes an RTOS responsive: an urgent event (via an ISR giving a semaphore, say) gets the CPU within a context switch, no matter what low-priority work was running. The cost is that low-priority tasks can be starved if higher-priority tasks stay Ready (a busy high task β lower tasks never run, the never-block hazard from the last lesson).
Round-robin: fairness among equals
What if two tasks have the same priority and both are Ready? Pure priority can't choose. Round-robin time-slices: each runs for one tick, then yields to the next equal-priority task, cycling fairly.
two tasks at priority 2, time-sliced on each tick:
tick: | A | B | A | B | A | B | ...So in FreeRTOS the two policies stack: priority first (a priority-3 task always beats priority-2), and round-robin only among the equal-priority Ready set at the top. Time-slicing is configurable (configUSE_TIME_SLICING).
Cooperative: simple but fragile
In cooperative scheduling the running task keeps the CPU until it chooses to yield (block or call taskYIELD()); the scheduler never forcibly preempts. (FreeRTOS offers this via configUSE_PREEMPTION = 0.)
- Pro: no preemption means data shared between tasks is implicitly safe between yield points, fewer locks, simpler reasoning.
- Con: one misbehaving task that doesn't yield hangs the whole system, and a high-priority task can't preempt to meet a deadline. Responsiveness depends on every task being well-behaved.
Cooperative scheduling is essentially the disciplined super-loop with extra structure; preemptive is what you want for hard responsiveness.
The tradeoffs
flowchart LR
P[Preemptive priority] -->|+| R1[responsive: urgent preempts instantly]
P -->|β| S1[starvation: low priority can be locked out]
RR[Round-robin] -->|+| F[fair sharing among equals]
RR -->|β| O[switch overhead; not deadline-aware]
C[Cooperative] -->|+| L[simpler, fewer races]
C -->|β| H[one bad task hangs all; no forced preemption]- Preemptive maximizes responsiveness but risks starvation, mitigate with correct priority design (and bounded high-priority work that blocks to wait).
- Round-robin gives fairness among equals but each slice costs a context switch and it isn't deadline-aware (good for non-critical peers, not for ordering by urgency).
- Cooperative is simple and race-light but only as responsive as the worst-behaved task.
Priority assignment is the real lever: give the tightest-deadline work the highest priority, keep high-priority tasks short and blocking, and group truly-equal peers at one priority to share via round-robin.
Gotchas
- Starvation under preemption. A high-priority task that stays Ready (busy-loops or has too much work) locks out everything below it forever. High-priority tasks must block to wait; budget CPU so lower tasks still run.
- Too many same-priority tasks. Piling tasks at one priority turns the system into round-robin and erases your prioritization, and time-slicing adds switch overhead. Use distinct priorities where ordering matters.
- Cooperative = one bad apple hangs all. With preemption off, a task that never yields freezes the system and no deadline can be met. Only use cooperative when you control every task's yield discipline.
- Round-robin isn't deadline-aware. Equal-priority time-slicing is fair, not timely, it won't preferentially run the task with the nearest deadline. For deadline ordering, use priorities (or a deadline-based scheduler if your RTOS supports it).
- Time-slice length vs overhead. A very short tick gives finer round-robin sharing but more context-switch overhead; too long hurts responsiveness among equals. Match the tick rate to your timing needs.
TL;DR
- Schedulers blend three policies: preemptive priority (highest-priority Ready task always runs, preempting instantly), round-robin (equal-priority tasks time-slice fairly), and cooperative (tasks run until they voluntarily yield).
- FreeRTOS defaults to preemptive priority + round-robin among equal priorities: priority decides who, round-robin shares the CPU among ties.
- Preemptive gives responsiveness (urgent events preempt) at the risk of starving low-priority tasks; round-robin gives fairness among equals but isn't deadline-aware; cooperative is simple and race-light but one non-yielding task hangs everything.
- The key design lever is priority assignment: tightest deadline β highest priority, keep high-priority tasks short and blocking, group genuine peers at one priority.
- Watch for starvation (never-blocking high task), too many equal-priority tasks (erases prioritization), and choosing cooperative only when every task's yield behavior is trusted.