Search

How Your OS Decides Which Program Gets the CPU Next

The short answer

Quick answer: A computer usually has far more runnable tasks than CPU cores. The operating system's scheduler shares the cores among them by letting each task run for a short time slice, typically a few milliseconds, then pausing it and running another. A hardware timer forces the switch, so no program can hog the CPU. The scheduler chooses who runs next using priorities and a fairness rule: tasks that have had less CPU time recently go first, which keeps interactive programs responsive.

The problem

Open a task manager and you will see hundreds of threads. A laptop might have eight cores. Each core can run exactly one thread at any instant, so something has to decide:

  • Which thread runs on which core?
  • For how long?
  • What happens when a more important thread wakes up?

The scheduler makes these decisions thousands of times per second. Technically it schedules threads, not whole programs; see processes vs threads.

Thread states

Each thread is in one of a few states:

StateMeaning
RunningCurrently executing on a core
Ready (runnable)Could run, waiting for a core
Blocked (sleeping)Waiting for something: disk, network, a timer, a lock, a key press

Most threads are blocked most of the time. Your text editor spends nearly all its life waiting for you to type. The scheduler only chooses among the ready threads.

Preemption: nobody gets to hog the CPU

Early systems used cooperative multitasking: a program ran until it volunteered to give up the CPU. One badly written program could freeze the whole machine.

Modern systems use preemptive multitasking. A hardware timer interrupts the CPU at regular intervals. On each interrupt the kernel regains control and can decide to switch threads, whether the running program likes it or not.

A thread also gives up the CPU voluntarily whenever it blocks, for example by making a system call that has to wait for the disk.

The context switch

Switching from thread A to thread B means:

  1. Save A's CPU registers, program counter and stack pointer.
  2. Pick B.
  3. If B belongs to a different process, switch to its memory mapping.
  4. Restore B's saved registers and continue where it left off.

The switch itself takes on the order of a microsecond. The larger cost is indirect: B's data is probably not in the CPU cache, so it runs slowly at first. That is why schedulers avoid switching more often than necessary.

What makes a good scheduler

Schedulers juggle goals that pull against each other:

  • Responsiveness. A key press or mouse click should be handled immediately.
  • Throughput. Get as much total work done as possible, which favours fewer switches.
  • Fairness. Nobody should starve.
  • Energy. Idle cores should sleep, and small tasks should run on efficient cores.

A video encoder wants long uninterrupted runs. A text editor wants a few microseconds of CPU immediately. The same policy must serve both.

Classic algorithms

AlgorithmIdeaWeakness
First come, first servedRun tasks in arrival order to completionA long task makes everyone wait
Shortest job firstRun the quickest task firstNeeds to know the future; long tasks can starve
Round robinGive each task a fixed time slice in turnTreats interactive and batch tasks the same
Priority schedulingAlways run the highest priority taskLow priority tasks can starve
Multilevel feedback queueDemote tasks that use full slices, promote those that block oftenMany tuning knobs

The multilevel feedback queue captures an important insight: tasks that often block are probably interactive, so they should run quickly when they wake. Tasks that always use their full slice are CPU-bound and can wait a little.

How Linux does it

For many years Linux used the Completely Fair Scheduler (CFS). Its idea is simple: track how much CPU time each task has received, as a value called virtual runtime, and always run the task with the least. Tasks are kept in a sorted tree so the next one is found quickly. The kernel's CFS documentation describes it as modelling an "ideal, precise multi-tasking CPU".

An interactive task that sleeps most of the time accumulates little virtual runtime, so when it wakes it naturally jumps the queue.

Since kernel 6.6, Linux has been moving to EEVDF (Earliest Eligible Virtual Deadline First). It keeps the fairness accounting but also gives each task a virtual deadline, which lets latency-sensitive tasks be served sooner without extra heuristics. The EEVDF documentation explains the details.

Priorities and "nice"

On Unix-like systems each task has a nice value from -20 to 19. A higher value means the task is "nicer" to others and gets a smaller share of CPU. It is a weight, not a strict ranking: a nice 19 task still runs, just less.

nice -n 10 ./long-backup.sh      # start with lower priority
renice -n 5 -p 12345             # change a running process

Separate real-time scheduling classes exist for audio, robotics and similar work, where a task must run within a deadline. These always take precedence over normal tasks.

Multiple cores

With several cores, the scheduler keeps a run queue per core and balances load between them. Two extra considerations appear:

  • Cache affinity. A thread runs faster on the core it used last, because its data may still be cached there. Schedulers avoid moving threads unnecessarily.
  • Different core types. Many modern chips mix fast performance cores with slower efficient ones. The scheduler tries to put demanding work on the fast cores and background work on the efficient ones.

In containers and cloud systems, control groups add another layer, limiting how much CPU a whole group of processes may use.

Frequently asked questions

What is a time slice?

The maximum time a thread may run before the scheduler considers switching to another. It is usually a few milliseconds and often varies with load.

Why is my computer responsive even at 100% CPU?

Fair schedulers favour tasks that have used little CPU recently. Your interactive apps mostly sleep, so they get served promptly when they wake, even while a heavy job uses the rest.

What does CPU usage of 100% mean?

That there was always at least one ready thread for each core during the measurement period. It says nothing about whether the work was useful.

Can I control which core a program uses?

Yes. This is called CPU affinity (taskset on Linux). It is occasionally useful for benchmarking or latency-critical software, but the scheduler usually does better on its own.

Conclusion

The scheduler creates the illusion that everything runs at once by switching between tasks faster than you can notice. Preemption guarantees no program can take over, fairness accounting keeps interactive tasks snappy, and cache-aware placement keeps the switching cheap. It is one of the busiest and least visible parts of any operating system.

Related articles

Sources and further reading

Usama Muneer

Usama Muneer

Coder, Blogger, Tech Speaker & Web Technologies Enthusiast. Passionate about working on open-source Programming languages & Tools while utilizing my Product Development skills.

Your experience on this site will be improved by allowing cookies Cookie Policy