When you run MIT’s xv6 in QEMU, you’ll often see 100% CPU usage on one of the CPU threads, even when the system is doing absolutely nothing. I first noticed this when I could hear the normally super-quiet fan of my PC spinning. But why?
The Problem: Busy-Waiting in the Scheduler
xv6 implements a simple process scheduler in proc.c
. Here’s a simplified view:
|
|
This loop runs forever. Even when no processes are runnable, the scheduler keeps spinning, checking over and over, eating CPU cycles.
Since xv6 is running on a virtual CPU (QEMU) mapped to a real CPU thread, that thread is pegged at 100%, while doing nothing useful.
✅ The Fix: Halt the CPU Until the Next Interrupt
Real operating systems avoid this waste by halting the CPU when there’s nothing to do, using the hlt
instruction.
Here’s how the fix works in xv6:
|
|
found
is a local flag that tracks whether the scheduler found a RUNNABLE process during its loop.
Then later we use the hlt
instruction to halt the CPU until the next interrupt:
|
|
sti()
ensures that interrupts are enabled.hlt
pauses the CPU until the next interrupt (e.g. a timer, keyboard, etc.)- When an interrupt fires, control resumes and the scheduler continues.
This reduces CPU usage dramatically when the system is idle.
Why It Matters
- Power saving: Halting prevents wasting energy in real CPUs.
- Better QEMU performance: Virtual machines behave more like real ones.
- Realism: It’s an important part of understanding how OS schedulers really work.
Sidebar: What’s a “CPU Thread”?
In modern CPUs, each physical core can run one or more hardware threads (e.g. via Hyper-Threading).
What you see in top
, htop
, or your system monitor as CPU 0
to CPU N
are logical CPUs aka CPU threads.
So when we say “xv6 uses 100% of a CPU thread,” we mean it keeps one logical CPU busy, even when idle — unless we halt it properly.
Did the PDP-11 Have a Halt Instruction? Yes — The PDP-11, which Unix 6 was originally written for, had the WAIT instruction to halt the CPU until an interrupt. Xv6, as an educational OS, omits this for simplicity, but it’s realistic (and useful) to add back when exploring CPU scheduling and system behavior.
The Fix (The Actual Code to Use)
In the file proc.c
, replace the entire scheduler()
function with this one:
|
|
✅ Result
After this fix, xv6 only uses CPU time when it’s actually doing something. When idle, the thread goes quiet, like a real OS should.