Priority Scheduling — Mars Pathfinder Inversion
- Priority scheduling: always run the highest-priority ready process.
- Lower priority number conventionally means higher priority (like OS nice values).
- Starvation: low-priority processes may never execute — solved by aging.
- Priority scheduling selects the highest-priority ready process to run.
- Starvation occurs when low-priority processes never get CPU — solved by aging.
- Aging gradually increases waiting processes' priority — guarantees eventual execution.
- Linux nice values range -20 (highest) to +19 (lowest) — lower numeric means higher priority.
- Production insight: priority inversion can cause real-time tasks to miss deadlines if a high-priority task waits on a low-priority lock.
- Biggest mistake: confusing numeric priority direction — always verify with
ps -eo pid,pri,nice.
Quick Cheat Sheet: Priority Scheduling Issues
Process stuck and never gets CPU
ps -eo pid,pri,nice,state,cmd --sort=pritop -p <pid>Real-time application fails intermittently
echo 1 > /proc/sys/kernel/sched_schedstats; cat /proc/lock_statstrace -f -e futex,lock <pid>Unexpected system resets
dmesg | tail -50journalctl -k | grep -i 'priority|inversion|hang'Production Incident
Production Debug GuideSymptom-Action Guide
ps -eo pid,pri,nice,pcpu,cmd --sort=pri and look for processes with low nice values (high numbers) consuming no CPU.strace -p <pid> to see blocked syscalls./proc/lock_stat or enable priority inheritance in the scheduler (requires kernel support).Priority scheduling is what your OS uses right now. Linux's nice values (-20 to +19), Windows' priority classes (0-31), Java's Thread.setPriority() — all are implementations of priority scheduling. The starvation problem is real: in early Unix systems before aging was implemented, background processes at low priority could wait hours or days before getting CPU time if the system was heavily loaded.
The solution — aging — is an example of a general algorithm design principle: make greedy choices less greedy over time by adjusting inputs. A low-priority process that has waited long enough becomes a high-priority process. The invariant being maintained is that no process waits forever, even if it regularly loses to higher-priority work.
Priority Scheduling Implementation
The non-preemptive priority scheduler implemented here picks the highest-priority ready process (lower numeric value = higher priority). It uses a min-heap to manage ready processes efficiently. Processes are sorted by arrival time first; then at each tick, all processes that have arrived are pushed onto the heap. The scheduler always runs the process with smallest priority number. This is a classic batch-oriented approach – suitable when tasks are short and infrequent, but dangerous in interactive systems where unpredictability can cause missed deadlines.
import heapq def priority_non_preemptive(processes: list[dict]) -> list[dict]: """ processes: {pid, arrival, burst, priority} Lower priority number = higher priority (1 = highest). """ procs = sorted(processes, key=lambda p: p['arrival']) results = [] time = 0 heap = [] # (priority, arrival, process) — min-heap i = 0 while i < len(procs) or heap: while i < len(procs) and procs[i]['arrival'] <= time: p = procs[i] heapq.heappush(heap, (p['priority'], p['arrival'], p)) i += 1 if not heap: time = procs[i]['arrival'] continue _, _, p = heapq.heappop(heap) start = time ct = start + p['burst'] tat = ct - p['arrival'] wt = tat - p['burst'] time = ct results.append({**p, 'start': start, 'ct': ct, 'tat': tat, 'wt': wt}) return results processes = [ {'pid':'P1','arrival':0,'burst':10,'priority':3}, {'pid':'P2','arrival':1,'burst':1, 'priority':1}, {'pid':'P3','arrival':2,'burst':2, 'priority':4}, {'pid':'P4','arrival':3,'burst':1, 'priority':5}, {'pid':'P5','arrival':4,'burst':5, 'priority':2}, ] results = priority_non_preemptive(processes) for r in sorted(results, key=lambda x: x['pid']): print(f"{r['pid']}: priority={r['priority']}, WT={r['wt']}, TAT={r['tat']}")
P2: priority=1, WT=9, TAT=10
P3: priority=4, WT=17, TAT=19
P4: priority=5, WT=19, TAT=20
P5: priority=2, WT=7, TAT=12
Starvation and Aging
Starvation occurs when low-priority processes wait indefinitely because high-priority ones keep arriving.
Aging solution: increase the priority of a process by 1 every T time units it waits. Eventually every process becomes highest priority and executes. The choice of T is critical – too large and starvation persists, too small and the priority system loses its intended differentiation. A common starting point: T = 5 * average burst time of all processes.
def apply_aging(processes: list[dict], time: int, aging_interval: int = 5) -> None: """Boost priority of waiting processes every aging_interval time units.""" for p in processes: if p.get('waiting', 0) > 0 and p['waiting'] % aging_interval == 0: p['priority'] = max(1, p['priority'] - 1) # lower number = higher priority # Demonstration: process stuck at priority 10, aging every 5 time units print('Priority progression with aging:') priority = 10 for t in range(0, 51, 5): print(f't={t:3}: priority={priority}') priority = max(1, priority - 1)
t= 0: priority=10
t= 5: priority=9
t= 10: priority=8
t= 15: priority=7
t= 20: priority=6
t= 25: priority=5
t= 30: priority=4
t= 35: priority=3
t= 40: priority=2
t= 45: priority=1
t= 50: priority=1
Multilevel Queue Scheduling
Modern OSes use multilevel queues – separate queues for different process types (real-time, interactive, batch), each with its own scheduling algorithm. Processes are permanently assigned to a queue based on their type. Multilevel Feedback Queue (MLFQ) adds mobility: processes move between queues based on behaviour – CPU-bound processes drift to lower priority, I/O-bound stick to higher priority. This is what Linux and Windows implement underneath (with dynamic priority boosts for interactive tasks).
MLFQ prevents starvation by periodically boosting all processes to the top queue (every ~200ms in Linux). This ensures every process gets a time slice eventually.
ps -eo pid,pri,nice,cls to identify unexpected demotions.nice and priority levels to confirm correct scheduling class.Preemptive vs Non-Preemptive Priority Scheduling
In non-preemptive priority scheduling, once a process starts running, it continues until it blocks or finishes – even if a higher-priority process arrives in the meantime. This can cause high-priority tasks to miss deadlines. In preemptive priority scheduling, the running process is immediately interrupted when a higher-priority process becomes ready. Preemptive ensures that the highest-priority ready process always runs, but it adds context-switch overhead and requires careful synchronization for shared resources.
Real-time systems (e.g., VxWorks, RT-Linux) use preemptive priority scheduling. General-purpose OSes use a hybrid: preemptive with time quanta, where priority determines the next process but CPU-bound tasks eventually get demoted.
Priority Inversion – The Mars Pathfinder Bug
In 1997, the Mars Pathfinder rover, running VxWorks, experienced repeated system resets. The root cause was priority inversion: a high-priority data bus task blocked waiting for a mutex held by a low-priority meteorological task. A medium-priority communications task ran continuously, preventing the low-priority task from releasing the mutex. The high-priority task starved, causing a hardware watchdog to reset the system.
The fix was to enable priority inheritance on the mutex: when a high-priority task blocks on a lock, the lock-holding low-priority task temporarily inherits the higher priority, allowing it to run and release the lock quickly. This is now standard practice in real-time operating systems.
- Without inheritance: low-priority holder stays low, gets preempted by medium tasks → high-priority starves.
- With inheritance: low-priority holder temporarily runs at high priority → releases lock fast → returns to original priority.
- This is a simple, local fix that prevents cascading failures in real-time systems.
| Feature | Non-Preemptive | Preemptive | MLFQ |
|---|---|---|---|
| Process can be interrupted | No | Yes | Yes |
| Suitable for real-time | No | Yes | Conservative – real-time queues can be fixed priority |
| Starvation risk | High (without aging) | Medium (without aging) | Low (with periodic boost) |
| Context-switch overhead | Low | Medium-High | Medium |
| Implementation complexity | Low | Medium | High |
🎯 Key Takeaways
- Priority scheduling: always run the highest-priority ready process.
- Lower priority number conventionally means higher priority (like OS nice values).
- Starvation: low-priority processes may never execute — solved by aging.
- Aging: increase priority by 1 every T wait units — guarantees eventual execution.
- MLFQ (Multilevel Feedback Queue) combines priority with dynamic adjustment based on behaviour.
- Priority inversion can crash real-time systems — use inheritance on all shared mutexes.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is starvation in priority scheduling and how does aging solve it?JuniorReveal
- QCompare preemptive vs non-preemptive priority scheduling.Mid-levelReveal
- QWhat is Multilevel Feedback Queue scheduling?SeniorReveal
- QIn Linux, what do negative nice values indicate?JuniorReveal
- QExplain priority inversion and how priority inheritance fixes it.SeniorReveal
Frequently Asked Questions
What is the difference between priority scheduling and SJF?
SJF is actually a special case of priority scheduling where the priority is the inverse of burst time (shorter burst = higher priority). The starvation problem also applies to SJF for the same reason.
Can aging cause priority explosion?
Yes, if aging runs too fast, all processes quickly converge to highest priority, defeating the priority system. The aging interval must be tuned – typically 5–10 times the average burst time. Also cap priority to the highest level to avoid overflow.
Does Linux use priority scheduling?
Linux uses Completely Fair Scheduler (CFS) by default, which models CPU allocation as a virtual runtime but also incorporates nice values for priority weights. Additionally, real-time scheduling policies (SCHED_FIFO, SCHED_RR) use strict priority-based preemption for processes needing deterministic behaviour.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.