Konrad Kowalski (rootsher)Principal Platform & Reliability Architect100001110111000111110111100101000011100011010001

How Linux really gives a process CPU

date
category
Computer Science
reading
3 min / 671 words

A process does not "have CPU".

A process can be ready to execute, but it is the Linux scheduler that decides when it actually gets processor time.

That is the single most important starting point for understanding CPU in an operating system.

Runnable does not mean running

If a process or thread has work to do and is not waiting on I/O, a lock or a timer, it can be in the runnable state.

Roughly speaking:

text
runnable -> running

running means the task is currently executing instructions on a CPU.

runnable means:

text
it is ready to execute,
but it may still be waiting for a processor

That distinction matters.

On a single logical CPU, only one task can execute at any given moment.

If we have:

text
CPU 0

task A - running
task B - runnable
task C - runnable

then B and C are ready to work, but they have to wait until the scheduler gives them a CPU.


The scheduler picks the next task

Linux keeps per-CPU scheduler structures holding the tasks that are ready to execute.

A simplified picture:

text
CPU 0

running:
    A

runnable:
    B
    C
    D

After a while the scheduler may decide:

text
A -> B

The state then changes roughly like this:

text
A: running  -> runnable
B: runnable -> running

A context switch happens and the CPU starts executing process B's code.

Later C may be picked, then A again, and so on.

text
A -> B -> C -> A -> D -> ...

That is how many processes can share a single processor.


CPU time

If a process executed for 10 ms, it used about 10 ms of CPU time.

That is independent of how much time passed on the clock.

Example:

text
0 ms      process becomes runnable
0-20 ms   waits for CPU
20-30 ms  executes
30 ms     finishes its work

Wall-clock time:

text
30 ms

CPU time:

text
10 ms

For the remaining 20 ms the process was ready to execute but did not get a processor.

This is a very important distinction:

text
wall-clock time != CPU time

CPU time measures the time spent actually executing code on a processor.


What happens when there are more processes than CPUs

Assume one logical CPU and two processes:

text
A
B

Both are CPU-bound and always have work.

Each of them would like to execute without interruption.

That is not possible, because only one CPU is available.

So the scheduler has to divide processor time:

text
time ------------------------------------------------>

CPU:
| A | B | A | B | A | B | A | B |

Each process is running for part of the time and runnable for another part.

Over a longer window they may each receive roughly half of the CPU time.

If we observe a one-second window:

text
A CPU time ~ 500 ms
B CPU time ~ 500 ms

That does not mean each one got "half of a physical core".

They got alternating slices of execution time on the same logical CPU.


And what if we have several CPUs?

On a multiprocessor machine several tasks can execute in parallel.

Example:

text
CPU 0 -> task A
CPU 1 -> task B
CPU 2 -> task C
CPU 3 -> task D

If a fifth runnable task appears:

text
task E

it cannot execute in parallel with the other four.

It has to wait until the scheduler makes one of the CPUs available to it.

So with four logical CPUs:

text
4 runnable tasks  -> all can be running
5 runnable tasks  -> at least one has to wait
20 runnable tasks -> most will be waiting at any moment

The mere fact that a process is runnable does not guarantee immediate execution.


Threads are scheduled separately

The scheduler does not look at a process only as a whole.

In practice tasks are what gets scheduled, so a multithreaded process can execute several threads at once.

A process:

text
app
├── thread A
├── thread B
├── thread C
└── thread D

On four CPUs we can have:

text
CPU 0 -> thread A
CPU 1 -> thread B
CPU 2 -> thread C
CPU 3 -> thread D

Such a process can consume roughly:

text
4 CPU-seconds

in a single second, because four of its threads executed in parallel for that second.

That is why a value like:

text
400% CPU

is nothing strange for a multithreaded process in tools that treat 100% as full utilisation of one logical CPU.


The scheduler does not "start a process once"

A process does not get a CPU once and for all.

The scheduler keeps making decisions continuously.

A typical CPU-bound task may look like this:

text
running
   |
   v
runnable
   |
   v
running
   |
   v
runnable
   |
   v
running

The task is repeatedly taken off the CPU and let back onto it.

From the application's point of view execution looks continuous.

From the kernel's point of view it is a series of CPU-time fragments handed out among many tasks.


The most important mental model

The mechanism boils down to:

text
task has work
    |
    v
runnable
    |
    v
scheduler picks the task
    |
    v
running
    |
    v
task consumes CPU time

If there are more available CPUs than runnable tasks, a task can usually run quickly.

If there are more runnable tasks than available CPU, some of them have to wait.

And this is the foundation of the whole discussion about CPU:

text
a process does not own a processor

a process gets time slices from the scheduler
in which it may execute instructions

Every higher layer of CPU control is built on top of this mechanism.