<aside> 🧭

Module 07 · CPU scheduling, nice and load average

Module 01 showed you the timer interrupt that lets the kernel take the CPU back. This module is about what it does with it — who runs next, why, and how to read the numbers everyone quotes and few people understand.

🧠 concept → 🧩 real-world analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> ✅

Before you start, you should already know:

From Module 01 — the timer interrupt, preemption, and mode versus context switches.

From Module 02 — process states R, S, D, the voluntary and involuntary switch counters, and the basic idea of load average.

From Module 06 — that Linux schedules threads, not processes. This module depends on that completely.

</aside>


⚖️ Part A · How the scheduler decides

A1 · What the scheduler is actually choosing between

<aside> 📖

Official docs: sched(7) — overview of CPU scheduling · Scheduler — kernel docs · proc_pid_stat(5)

</aside>

The scheduler's job is one decision, made constantly: which runnable thread gets a CPU next, and for how long.

Three words in that sentence are doing real work.

Thread, not process. Module 06 established this. A process with eight threads is eight separate candidates, and the scheduler has never heard of the process they belong to.

Runnable, not all. Only threads in state R are candidates. On the machine from Module 01 that was 1 out of 243 — everything else was asleep and not competing for anything. The scheduler's job is much smaller than people assume.

A CPU, not the CPU. Each CPU has its own run queue. Work is balanced between them, which is Section B2.

<aside> 🧩

Real-world analogy — the queue at a counter

A post office has four counters and a room full of people. But most of the people are not in the queue — they are filling in forms, waiting for someone, or reading. They are not competing for a counter and the staff correctly ignore them.

Only the people actually standing in the queue matter. That is state R.

Two things follow, and both correct common misconceptions.

First, a room with two hundred and forty-three people and one in the queue is not busy. That is the machine from Module 01, and it is why process count tells you almost nothing.

Second, the counters serve individuals, not families. Four people who arrived together still queue as four. That is threads, not processes, and it is why a process's CPU figure is a sum rather than a thing the scheduler ever considered.

Where the analogy stops working, and it is the whole of Section A2. A post office queue is first-come-first-served, and your place is decided when you arrive.

Linux does not work that way at all. It does not ask who arrived first — it asks who has had the least so far. Somebody who has just been served goes to the back regardless of when they arrived, and somebody who has been waiting patiently moves up.

</aside>

🧪 Exercise A1.1 — See how few threads are actually competing

# How many threads exist, and how many are RUNNABLE right now?
echo "threads total:     $(ps -eL --no-headers | wc -l)"
echo "threads runnable:  $(ps -eLo stat --no-headers | grep -c '^R')"
echo "CPUs available:    $(nproc)"

# The kernel's own view: runnable / total, from /proc/loadavg field 4
cat /proc/loadavg

# Now create real competition and look again
for i in $(seq 1 4); do ( end=$((SECONDS+6)); while [ $SECONDS -lt $end ]; do :; done ) & done
sleep 2
echo "--- with 4 busy loops running ---"
echo "threads runnable:  $(ps -eLo stat --no-headers | grep -c '^R')"
cat /proc/loadavg
wait

A2 · Fair share, not priority

<aside> 📖

Official docs: sched(7) · EEVDF Scheduler — kernel docs · CFS Scheduler — kernel docs

</aside>

Older schedulers used fixed priority levels: run the highest-priority thread, and only look at lower ones when nothing above them is ready. That starves low-priority work completely.