The most common RTOS priority mistake is assigning priorities by business importance.
For example:
control task is most important -> highest priority
communication is also important -> next priority
logging is not important -> lowest priority
This is intuitive, but not enough. RTOS priority decides which ready task runs first. It is not the same as business value, and it does not guarantee that a high-priority task finishes first.
The first model is:
priority serves response time, not status ranking
An important task should not necessarily run at high priority all the time. Deadlines, execution time, blocking, and resource dependencies matter more.
Priority Only Applies to Ready Tasks
A high-priority task can preempt lower-priority tasks only when it is ready.
If it is waiting for a queue, semaphore, mutex, I/O, DMA completion, or timeout, it is not in the ready queue. A lower-priority task running at that moment does not violate scheduler rules.
Many “why did the high-priority task not run” issues are blocking issues:
- the queue has no message
- a needed mutex is held by another task
- the notification was lost
- the ISR did not wake the task correctly
- timeout settings are wrong
So response debugging cannot stop at the priority table. Inspect task state and blocking objects.
Deadlines Matter More Than Importance
When assigning priority, ask:
- how soon this task must respond
- how long each run can take
- whether it blocks on slow I/O
- whether it does only minimal work after wakeup
- whether it holds resources needed by other tasks
A task may be business-critical but have a wide response window. Cloud configuration sync is important, but hundreds of milliseconds may not affect device safety.
Another task may look like a low-level data mover but must drain a hardware FIFO quickly or data is lost. Its priority may need to be higher.
Real-time systems care about completing critical actions within deadlines, not about names that sound important.
High-Priority Tasks Must Not Run Forever
In a preemptive RTOS, a high-priority task that stays ready can hold down lower-priority tasks.
That is necessary for critical control paths, but it can also cause starvation:
- logging rarely runs
- storage cannot flush data
- network background work is delayed
- the idle task cannot clean up or enter low power
A high-priority task should usually be short:
wake on event
-> read necessary state
-> handle critical action
-> post follow-up work
-> block again
If it performs heavy computation, printing, flash writes, network waits, or large state machines, the system becomes a high-priority superloop.
Resource Dependencies Create Priority Inversion
A high-priority task waiting for a mutex held by a low-priority task is common in RTOS systems.
Without priority inheritance, medium-priority tasks may keep preempting the low-priority task. The low-priority task cannot release the mutex, so the high-priority task keeps waiting.
This is classic priority inversion:
low-priority task holds lock
high-priority task waits for lock
medium-priority task keeps running
low-priority task cannot release lock
The fix is not raising every priority. Better options include:
- shorten lock hold time
- use mutexes with priority inheritance
- keep high-priority tasks away from slow resources
- serialize access with a resource-owner task
- move slow I/O out of high-priority paths
Priority design must be evaluated with locks, queues, and resource ownership.
ISR Wakeup Also Affects Priority
Many high-priority tasks are woken by ISRs.
The full path is:
hardware interrupt
-> ISR clears interrupt and posts notification
-> high-priority task becomes ready
-> scheduler switches
-> task handles event
If the ISR does not use the right ISR-safe API, or does not trigger the needed scheduling decision, the high-priority task may not run soon enough.
If interrupts are disabled for too long, the high-priority task cannot be woken promptly. Priority cannot preempt code while interrupts are masked.
High-priority response therefore includes task priority, interrupt priority, critical-section length, ISR-to-task notification, and scheduling points.
A Practical Layering
A rough priority structure can look like:
highest: fast hardware or safety response, short execution
higher: real-time control, low-level protocol movement, key state handling
middle: business state machines, communication protocol, data processing
lower: storage, logging, reporting, statistics, maintenance
lowest: idle, cleanup, low-power assistance
This is a starting point, not a template. Real projects adjust it by deadline, blocking paths, and resource dependencies.
More useful rules:
- high-priority tasks do little work and block quickly
- slow I/O stays out of high-priority paths
- lock holders do not do complex work
- ISR-woken tasks need overload policies
- background tasks still need CPU time
- the idle task must not be starved forever
Do Not Tune Only the Numbers
When response is slow, the first instinct is often to raise priority.
That may hide the issue temporarily or create new ones. A better debugging path is:
- is the task ready or blocked
- which object is it blocked on
- is a queue full or empty
- who holds the mutex and for how long
- does the ISR wake the task correctly
- are interrupts disabled too long
- does a high-priority task run without blocking
- are stacks, heap, logs, and asserts healthy
The priority number is only the final expression. The real target is the response path.
Good Priority Design
Good priority design is not an importance ranking. It is a set of explainable response guarantees:
- every high-priority task has a clear deadline
- high-priority tasks run briefly
- slow paths are pushed to background tasks
- shared resource hold time is controlled
- priority inversion has a strategy
- ISR-to-task wakeup paths can be verified
- background and idle tasks still run
RTOS priority design is not about making important code always run first. It is about completing critical actions within required time while keeping the whole system alive.