bigonotes
CS ConceptsOS Fundamentals

CPU Scheduling

How the OS decides which ready task runs next: FCFS, SJF, round robin, priority scheduling and multi-level feedback queues.

8 min readLast updated OS FundamentalsListen · Watch on NotebookLM

There are usually more ready tasks than CPU cores, so something has to decide who runs next and for how long. That's the CPU scheduler. Knowing the classic algorithms helps you reason about latency, fairness and starvation in operating systems and in your own job queues.

For example, six processes P1 to P6 sit in the ready queue, but there are only two cores. Only two can run at a time: who goes first, in what order, and for how long?

What a scheduler balances#

A scheduler juggles several goals at once.

Goal Meaning
Throughput Finish as many tasks as possible per unit of time
Fairness Every task gets a reasonable share of CPU
Waiting time Minimize time spent in the ready queue
Response time React quickly to user input
No starvation No task waits forever

These goals conflict. Maximizing throughput can starve small tasks; strict fairness can lower throughput. Every algorithm picks a trade-off.

Preemptive vs non-preemptive#

Schedulers split first on whether they ever take the CPU away from a running task.

  • Non-preemptive: once a task starts, it keeps the CPU until it finishes or blocks on I/O.
  • Preemptive: the OS can take the CPU away (usually via a timer interrupt) and put the task back in the ready queue.

The OS can always stop a process. "Non-preemptive" describes a scheduling policy where the OS chooses not to interrupt, not a power the process has. Think of two teachers: one lets each student finish their presentation, the other cuts everyone off at five minutes.

Non-preemptive is simpler, more predictable and has less switching overhead, but one task stuck in an infinite loop can hog the CPU forever. That's why Linux, Windows and macOS all schedule preemptively.

One exception: kernel code sometimes disables interrupts for a few instructions (for example, around a spinlock). During that tiny window even the timer can't fire. That's the kernel protecting itself, not a user process controlling its own scheduling.

Algorithm 1: First Come, First Served (FCFS)#

Run tasks in arrival order, like a grocery line with no cutting. The examples below use these three processes:

Process Arrival Burst (CPU time needed)
P1 0 ms 10 ms
P2 1 ms 4 ms
P3 2 ms 2 ms

FCFS runs them in this order:

Order Process Runs from To
1 P1 0 ms 10 ms
2 P2 10 ms 14 ms
3 P3 14 ms 16 ms

P2 waits 9 ms for a 4 ms job; P3 waits 12 ms for a 2 ms job. This is the convoy effect: one long task holds up everyone behind it, like being stuck behind a full cart when you have two items.

Type Fair? Starvation? Good for Bad for
Non-preemptive In order, not in waiting time No Simple batch systems Interactive systems

Algorithm 2: Shortest Job First (SJF)#

Run the task with the shortest burst first. With the same three processes:

Order Process Runs from To
1 P3 0 ms 2 ms
2 P2 2 ms 6 ms
3 P1 6 ms 16 ms

Average wait (ignoring arrival offsets):

Algorithm P1 P2 P3 Average
FCFS 0 9 12 7 ms
SJF 6 2 0 2.7 ms

SJF is provably optimal for minimizing average waiting time, but it has two problems:

  1. You don't know burst times in advance. Real systems can only estimate from past behavior or hints, so pure SJF is mostly theoretical.
  2. Starvation. If short jobs keep arriving, a 50 ms job can be pushed back forever.
Type Fair? Starvation? Good for Bad for
Non-preemptive (preemptive version: SRTF, Shortest Remaining Time First) No Yes, for long jobs Minimizing average wait Long-running tasks

Algorithm 3: Round Robin (RR)#

Each task gets a fixed time slice (quantum). When it expires, the task goes to the back of the queue. Round robin is the basis of interactive, general-purpose scheduling.

With P1 = 10 ms, P2 = 4 ms, P3 = 2 ms and a quantum of 3 ms:

Slot Process Time Runs for Outcome
1 P1 0–3 ms 3 ms Preempted
2 P2 3–6 ms 3 ms Preempted
3 P3 6–8 ms 2 ms Done early
4 P1 8–11 ms 3 ms Preempted
5 P2 11–12 ms 1 ms Done
6 P1 12–16 ms 4 ms Remaining 4 ms, done

(The last P1 segment is 4 ms, longer than the quantum: P1 is the only task left, so it gets a 3 ms slice and then a 1 ms slice back to back.)

Choosing the quantum#

The quantum has to be large compared with the cost of a switch, but small enough that nobody waits long.

Quantum Behavior
Too small (1 ms) Constant switching (P1, P2, P3, P1, P2, P3, ...), poor throughput
Too large (10 s) Behaves like FCFS, terrible response time
Sweet spot (~10–100 ms) Real work per turn, nobody waits long

The overhead is the switch cost divided by the quantum:

  • 20 µs / 100 ms = 0.02%, which is negligible.
  • 20 µs / 1 ms = 2%, which is noticeable waste.
Type Fair? Starvation? Good for Bad for
Preemptive Yes, equal slices No Interactive, general-purpose systems Equal-length jobs (FCFS plus extra switches)

Algorithm 4: Priority scheduling#

Each task has a priority; the highest-priority ready task runs first. Here a lower number means a higher priority:

Process Burst Priority Runs
P2 4 ms 1 0–4 ms
P3 2 ms 2 4–6 ms
P1 10 ms 3 6–16 ms
P4 6 ms 4 16–22 ms

Where priorities come from:

  • Static: set at creation, never changes (system tasks high, user tasks lower).
  • Dynamic: the OS adjusts based on behavior. I/O-heavy tasks get boosted because they give up the CPU voluntarily; CPU hogs get lowered.

Preemptive vs not: if P1 (priority 3) is running and P2 (priority 1) arrives, a non-preemptive scheduler lets P1 finish; a preemptive one moves P1 back to Ready and runs P2 now. Modern systems use the preemptive form.

Starvation and aging#

If high-priority tasks keep arriving, a low-priority task may wait for hours. The fix is priority aging: raise a task's priority the longer it waits.

Time waiting P10's priority
0 (start) 5
1 min 4
2 min 3
3 min 2
4 min 1, and it finally runs

Eventually every waiting task reaches top priority, so none starves.

Type Fair? Starvation? Good for Bad for
Either (modern: preemptive) No Yes, unless you add aging Separating important from background work, real-time Fairness without aging

Combining them: Multi-Level Feedback Queue (MLFQ)#

Real schedulers mix these ideas. The textbook design is the multi-level feedback queue: several round-robin queues at different priorities with different quanta.

Queue Policy Quantum Typical tasks
0 (highest) RR 5 ms Interactive: keyboard, UI
1 RR 10 ms
2 RR 50 ms Background work
3 (lowest) FCFS / long quantum Long Batch jobs

Going down the queues, priority decreases and the quantum increases.

Rules: a task moves between queues based on how it uses the CPU.

  • A new task starts in Queue 0 (benefit of the doubt).
  • Uses its full quantum without blocking → demoted one level.
  • Blocks on I/O before the quantum ends → stays or is promoted.
  • Waits too long in a low queue → aged upward, so nothing starves.

For example, follow one new task:

  1. It starts in Queue 0.
  2. It uses its full 5 ms, so it drops to Queue 1.
  3. It uses its full 10 ms, so it drops to Queue 2.
  4. It blocks on I/O after 3 ms, so it moves back up to Queue 1.

Over time, CPU hogs sink toward Queue 3, while aging promotes long-waiting tasks back up toward Queue 0.

Why the quantum grows as priority drops: interactive tasks need only short bursts (they block on input before the quantum ends), so small quanta keep response time low. CPU-heavy tasks need long stretches; tiny quanta would just switch them constantly for no benefit. "You get the CPU less often, but for longer when you do."

The result: editors and browsers stay responsive in the top queue, compilation and ML training get long slices lower down, and aging guarantees progress, all without anyone setting priorities by hand.

Windows and macOS use priority-based schedulers in the MLFQ family, with boosts for interactive and I/O-bound threads. Linux takes a different route: since 2.6.23 it used the Completely Fair Scheduler (CFS), which tracks each task's CPU usage and runs the one that has had the least, and kernel 6.6 replaced CFS with EEVDF. The goals (responsive interactive tasks, fair share, no starvation) are the same.

In practice, the same ideas show up beyond the OS: hypervisors share physical CPUs across virtual machines, and multi-tenant cloud platforms use priorities and quotas so one customer's workload can't starve another's.

All algorithms compared#

Here are the five algorithms side by side.

Algorithm Preemptive? Starvation? Best for
FCFS No No Simple batch
SJF No (SRTF is preemptive) Yes Minimizing average wait
Round Robin Yes No Fairness, interactivity
Priority Either Yes, without aging Real-time, importance
MLFQ Yes No, with aging General-purpose OS

Key takeaways#

  • The scheduler trades off throughput, fairness, waiting time and response time; no algorithm wins on all.
  • FCFS suffers the convoy effect; SJF minimizes average wait but needs burst times you rarely know and can starve long jobs.
  • Round robin is fair and responsive if the quantum is much larger than the context-switch cost.
  • Priority scheduling needs aging to prevent starvation.
  • MLFQ demotes CPU hogs, keeps I/O-bound tasks responsive, and ages waiters so nothing starves.

Found a mistake? Tell me on LinkedIn ↗

Further reading#