To allow multiplexing to happen, there needs to be a point where a CPU goes from executing one thread to executing another thread.
Let's break down the precise steps:
- Interrupt or system call – The currently running process either voluntarily yields (e.g., waits for I/O) or an interrupt (e.g., timer interrupt) forces the CPU to jump into the kernel.
- Save current process state – The kernel saves the current process’s program counter, registers, and other volatile state into its PCB.
- Switch to the scheduler – The kernel runs the scheduler code to choose the next process from the ready queue.
- Load next process state – The kernel loads the saved state from the chosen process’s PCB into the CPU registers and program counter.
- Return to user mode – The CPU resumes execution of the new process at the point where it was previously stopped.
The context switch is pure overhead. During a context switch, no useful user work is done. This is why there is a trade-off between context switching time and "real stuff" execution time. A typical context switch on modern hardware can take a few microseconds (saving/restoring ~100–1000 bytes of registers + flushing pipeline). Frequent switches reduce responsiveness but waste CPU time. Infrequent switches can cause unfairness or high latency.
The rule of thumb – less than 10% of CPU time on context switching – is widely used in practice. If you measure 20% or more, the scheduler is either too aggressive (too small time slices) or there are too many active processes competing.
Blocking I/O
One of the reasons why there is context switching is blocking I/O. Let’s expand the sequence:
- A thread executes a system call like
read(fd, buffer, size). - The CPU transitions from user mode to kernel mode (via
syscallorintinstruction). The kernel now runs on behalf of that thread. - The kernel initiates the I/O request (e.g., sends a command to the disk driver). The I/O will take milliseconds to complete.
- Instead of busy-waiting, the kernel changes the thread’s state from RUNNING to WAITING (blocked on I/O). It removes the thread from the ready queue.
- The kernel then calls the scheduler to choose another thread to run – this is the “switch while we wait”.
- When the I/O completes, a hardware interrupt occurs (see Part 7). The interrupt handler marks the waiting thread as READY and puts it back in the ready queue.
- Eventually, the scheduler will resume the thread.
The key is that the kernel proactively switches away when it knows the thread cannot proceed. This is a form of Preemptive Multitasking, but enforced by the kernel – the thread has no choice.