Operating Systems
📂 Scheduling Algorithm
· 4 of 5
50 min read
Priority Scheduling Algorithms
Master Priority Scheduling from Galvin's Operating System Concepts. This tutorial covers preemptive and non-preemptive variants using the low-number-equals-high-priority convention, three fully worked numericals with step-by-step traces, animated SVG Gantt charts, the starvation problem, aging solution, priority inversion (Mars Pathfinder story), Python implementations for both variants, and comparison with FCFS, SJF, and Round Robin.
Section 01
The Story That Explains Priority Scheduling
📖 Real World Analogy
The Hospital Emergency Room
Imagine a busy hospital emergency room. Patients arrive continuously — a child with a fever,
an elderly man with chest pain, a teenager with a sprained ankle, a woman with severe bleeding.
The receptionist does not serve them First-Come-First-Served. If she did, the man
with chest pain might die while waiting behind the sprain.
Instead, the triage nurse assigns each patient a priority number based on
urgency. The chest-pain case gets priority 1 (highest). The bleeding case gets 2. The fever
gets 4. The sprained ankle gets 7. The doctor always calls the patient with the
smallest priority number next — because in this system, lower number
means more urgent.
That is exactly how Priority Scheduling works inside your operating system,
where "patients" are processes and the "doctor" is the CPU.
In an operating system, hundreds of processes compete for a single CPU. Some are critical
(a system daemon handling network packets), others are trivial (a background spellchecker).
Priority Scheduling — one of the most studied algorithms in Galvin's
Operating System Concepts — assigns each process a priority and lets the CPU
always execute the highest-priority process first.
💡
The Core Definition
Priority Scheduling is a CPU scheduling algorithm in which each process
is assigned a priority number, and the CPU is allocated to the process with the
highest priority. Equal-priority processes are scheduled in
FCFS order. Galvin's textbook convention: smaller number = higher priority.
Section 02
The Two Flavours — Preemptive vs Non-Preemptive
Priority Scheduling exists in two forms. The difference is what happens when a
new higher-priority process arrives while a lower-priority one is running.
🔒
Non-Preemptive Priority
once started, runs to completion
Once the CPU is given to a process, it holds it until it either finishes or voluntarily
blocks (for I/O). Even if a higher-priority process arrives, it must wait in the
ready queue until the current one is done. Simple and predictable, but less
responsive.
⚡
Preemptive Priority
high-priority arrival kicks out CPU
If a new process arrives whose priority is higher than the running one,
the CPU is immediately taken away (preempted) from the current process
and given to the newcomer. The kicked-out process goes back to the ready queue.
More responsive; used in most real systems.
📏
Priority Convention
Galvin uses low = high
In Galvin's book, a smaller priority number means a higher priority
(Priority 0 > Priority 5). Linux internally uses the opposite (nice values), but for
textbook problems always follow Galvin: P1 with priority 1
is more urgent than P2 with priority 5.
Section 03
Key Terms You Must Know
📋 Vocabulary for Every Numerical
AT
Arrival Time — the moment the process enters the ready queue.
BT
Burst Time — total CPU time the process needs to complete.
PR
Priority — the number assigned to the process. Lower = more important.
CT
Completion Time — the clock time when the process finishes.
TAT
Turnaround Time = CT − AT. Total time process spent in the system.
WT
Waiting Time = TAT − BT. Time spent waiting in the ready queue.
RT
Response Time = time of first CPU allocation − AT. Time until first response.
Turnaround Time
TAT = CT − AT
Total wall-clock time a process spends in the system, from arrival to completion.
Waiting Time
WT = TAT − BT
Only the time spent waiting in the ready queue, not executing.
Section 04
Numerical 1 — Non-Preemptive Priority Scheduling
Let's solve a complete Galvin-style problem step by step. Convention: lower number = higher priority.
Process
Arrival Time (AT)
Burst Time (BT)
Priority (PR)
P1
0
4
2
P2
1
3
3
P3
2
1
4
P4
3
5
5
P5
4
2
1
Step-by-Step Execution Trace
⏱️ Timeline Walkthrough (Non-Preemptive)
t=0
Only P1 has arrived. CPU is idle → give it to P1. Since non-preemptive, P1 runs to completion regardless of new arrivals.
t=0→4
P1 executes for its full 4 units. During this window P2, P3, P4 arrive and wait in the ready queue.
t=4
P1 finishes. Ready queue = {P2(pr=3), P3(pr=4), P4(pr=5), P5(pr=1)}. P5 has lowest priority number → CPU goes to P5.
t=4→6
P5 runs for 2 units and completes.
t=6
Ready queue = {P2(pr=3), P3(pr=4), P4(pr=5)}. P2 has smallest number → CPU goes to P2.
t=6→9
P2 executes for 3 units and completes.
t=9
Ready queue = {P3(pr=4), P4(pr=5)}. P3 has smaller number → CPU goes to P3.
t=9→10
P3 runs for 1 unit and completes.
t=10→15
Only P4 left. It executes for 5 units and finishes at t=15.
Animated Gantt Chart — Step Reveal
Non-Preemptive Priority — Gantt Chart Building Frame by Frame
Order: P1 → P5 → P2 → P3 → P4. Notice P5 arrived last but ran second because pr=1 beats everyone.
Calculating CT, TAT, and WT
Process
AT
BT
PR
CT
TAT = CT − AT
WT = TAT − BT
P1
0
4
2
4
4 − 0 = 4
4 − 4 = 0
P2
1
3
3
9
9 − 1 = 8
8 − 3 = 5
P3
2
1
4
10
10 − 2 = 8
8 − 1 = 7
P4
3
5
5
15
15 − 3 = 12
12 − 5 = 7
P5
4
2
1
6
6 − 4 = 2
2 − 2 = 0
📈
Averages
Average TAT = (4 + 8 + 8 + 12 + 2) / 5 = 34 / 5 = 6.8 ms Average WT = (0 + 5 + 7 + 7 + 0) / 5 = 19 / 5 = 3.8 ms
Section 05
Numerical 2 — Preemptive Priority Scheduling
Now let's solve the same processes but with preemption enabled. The moment
a higher-priority process arrives, the CPU is snatched away from the currently running process.
Process
Arrival Time
Burst Time
Priority
P1
0
4
2
P2
1
3
3
P3
2
1
4
P4
3
5
5
P5
4
2
1
Preemption Trace — Every Time-Unit Decision
⚡ Preemptive Decision Log
t=0
Only P1 (pr=2) is ready. P1 starts. Remaining BT of P1 = 4.
t=1
P2 (pr=3) arrives. Compare: running P1(pr=2) vs new P2(pr=3). P1 still has lower number → no preemption. P1 continues.
t=2
P3 (pr=4) arrives. P1(pr=2) still wins → P1 continues.
t=3
P4 (pr=5) arrives. P1 still wins → P1 continues. Remaining BT of P1 = 1.
t=4
P5 (pr=1) arrives! P5 has lower number than running P1(pr=2). PREEMPT! But wait — at t=4 exactly, P1 also finishes its last unit (started at t=0, BT=4). So P1 completes at t=4. Now P5 starts.
t=4→6
P5 runs for its full 2 units (highest priority, no one can preempt). P5 completes at t=6.
P2 runs uninterrupted (nothing new arrives, nothing beats pr=3 in queue) → P2 completes at t=9.
t=9→10
P3 runs 1 unit → completes at t=10.
t=10→15
P4 runs 5 units → completes at t=15.
💡
Tie-Break Rule at Preemption Instant
When a new process arrives exactly at the moment the running process completes,
most textbooks (Galvin included) treat completion as happening first, then the
new arrival is scheduled. If instead a preemption happens mid-execution, the interrupted
process retains its remaining burst time and rejoins the ready queue.
Animated Preemptive Gantt Chart
Preemptive Priority — Animation with Preemption Marker
Final Table — Preemptive Version
Process
AT
BT
PR
CT
TAT
WT
P1
0
4
2
4
4
0
P2
1
3
3
9
8
5
P3
2
1
4
10
8
7
P4
3
5
5
15
12
7
P5
4
2
1
6
2
0
Section 06
Numerical 3 — Preemption Actually Happens
Now a case where preemption changes the outcome. Same idea, but P5 arrives earlier
with a higher-priority than the process currently running.
Where does the priority number come from? Galvin classifies the source into two categories.
💻 Internal Priority
Assigned By
Based On
The Operating System
Memory needs
Automatically computed
Time limits
Uses measurable quantities
Number of open files
Objective and dynamic
Ratio of avg I/O burst to avg CPU burst
👤 External Priority
Assigned By
Based On
System Administrator
Importance of the user
Set outside the OS
Type & amount paid for computer use
Political / business decisions
Department sponsoring the work
Subjective and static
Other non-technical factors
Section 08
The Starvation Problem
📖 Story
The MIT IBM 7094 Legend
Galvin recounts a famous story: when MIT shut down the IBM 7094 at the end of 1973,
they discovered a low-priority process that had been submitted in 1967 and had never run.
Six years of waiting. The higher-priority jobs kept flooding in, and the poor low-priority job
was pushed back forever. This is starvation — also called
indefinite blocking.
⚠️
The Fatal Flaw of Pure Priority Scheduling
A steady stream of high-priority processes prevents low-priority ones from
ever getting the CPU. Some low-priority processes wait so long they
effectively never complete. This is unfair, and in production systems it is unacceptable.
The solution — Aging — is what makes priority scheduling actually usable.
Section 09
The Solution — Aging
Aging gradually increases the priority of processes that have been waiting
in the ready queue for a long time. Eventually even the lowest-priority process becomes
the highest-priority and gets scheduled. Simple, elegant, effective.
🔆
Galvin's Aging Example
Suppose priorities range from 127 (lowest) to 0 (highest). Every 15 minutes,
decrement the priority of every waiting process by 1. Even a priority-127 process
that submitted at 8 AM would be top-priority (0) by 8 AM the next day.
Starvation is impossible.
Aging Illustrated as a Pipeline
01
Process enters ready queue
A new process arrives with an initial static priority — say pr=100 (low).
02
Waits in queue while higher-priority processes run
Time passes. Higher-priority processes keep arriving and running. Our process waits.
03
Priority ages down (becomes better) every T units
The scheduler periodically decrements the priority number of every waiting process. pr=100 → 99 → 98 → ...
04
Eventually crosses newcomers
After enough waiting, the aged process has priority lower than any new arrival → it becomes the highest-priority process.
05
CPU allocated — starvation eliminated
The process runs. Fairness restored. Every process is guaranteed to eventually execute.
Section 10
Python Implementation — Both Variants
Non-Preemptive Priority Scheduling
# Non-Preemptive Priority Scheduling — Galvin convention (low number = high priority)defnon_preemptive_priority(processes):
# processes: list of dicts { 'pid', 'at', 'bt', 'pr' }
n = len(processes)
completed = []
ready = []
t = 0
remaining = sorted(processes, key=lambda p: p['at'])
while remaining or ready:
# Move all arrived processes into ready queuewhile remaining and remaining[0]['at'] <= t:
ready.append(remaining.pop(0))
if not ready:
t = remaining[0]['at'] # CPU idle → jump to next arrivalcontinue# Pick process with SMALLEST priority number
ready.sort(key=lambda p: p['pr'])
p = ready.pop(0)
start = t
t += p['bt'] # Runs to completion
p['ct'] = t
p['tat'] = p['ct'] - p['at']
p['wt'] = p['tat'] - p['bt']
completed.append(p)
return completed
procs = [
{'pid': 'P1', 'at': 0, 'bt': 4, 'pr': 2},
{'pid': 'P2', 'at': 1, 'bt': 3, 'pr': 3},
{'pid': 'P3', 'at': 2, 'bt': 1, 'pr': 4},
{'pid': 'P4', 'at': 3, 'bt': 5, 'pr': 5},
{'pid': 'P5', 'at': 4, 'bt': 2, 'pr': 1},
]
result = non_preemptive_priority(procs)
print("PID AT BT PR CT TAT WT")
for p insorted(result, key=lambda x: x['pid']):
print(f"{p['pid']:3} {p['at']:3} {p['bt']:3} {p['pr']:3} {p['ct']:3} {p['tat']:4} {p['wt']:3}")
avg_tat = sum(p['tat'] for p in result) / len(result)
avg_wt = sum(p['wt'] for p in result) / len(result)
print(f"\nAvg TAT = {avg_tat:.2f}, Avg WT = {avg_wt:.2f}")
# Preemptive Priority Scheduling — recheck priorities at every time unitdefpreemptive_priority(processes):
n = len(processes)
remaining = {p['pid']: p['bt'] for p in processes}
completed = {}
t = 0# Precompute total burst so we know when to stop
total_bt = sum(p['bt'] for p in processes)
whilelen(completed) < n:
# Which processes have arrived by time t and still have BT left?
available = [p for p in processes
if p['at'] <= t and remaining[p['pid']] > 0]
if not available:
t += 1# CPU idlecontinue# Choose highest priority (smallest pr number)
curr = min(available, key=lambda p: p['pr'])
remaining[curr['pid']] -= 1
t += 1if remaining[curr['pid']] == 0:
completed[curr['pid']] = {
'ct': t,
'tat': t - curr['at'],
'wt': (t - curr['at']) - curr['bt'],
}
return completed
procs = [
{'pid': 'P1', 'at': 0, 'bt': 6, 'pr': 4},
{'pid': 'P2', 'at': 1, 'bt': 4, 'pr': 2},
{'pid': 'P3', 'at': 3, 'bt': 3, 'pr': 1},
{'pid': 'P4', 'at': 5, 'bt': 2, 'pr': 3},
]
res = preemptive_priority(procs)
for pid, m insorted(res.items()):
print(f"{pid}: CT={m['ct']}, TAT={m['tat']}, WT={m['wt']}")
SJF is just Priority Scheduling where the priority is the (inverse of the) next CPU burst.
A shorter burst → higher priority. This is why the two algorithms are studied together in every OS textbook.
Section 12
Advantages & Disadvantages
✅
Handles Importance
Advantage
Real workloads have unequal importance. A crashing kernel daemon must run before a
background wallpaper refresh. Priority scheduling directly encodes this.
✅
Flexible
Advantage
Works in both preemptive and non-preemptive modes. Priorities can be internal (computed)
or external (assigned) — you can tune the policy to your workload.
✅
Foundation for Real Systems
Advantage
Linux CFS, Windows Multilevel Feedback Queues, and every real-time OS
(VxWorks, QNX, FreeRTOS) are built on some form of priority scheduling.
❌
Starvation
Disadvantage
Without aging, low-priority processes may never execute. The MIT IBM 7094 story
is the classic warning. Any real implementation must include aging.
❌
Priority Inversion
Disadvantage
A high-priority process may be blocked waiting for a resource held by a
low-priority process. Famously delayed NASA's Mars Pathfinder in 1997.
Requires priority inheritance protocols.
❌
Priority Assignment Problem
Disadvantage
"How do I know what priority to give a process?" is genuinely hard.
Getting this wrong ruins performance. Systems often add multi-level queues to help.
Section 13
Real-World Applications
🏆
Linux Scheduler
Linux uses two priority ranges: 0-99 for real-time processes (highest), 100-139 for normal
processes controlled via nice values (-20 to +19). Aging is built in.
Completely Fair Scheduler (CFS)
📡
Real-Time Systems
Pacemakers, ABS car brakes, industrial robotics — all use strict priority scheduling
where missing a deadline causes catastrophic failure. Preemption is mandatory.
VxWorks, QNX, FreeRTOS, RTLinux
🖥️
Windows Scheduler
Windows uses 32 priority levels (0-31). The foreground application gets a temporary
priority boost so your active window feels responsive. Aging prevents starvation.
Multilevel Feedback Queue
🔋
Database Query Engines
Oracle, PostgreSQL, and SQL Server prioritise short OLTP queries over long analytical
scans so interactive users don't wait behind a batch job.
Workload Manager, Resource Governor
🌐
Network Routers
Quality of Service (QoS) marks packets with priority classes. VoIP packets jump ahead of
bulk file transfers so your call doesn't stutter.
DiffServ, DSCP marking
👷
Print Spooler & Job Queues
Enterprise print servers let admins boost priority for urgent contract prints
over overnight batch reports. Classic non-preemptive priority.
CUPS, Windows Print Manager
Section 14
Priority Inversion — A Cautionary Tale
🛸 NASA Story
Mars Pathfinder, July 1997
NASA's Mars Pathfinder rover kept mysteriously rebooting on Mars. The cause?
Priority inversion. A low-priority meteorological task held a shared
resource. A high-priority bus management task needed that resource and blocked.
Meanwhile, a medium-priority communications task ran freely, starving the low-priority
task from releasing the resource. The system's watchdog concluded the high-priority
task was hung, and rebooted the rover.
JPL engineers fixed it remotely by enabling priority inheritance
on the mutex. The lesson: pure priority scheduling can lead to catastrophic bugs
unless the OS supports inheritance protocols.
⚠️
Priority Inversion vs Starvation
Starvation = low-priority process never gets CPU because higher-priority ones keep coming. Fix: aging. Priority Inversion = high-priority process is blocked waiting for a resource held by a lower-priority one. Fix: priority inheritance protocol — the low-priority holder temporarily inherits the higher priority.
Section 15
Golden Rules — Priority Scheduling
🎖 Galvin's Non-Negotiable Rules
1
Convention matters. In Galvin's textbook and most exams, lower priority number
means higher priority. Always state this at the start of every solution. Some systems (like Linux)
use the reverse — read the question carefully.
2
For non-preemptive, once a process starts it runs to completion. Only choose a
new process when the CPU becomes free. Break ties by arrival time, then by process ID.
3
For preemptive, re-evaluate priorities at every arrival event. If a new arrival
has strictly higher priority (smaller number), preempt immediately. On a tie, the running process usually keeps the CPU.
4
When the CPU is idle (no arrived process), fast-forward time to the next arrival.
Do not confuse idle time with waiting time — waiting is only when a process is in the ready queue.
5
Always remember the formulas: TAT = CT − AT and WT = TAT − BT.
For preemptive versions, BT means total burst time — not remaining.
6
Starvation is the killer. Any priority scheduler in production must implement
aging — periodically raising the priority of long-waiting processes.
Without aging, low-priority jobs may never run (MIT's 1967 → 1973 job).
7
Priority Inversion is different from starvation and requires a different fix:
priority inheritance. Not knowing this cost NASA a working Mars rover for weeks.
8
SJF is a special case of priority scheduling where priority = next CPU burst.
This unification is a favourite viva question — memorise it.