COMP 3150Operating Systems

01 Introduction

Every time the operating system decides to run a different process, it must perform a small miracle: suspend one running computation mid-instruction, save everything about it, restore a completely different computation whose state was saved earlier, and resume it as if no interruption had occurred. This operation is the context switch.

Context switching is what makes preemptive multitasking possible. Without it, a process would run until it voluntarily gave up the CPU, and the operating system would have no way to enforce fairness, respond to interrupts, or protect one process from another's monopolisation. With it, the machine can give every process the illusion of a private CPU while dozens of processes share one physical core.

But context switching is not free. Every switch consumes CPU cycles that could have been used for useful work, and it also pollutes the CPU caches and the TLB in ways that degrade the incoming process's performance for some time after the switch. This lesson examines what a switch involves, why it costs, how to measure the cost, and what that cost means for scheduling policy. It closes Unit 3 by tying together the process model, the state model, the PCB, and the scheduler.

Prerequisites

This lesson assumes you have read Lessons 3.1 through 3.4. In particular, the PCB fields from Lesson 3.3 (particularly the saved processor state) and the scheduling decision points from Lesson 3.4 are the foundation of everything that follows.

02 Learning objectives

By the end of this lesson you will be able to

  • Define context switching.
  • Describe switching operations.
  • Explain switching overhead.
  • Measure switching activity.
  • Analyse performance impact.

This lesson contributes to U3.5 and to course learning outcomes CLO3 and CLO6. It completes Unit 3.

03 Required reading

  • Operating System Concepts — Chapter 3, Section 3.4 (Context Switch). The classical description of what happens on a switch and where the cost comes from.
  • OSTEP — Chapter 6, Mechanism: Limited Direct Execution. How a system call traps into the kernel and how the kernel performs a context switch — the mechanism behind everything this lesson discusses.
  • Love, R. — Linux Kernel Development, 3rd ed., Chapter 4, the section on context switching. Linux's implementation of the switch, including the context_switch() function and the difference between switching between processes and switching between threads.
  • Linux man pages: man 1 vmstat, man 1 pidstat, man 1 perf-stat, man 5 proc (the ctxt_switches field in /proc/<pid>/status).

Reading strategy

Read Silberschatz Section 3.4 first for the abstract description of the switch, then OSTEP Chapter 6 for how the trap into the kernel actually works. Love's section on context_switch() is short and precise — read it with the source open if you have it, or read it alongside the diagram in Figure 3.5.

04 Core content

4.1 What a context switch is

A context switch is the act of saving the state of one process and restoring the state of another, so that the second process can resume where it previously left off.

The "state" saved and restored is precisely the process context from Lesson 3.3: the register values, the program counter, the stack pointer, and the memory-management information that tells the MMU how to translate virtual addresses. When the kernel finishes the switch, the CPU's registers hold the values the incoming process had at the moment it was last suspended, and its next instruction is the one it was about to execute.

The key insight is that a context switch is a change in which process is running, not a change in what the process is doing. The incoming process resumes mid-computation, with no awareness that anything happened. From its perspective, it has simply been executing continuously since it last ran. This is the illusion that makes multiprogramming work.

4.2 When context switches happen

A context switch never occurs spontaneously. It is always the consequence of a decision or an event. The four triggers correspond exactly to the four scheduling decision points from Lesson 3.4:

  • A blocking system call. The running process calls read, wait, or a similar operation that cannot complete immediately. The process enters the Blocked state, and the kernel must select another ready process. Running → Blocked followed by a switch.
  • A timer interrupt. The hardware timer fires, the kernel's timer handler runs, and the scheduler decides another process should run. This is the mechanism by which preemption happens — the transition Running → Ready from Lesson 3.2. The switch is involuntary from the process's point of view.
  • An I/O completion interrupt. A device finishes an operation, its interrupt handler moves a blocked process back to Ready, and the scheduler may decide to preempt the current process in favour of the newly runnable one. Blocked → Ready, followed by a possible switch.
  • Process termination. The running process exits. The CPU is now idle and the kernel must select another process. Running → Terminated followed by a switch.

On a modern general-purpose system, these events occur frequently. A loaded server may perform hundreds of thousands of context switches per second, each one a small operation that the kernel performs as part of normal operation. It is the cumulative cost of all of them that matters, not the cost of any single one.

4.3 The steps of a context switch

A simplified account of what the kernel does between two processes — A (outgoing) and B (incoming):

The steps of a context switch A vertical sequence of six steps: process A is running and traps into the kernel; the kernel saves A's registers into A's PCB; the scheduler selects process B; the kernel restores B's registers from B's PCB; the memory map is switched to B's page table; and the kernel executes return-from-trap so that B resumes at its saved program counter. THE STEPS OF A CONTEXT SWITCH 1 · Process A is running. An interrupt (timer) or trap (system call) enters the kernel. 2 · Kernel saves A's registers into A's PCB (or onto A's kernel stack). 3 · Kernel runs the scheduler. It selects process B from the ready queue. 4 · Kernel restores B's saved registers from B's PCB. 5 · Kernel switches the memory map: loads B's page-table base into the MMU. 6 · Kernel executes return-from-trap. B resumes at its saved program counter. Steps 2–5 are the context switch proper. They are pure overhead from the perspective of useful computation. The mode switches in step 1 and step 6 are not part of the switch; they are the entry and exit.
Figure 3.5 — The steps of a context switch. Steps 2–5 are the switch proper; steps 1 and 6 are the mode transitions that bracket it.

Several details deserve comment.

  • The kernel runs on the incoming process's kernel stack. On modern kernels, each process has its own kernel stack — a small region of kernel memory used while executing system calls and interrupt handlers on that process's behalf. During a switch, the scheduler and the interrupt handlers execute on that stack. This keeps the user's stack intact and prevents a system-call-heavy process from consuming unbounded kernel stack space.
  • Where the saved state is kept varies. On some architectures, the incoming process's registers are restored from a dedicated CPU-state area referenced by a per-CPU variable. On others — and on Linux in particular — the state is saved on the kernel stack, and switching stacks is what actually performs the context switch.
  • The memory map change is expensive. On older architectures, switching page tables required flushing the TLB (translation lookaside buffer), which is a hardware cache of recent address translations. A flushed TLB means every subsequent memory access misses and must walk the page table — a serious slowdown that persists for thousands of instructions after the switch.
  • Modern hardware mitigates this. ASIDs (Address Space Identifiers) tag each TLB entry with the process it belongs to, so switching between processes does not require a full flush. This substantially reduces the true cost of a context switch on modern CPUs, though it does not eliminate it: the incoming process's working set is still not in the cache.

4.4 Sources of overhead

The overhead of a context switch comes from several distinct sources, and it is important to distinguish them because they have different characteristics and different implications.

Direct cost

  • Executing the scheduler's decision logic.
  • Saving and restoring registers.
  • Switching the memory map (writing the CR3 register on x86-64).
  • Updating kernel bookkeeping (state, queues, accounting).
  • Invalidating or updating TLB entries, if necessary.

On a modern CPU this direct cost is typically a few microseconds.

Indirect cost

  • Cold caches after the switch: L1, L2, and L3 data and instruction caches now hold the previous process's working set, which the incoming process does not need.
  • TLB misses until the incoming process's working set is refilled (much less severe with ASIDs, but not zero).
  • Branch predictor state that is specific to the previous process.
  • Prefetcher state that is now pointing at the wrong memory.

The indirect cost is often larger than the direct cost.

The indirect cost dominates

On a modern processor, the direct cost of a context switch is measurable in single-digit microseconds. But the cache and TLB pollution it causes can degrade the incoming process's performance for tens of microseconds afterward. A common rule of thumb is that the effective cost of a context switch is several times the direct measurement — sometimes 5–10× when the two processes have little cache overlap. This is why reducing context-switch frequency is a common optimisation for high-performance applications, and why running a CPU-bound workload on a quiet machine can be substantially faster than running it on a machine that is doing other work simultaneously.

4.5 Context switch versus mode switch

A term that is frequently confused with context switch is mode switch. They are not the same thing, and the distinction matters for performance analysis.

Mode switch
A change of privilege level within the same process. When a process issues a system call, the CPU traps from user mode to kernel mode, runs the kernel code, and returns to user mode. The process itself is unchanged — it is the same process, with the same memory map, the same registers (mostly), and the same scheduling state. A mode switch costs a few hundred cycles: a trap, a save of some registers, a return.
Context switch
A change of process. The kernel saves one process's state, restores another's, and switches the memory map. This involves two mode switches (into the kernel and out of it) plus the save-and-restore work in between, plus the cache and TLB pollution that follows. A context switch is a strictly larger operation than a mode switch, typically 5–10× more expensive in direct cost.

A system call that completes quickly requires only a mode switch. A system call that blocks requires a mode switch and a context switch. This is why blocking system calls are more expensive than non-blocking ones, and why programs that avoid blocking calls where possible run faster.

4.6 Thread context switches

A preview of Unit 4: a context switch between two threads of the same process is much cheaper than a switch between two different processes. Because threads share the same address space, the memory map does not need to change — no page table switch, no CR3 write, no TLB flush. Only the registers and the stack pointer change. And because the threads share the same working set — the same code, data, and heap — the cache pollution is much less severe; the incoming thread is likely to find much of what it needs already in the cache.

The direct cost of a thread switch is typically about half that of a process switch, and the indirect cost is often a small fraction. This is one of the main performance arguments for threads over processes: a thread switch is often several times cheaper than a process switch, and modern multithreaded servers rely on this to handle high request rates.

We will return to this distinction in Unit 4, when we examine the thread model itself. For now, the important point is that "context switch" is not one operation but a family of operations whose cost depends on what is being switched.

4.7 Measuring context switches

Linux exposes context-switch counts through several interfaces. Learning to read them is a core diagnostic skill for the remainder of the course.

# Per-process: voluntary and involuntary context switches
$ grep ctxt /proc/$$/status
voluntary_ctxt_switches:     17
nonvoluntary_ctxt_switches:  3

# System-wide: the "cs" column of vmstat shows switches per second
$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 4123456  98234 982344    0    0     4     8   87  143  1  1 98  0  0
 1  0      0 4123444  98234 982344    0    0     0     0  102  168  1  1 98  0  0
# The "cs" column is context switches per second.
# The "in" column is interrupts per second.

# Per-process: cswch/s (voluntary) and nvcswch/s (involuntary)
$ pidstat -w 1
Linux 6.5.0 ...        10/15/2024     _x86_64_    (4 CPU)

14:22:01  UID  PID   cswch/s nvcswch/s  Command
14:22:02 1000 1234      0.99      0.00  bash
14:22:02 1000 5678      5.00      0.00  code
14:22:02 1000 9012      0.00     12.34  yes

# System-wide cumulative count since boot
$ cat /proc/schedstat | head -1
version 15
# The 11th field is the total number of context switches.
$ awk 'NR==1 {print "switches:", $10}' /proc/schedstat
switches: 128453921

The distinction between voluntary and involuntary context switches is important:

  • A voluntary switch occurs when the process blocks on a system call — it chose to give up the CPU. The kernel's schedule() was called by the process itself (or by the kernel on its behalf as it entered a blocking path).
  • An involuntary switch occurs when the process is preempted — the CPU was taken away. The kernel's timer handler or another preemption source forced the switch.

A high rate of involuntary switches usually means the process is competing for CPU with other CPU-hungry processes. A high rate of voluntary switches usually means the process is I/O-bound — it is blocking frequently and yielding the CPU each time.

4.8 Why the cost matters for scheduling

The cost of a context switch is not merely an implementation detail — it directly constrains what scheduling policies make sense. Every policy decision must weigh the benefit of running the "right" process against the cost of switching to it.

  • Short time slices. Round-robin schedulers use a fixed time quantum. If the quantum is too small, the fraction of CPU time spent on context switches becomes significant. For example, if a switch costs 5 μs and the quantum is 100 μs, then 5% of every slice is overhead — and with hundreds of ready processes, the queueing delay compounds.
  • Preemption frequency. A scheduler that preempts on every interrupt may achieve excellent response times at the cost of many context switches. A scheduler that waits for a running process to block avoids switches but may allow a CPU-bound process to monopolise the CPU.
  • Cache locality. A process that is left to run for a longer period builds up warm caches and benefits from them. Frequent preemption destroys this benefit, which is why modern schedulers favour slightly longer quanta when the system is not under load.
  • Switching cost differential. A scheduler aware that thread switches are cheaper than process switches may prefer to switch threads when possible — an idea that modern language runtimes exploit with lightweight tasks.
Qualitative effect of quantum size (round-robin)
Quantum size Context-switch overhead Response time Throughput
Very small High (frequent switches) Excellent Poor (overhead dominates)
Moderate Acceptable Good Good
Very large Low (few switches) Poor (long waits) Best (no overhead)

The trade-off between quantum size, response time, and throughput is exactly the throughput-versus-latency trade-off from Lesson 3.4. Unit 5 examines how practical schedulers resolve it — the answer is not a single quantum value but a set of heuristics that adapt the quantum to the workload.

4.9 The illusion of concurrency

Stepping back, it is worth reflecting on what the context switch accomplishes in a larger sense. On a single-core CPU, only one process executes at any instant. Yet the user's experience is of many processes running simultaneously — a browser rendering a page while a music player streams audio while a backup runs in the background. This experience is a carefully constructed illusion.

The illusion depends on three things:

  1. Speed. Context switches happen far faster than a human can perceive. Switching between processes every few milliseconds means each process is running for a brief slice many times per second — enough to keep the user's perception of continuous progress intact.
  2. Blocking. Most processes block on I/O frequently, so the scheduler is not preempting them mid-computation but rather dispatching them when they have work to do. A music player consumes only a small fraction of the CPU; between its brief bursts, the CPU is free for other work.
  3. Aggregation. The combined effect of many short bursts from many processes is an apparently continuous multi-tasking experience. The user never sees the switching; they only see the output.

On multicore systems, the illusion becomes partially real: truly simultaneous execution happens across cores, and the context switch is what allows more processes than cores to share the machine. But even on multicore systems, the context switch is essential — it is what allows the number of runnable processes to exceed the number of cores.

4.10 Linux's implementation

Linux implements context switching in the function context_switch(), defined in kernel/sched/core.c. The function performs two distinct operations:

  • switch_mm() — switches the memory map. On x86-64 this writes the new process's page-table base to the CR3 register. If the incoming and outgoing processes share the same mm — because they are threads of the same process — this step is effectively a no-op.
  • switch_to() — switches the processor state and the kernel stack. This is architecture-specific code, usually a small assembly routine, because it must manipulate the stack pointer and the instruction pointer in ways that C cannot express.

The function has one more important property: it uses lazy TLB flushing. When switching to a process that has recently run, the kernel may skip the TLB flush entirely if it knows the old entries are still valid — for example, if the process is being resumed after a short preemption and the previous process's address space is unlikely to conflict. Modern hardware's PCID (Process Context Identifier) feature lets the CPU tag TLB entries with a process identifier, so a switch does not require flushing at all — the CPU simply starts using entries tagged with the new process's PCID.

The Linux context switch is one of the most carefully optimised pieces of code in the kernel. It runs hundreds of thousands of times per second on busy systems, and every instruction counts.

05 Worked example

To make the cost concrete, measure the actual context-switch overhead on your course VM.

Measuring the cost of a context switch

The following measurement uses a simple technique: run two processes that pass a token back and forth many times, and time the total operation. Each token pass involves two context switches (one to suspend the sender, one to run the receiver), so the total time divided by the number of passes gives an upper bound on the cost per switch.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>

#define ITERATIONS 100000

/* A pair of pipes connecting the parent and child */
int to_child[2];
int to_parent[2];

static double now(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec / 1e6;
}

int main(void) {
    if (pipe(to_child) < 0 || pipe(to_parent) < 0) {
        perror("pipe");
        return 1;
    }

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        /* Child: receive, send back */
        char c;
        for (int i = 0; i < ITERATIONS; i++) {
            if (read(to_child[0], &c, 1) != 1) break;
            write(to_parent[1], &c, 1);
        }
        return 0;
    } else {
        /* Parent: send, receive back */
        char c = 'x';
        double start = now();
        for (int i = 0; i < ITERATIONS; i++) {
            write(to_child[1], &c, 1);
            read(to_parent[0], &c, 1);
        }
        double elapsed = now() - start;
        wait(NULL);

        printf("iterations:      %d\n", ITERATIONS);
        printf("total time:      %.3f s\n", elapsed);
        printf("time per round:  %.3f μs\n", (elapsed * 1e6) / ITERATIONS);
        printf("per switch:      %.3f μs  (round trip / 2)\n",
               (elapsed * 1e6) / (2 * ITERATIONS));
    }
    return 0;
}

Build and run it in your VM:

$ gcc -Wall -Wextra -g -O2 -o ctx ctx.c
$ ./ctx
iterations:      100000
total time:      0.712 s
time per round:  7.120 μs
per switch:      3.560 μs  (round trip / 2)

The number you get on your own machine will differ, but it will be in the same order of magnitude. On a modern x86-64 VM running in a hypervisor, a "context switch" in this sense is a few microseconds. On bare metal, it is often less than a microsecond.

What the number actually measures.

Careful readers will note that each iteration involves more than just a context switch: a write to a pipe, a wake-up of the reader, a read from the pipe, a write back, and so on. Each of these adds overhead. The number 3.56 μs per switch is therefore an upper bound: the true cost of a context switch on this system is at most this much, and likely lower.

To get closer to the true cost, we would need to strip away the pipe overhead — using a shared memory region with atomic operations, or the futex mechanism from Unit 6. But for the purposes of this course, the upper bound is enough: it tells us the order of magnitude, and it demonstrates the point that a switch is a measurable operation with a real cost.

Compare with a thread-based variant.

Now modify the program to use two threads of the same process instead of a parent and child. The threads communicate through shared memory and a pair of condition variables. Because the memory map does not change on each switch, the cost should be lower:

process-based (fork + pipes):    3.56 μs per switch
thread-based (pthreads + cv):    1.42 μs per switch
speedup:                         ~2.5×

This is the difference between process context switches and thread context switches. When we reach Unit 4, this difference will be one of the main arguments for the thread model.

Compare with a CPU-bound workload.

To see the effect of context switching on a real workload, run a CPU-bound benchmark on a quiet VM and then again while other CPU-bound processes are competing for the CPU:

# Quiet system:
$ time ./benchmark
real    0m10.12s
user    0m10.11s
sys     0m0.01s

# With four competing processes:
$ for i in 1 2 3 4; do yes > /dev/null &; done
$ time ./benchmark
real    0m14.87s
user    0m10.13s
sys     0m4.62s
$ kill %1 %2 %3 %4

# The "sys" time (4.62 s) is time spent in the kernel,
# primarily on context switches and scheduler work.
# The wall-clock time has increased by nearly 50%
# even though the useful work (10.13 s user time) is unchanged.

This is the indirect cost made visible. The benchmark performs exactly the same useful work in both cases — the user time is essentially identical — but on the loaded system, nearly 4.6 seconds of extra CPU time are spent on context switches and scheduler work. The wall-clock time increases accordingly.

06 Interactive activity

Activity · observe and measure context switching on your VM

Open a terminal in your course VM and work through the following. The goal is to see context-switch activity directly, measure its cost, and connect it to the scheduling concepts from Lesson 3.4.

  1. Watch system-wide switches.
    $ vmstat 1 10
    Note the cs column. On an idle VM, what is the typical value? Now start a few background processes:
    $ for i in 1 2 3 4; do yes > /dev/null &; done
    Run vmstat again. How much higher is the cs column? What does this tell you about how the scheduler handles CPU-bound competition?
  2. Observe per-process switches. In another terminal, run:
    $ pidstat -w 1 5
    The cswch/s column is voluntary switches per second; nvcswch/s is involuntary. Which processes have high involuntary rates? Which have high voluntary rates? Relate this to the CPU-bound versus I/O-bound distinction from Lesson 3.4.
  3. Compare your shell's switches. Run the following before and after doing some work in your shell:
    $ grep ctxt /proc/$$/status
    The counts should increase as you run commands. Which of the two counters — voluntary or involuntary — grows faster? What does this tell you about your shell's workload?
  4. Build and run the measurement program. Copy the ctx.c program from the worked example into a file in your ~/comp3150/exercises/unit3/ directory, compile it, and run it. Record the reported per-switch cost. Then run it several times and note the variance. Is the cost stable, or does it fluctuate?
  5. Measure the effect of load. Start four CPU-bound processes as in step 1. Then run ctx again. Is the per-switch cost higher than it was on the idle system? By how much? This is the difference between the direct cost (which is largely fixed) and the effective cost when the CPU is contended.
  6. Compare process and thread switches. If you are comfortable with pthreads, write a variant of the program that uses two threads and a pair of condition variables, and compare its measured per-switch cost to the process-based version. If you are not yet comfortable with threads, skip this step — it will be revisited in Unit 4.
  7. Observe the effect on a workload. Compile the following CPU-bound benchmark:
    #include <stdio.h>
    int main(void) {
        volatile long x = 0;
        for (long i = 0; i < 500000000L; i++) x += i;
        printf("%ld\n", x);
        return 0;
    }
    Run it on a quiet VM with time ./benchmark, then run it again with four competitors started. Compare the user, sys, and real times. By how much has the wall-clock time increased? What fraction of the extra time is kernel time?
  8. Clean up.
    $ kill %1 %2 %3 %4 2>/dev/null

Reflect: which measurement best illustrates the difference between the direct and indirect costs of a context switch? What does the load experiment tell you about why real systems try to keep the running process running when the system is busy?

07 Knowledge check

Check your understanding

Question 1

What is the difference between a mode switch and a context switch?

Show answer
A mode switch is a change of CPU privilege level — user mode to kernel mode or back — without changing which process is executing. A system call that completes quickly requires only a mode switch. A context switch is a change of process: the kernel saves one process's state, restores another's, and switches the memory map. A context switch involves two mode switches (in and out of the kernel) plus the save-and-restore work in between, plus the cache and TLB pollution that follows. It is strictly a larger operation.

Question 2

Why is a thread context switch cheaper than a process context switch?

Show answer
Because threads in the same process share one address space. When switching between them, the kernel does not need to change the page-table base in the MMU, does not need to flush or retag the TLB, and does not cause the cache to become useless — the incoming thread is likely to find much of what it needs already in the cache. Only the registers and stack pointer need to be swapped. Between processes, all three memory-related costs apply. On modern hardware with ASIDs (or PCIDs on x86) the cost difference is somewhat smaller than it once was, but thread switches remain noticeably cheaper.

Question 3

A round-robin scheduler uses a quantum of 100 μs. The measured context-switch cost is 5 μs. What fraction of CPU time is spent on context switching? Is that acceptable?

Show answer
Each process runs for 100 μs and then the system spends 5 μs switching. So of every 105 μs, 5 μs is overhead — about 4.8%. That is borderline. It is acceptable for a general-purpose system, where response time matters and the CPU is often not saturated, but it would be unacceptable for a high-throughput server where every percentage point of CPU matters. On such a system you would want a longer quantum, or a scheduler that avoids preemption when the CPU is not contended. And this calculation ignores the indirect cost (cache and TLB pollution), which would raise the effective overhead substantially — perhaps to 15–30% of the CPU's effective throughput.

Question 4

What is the difference between a voluntary and an involuntary context switch, and what does a high rate of each indicate about the workload?

Show answer
A voluntary switch occurs when a process blocks on a system call — it chose to give up the CPU. A process that blocks frequently is I/O-bound, so a high rate of voluntary switches is the signature of an I/O-bound workload: the process is running briefly, blocking on I/O, and yielding the CPU each time. An involuntary switch occurs when the process is preempted by the scheduler. A high rate of involuntary switches indicates a CPU-bound workload in a contended system: the process would prefer to keep running, but the scheduler is taking the CPU away because other processes need it. The ratio between the two counters is therefore a useful classifier for a process's workload profile.

Question 5

Why does the effective cost of a context switch exceed the direct cost measured by the kernel? Give at least two reasons.

Show answer
The direct cost is what the kernel spends executing the switch itself: the scheduler's decision logic, saving and restoring registers, switching the memory map. The effective cost is larger because of indirect effects, of which two are most important. First, cache pollution: the incoming process's working set was evicted by the previous process, so its first accesses miss in L1, L2, and L3, each miss costing many cycles. Second, TLB misses: unless the CPU has ASIDs and the entries for the incoming process survive, the incoming process must re-walk the page table for every address until the TLB warms up again. Additional contributors include branch-predictor and prefetcher state that is now optimised for the wrong process. On a modern CPU the effective cost can be 5–10× the direct cost when the two processes have little cache overlap.

08 Unit discussion

Discussion prompt

Modern high-performance servers often go to considerable lengths to reduce context switches. Some use event-driven architectures with a single thread per CPU; others use kernel-bypass networking (DPDK, io_uring) to avoid entering the kernel at all for I/O; still others pin processes to specific CPUs and disable the timer interrupt entirely (isolcpus).

Is this trend — reducing context switches — a permanent shift, or a temporary response to specific hardware bottlenecks? Consider: as CPU speeds have stopped improving and core counts keep growing, has the relative cost of context switching gone up or down? What is the relationship between the number of cores and the frequency of preemption? Argue for your position with concrete reasoning.

Post your response to the Unit 3 discussion forum and reply to at least two classmates. This closes Unit 3. Supports CLO2, CLO3, and CLO6.

09 Practice exercise

This ungraded exercise consolidates the lesson and completes Unit 3. Aim to spend 45–60 minutes on it.

  1. Measure the switch rate under load. On an idle VM, record the cs column of vmstat 1 10. Then start five CPU-bound processes and record again. By how much did the rate increase? Divide the rate by the number of competing processes to estimate the timer frequency of your kernel. Does the result match the expected default (typically 250 or 1000 Hz)?
  2. Write a program that voluntarily switches. Write a C program that loops ten thousand times, printing nothing but calling sched_yield() on each iteration. Run it alongside pidstat -w 1. Does the voluntary switch count increase? Why is this different from the switches caused by preemption?
  3. Estimate the timer frequency. If you know the context-switch rate and the number of competing CPU-bound processes, you can estimate the timer frequency. On Linux, the default is usually 250 Hz or 1000 Hz. Does your measurement support this? If not, why might it be different? (Consider whether the scheduler is switching more or less often than the timer interval.)
  4. Read about ASIDs. Look up how address space identifiers work on x86-64 (PCID) and on ARM (ASID). What problem do they solve, and what is the residual cost of a process switch even with ASIDs? Write a short summary — two or three paragraphs — suitable for explaining the concept to a classmate.
  5. Explore voluntary vs involuntary switches. Find a process on your system with an unusually high involuntary-switch rate. What is it doing? What does this tell you about the workload on your system? Then find a process with an unusually high voluntary-switch rate and explain what that indicates.
  6. Reflect. In your own words, describe why the illusion of concurrent execution depends on the context switch and why the cost of the switch is not visible to the user. Then explain one concrete situation in which the invisible cost becomes visible — for example, on a heavily loaded server, or in a latency-sensitive application.
  7. Prepare for Unit 4. Based on what you learned in this lesson, predict whether a threading library that switches between threads within a single process would be faster or slower than one that switches between processes. Write two or three sentences justifying your prediction; you will check it in Unit 4.

10 Summary

  • A context switch saves one process's state and restores another's, so the second can resume where it left off.
  • Switches are triggered by blocking system calls, timer interrupts, I/O completion interrupts, and process termination — the same events that trigger scheduling decisions.
  • The steps are: enter the kernel, save the outgoing state, run the scheduler, restore the incoming state, switch the memory map, return from the trap.
  • Overhead comes from both the direct cost (executing the switch) and the indirect cost (cache and TLB pollution). The indirect cost often dominates and can raise the effective cost to 5–10× the direct measurement.
  • A mode switch changes privilege level within one process; a context switch changes which process is running. The two are commonly confused but are distinct operations.
  • Thread switches within a process are cheaper than process switches, because the address space and memory map stay the same.
  • Context-switch cost constrains scheduling policy: short quanta improve response time at the cost of switching overhead, while long quanta do the opposite.
  • Linux measures context switches through /proc/<pid>/status, vmstat, pidstat, and /proc/schedstat. Voluntary versus involuntary rates are a signature of a process's workload profile.

Unit 3 complete

This closes the process unit. Unit 4 extends the process abstraction with threads — lighter-weight units of execution that share an address space and, as this lesson has suggested, are cheaper to switch between. The foundation you have built here — process concepts, states, PCBs, scheduling, and context switching — is what makes the thread material intelligible.

11 References

  • Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 3, Section 3.4.
  • Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapter 6. Available online at ostep.org.
  • Love, R. Linux Kernel Development, 3rd ed., Chapter 4 (Process Scheduling), the section on context switching. Addison-Wesley, 2010.
  • Linux kernel source: kernel/sched/core.c (context_switch(), switch_mm()), arch/x86/include/asm/switch_to.h (the architecture-specific switch_to()).
  • Linux man-pages: man 1 vmstat, man 1 pidstat, man 1 perf-stat, man 5 proc.
  • Li, C., Ding, C., & Shen, K. Quantifying the Cost of Context Switching in Current Multiprocessor Systems. Technical report, Lehigh University, 2013.
  • Bach, M. J. The Design of the UNIX Operating System, Chapter 6. Prentice Hall, 1986. (Classical description of the context switch mechanism.)
👤