03 Fundamentals the Meaning of Context Switch in CPU Part One

03 Fundamentals The Meaning of Context Switch in CPU Part One #

Hello, I’m Ni Pengfei.

In the previous lesson, I explained how to understand the average load and demonstrated analysis methods for situations where the average load increases using three cases. Among these, the issue of multiple processes competing for the CPU is often overlooked.

You must be wondering why the system’s load increases even when processes are not actually running when they compete for the CPU. By now, you should already have guessed that CPU context switching is the culprit.

We all know that Linux is a multitasking operating system that supports running tasks much more than the number of CPUs. Of course, these tasks are not actually running at the same time. Instead, the system allocates the CPU to them one by one in a very short period of time, creating the illusion of multitasking.

Before each task runs, the CPU needs to know where the task is loaded from and where it starts running, which means the system needs to set up CPU registers and the program counter (PC) for it in advance.

CPU registers are small but extremely fast memory embedded in the CPU. The program counter is used to store the position of the instruction being executed by the CPU or the position of the next instruction to be executed. They are essential dependencies for the CPU before running any task and are therefore called CPU context.

Knowing what CPU context is, I think you can easily understand CPU context switching. CPU context switching is the process of first saving the CPU context of the previous task (i.e., CPU registers and the program counter), then loading the context of the new task into these registers and the program counter, and finally jumping to the new location indicated by the program counter to run the new task.

The saved contexts are stored in the system kernel and are loaded again when the task is rescheduled for execution. This ensures that the original state of the task is not affected and the task appears to run continuously.

I guess someone will say that CPU context switching simply updates the values of CPU registers. However, these registers are designed for fast task execution, so why do they affect the CPU performance of the system?

Before answering this question, have you ever wondered what these “tasks” managed by the operating system actually are?

You might say that tasks are processes, or tasks are threads. Yes, processes and threads are indeed the most common tasks. But besides them, are there any other tasks?

Don’t forget that hardware triggers signals, which lead to the invocation of interrupt handlers, and this is also a common task.

Therefore, depending on the different tasks, CPU context switching can be divided into several different scenarios, namely process context switching, thread context switching, and interrupt context switching.

In this lesson, I will show you how to understand these different context switches and why they can cause CPU performance-related issues.

Process Context Switching #

Linux divides the process execution space into kernel space and user space according to privilege levels, corresponding to Ring 0 and Ring 3 of the CPU privilege levels in the following figure.

  • Kernel space (Ring 0) has the highest privileges and can directly access all resources;

  • User space (Ring 3) can only access restricted resources and cannot directly access memory and other hardware devices. It must enter the kernel through a system call to access these privileged resources.

From another perspective, a process can run in both user space and kernel space. When a process is running in user space, it is called user mode, and when it enters the kernel space, it is called kernel mode.

The transition from user mode to kernel mode needs to be done through a system call. For example, when we view the contents of a file, multiple system calls are needed: first, we use the open() system call to open the file, then use the read() system call to read the file contents, and the write() system call to write the contents to standard output, and finally the close() system call to close the file.

So, does the process of a system call involve a context switch? The answer is yes.

The original user mode instruction location in the CPU registers needs to be saved first. Then, in order to execute kernel mode code, the CPU registers need to be updated with the new position of the kernel mode instructions. Only then can the jump to kernel mode to run kernel tasks occur.

After the system call is completed, the CPU registers need to be restored to the original saved user mode, and then switch back to user space to continue running the process. Therefore, a system call actually involves two CPU context switches.

However, it is important to note that the system call process does not involve the virtual memory and other user mode resources of the process, nor does it switch processes. This is not the same as what we usually refer to as process context switching:

  • Process context switching refers to switching from one process to another for execution.

  • While in the system call process, the same process is running all the time.

Therefore, the system call process is usually referred to as a privilege mode switch, not a context switch. However, in reality, CPU context switches still occur during the system call process.

So, what is the difference between process context switching and a system call?

First, you need to know that processes are managed and scheduled by the kernel. Process switching can only occur in kernel mode. Therefore, the process context includes not only user space resources such as virtual memory, stack, and global variables, but also kernel space states such as kernel stacks and registers.

Therefore, process context switching involves an additional step compared to a system call: before saving the current process’s kernel states and CPU registers, the process’s virtual memory and stack need to be saved; and after loading the kernel mode of the next process, the process’s virtual memory and user stack need to be refreshed.

As shown in the following figure, the process of saving and restoring the context is not “free” and requires the kernel to run on the CPU to complete.

According to a test report by Tsuna, each context switch requires tens of nanoseconds to a few microseconds of CPU time. This time is quite significant, especially when there are frequent process context switches, which can easily cause the CPU to spend a lot of time on saving and restoring resources such as registers, kernel stacks, and virtual memory, thus greatly reducing the actual running time of the process. This is also an important factor in increasing the average load, as discussed in the previous section.

In addition, we know that Linux manages the mapping relationship between virtual memory and physical memory through the Translation Lookaside Buffer (TLB). When the virtual memory is updated, the TLB also needs to be refreshed, and memory access will become slower. Especially on multiprocessor systems, caches are shared by multiple processors. Refreshing the cache will not only affect the process on the current processor but also affect the process of other processors that share the cache.

Now that we understand the potential performance issues of process context switching, let’s look at when process context switching occurs.

Obviously, process context switching only occurs when a process is switched. In other words, process context switching only occurs during process scheduling. Linux maintains a ready queue for each CPU, which sorts active processes (i.e., running processes and waiting processes for the CPU) based on priority and wait time for the CPU, and selects the process that needs the CPU the most, that is, the process with the highest priority and the longest wait time for the CPU, to run.

So, when will a process be scheduled to run on a CPU?

One of the easiest scenarios to think of is when a process has finished execution and releases the CPU it was using, a new process will be taken from the ready queue to run. In fact, there are many other scenarios that can trigger process scheduling, and I will explain them one by one.

First, to ensure fair scheduling of all processes, CPU time is divided into time slices, which are then assigned to different processes in a round-robin manner. This way, when a process’s time slice is exhausted, it will be suspended by the system and switched to another process waiting for the CPU to run.

Second, when system resources are insufficient (such as insufficient memory), a process needs to wait until the resources are available before it can run. At this time, the process will also be suspended and the system will schedule other processes to run.

Third, when a process voluntarily suspends itself, for example, by using the sleep function, it will naturally be rescheduled.

Fourth, when there is a process with a higher priority running, in order to ensure the running of the high-priority process, the current process will be suspended and the high-priority process will run.

The final scenario is when a hardware interrupt occurs, the process on the CPU will be interrupted and suspended and the kernel’s interrupt service routine will be executed.

It is necessary to understand these scenarios because once there are performance issues with context switching, they are the culprits behind the scenes.

Thread Context Switch #

After discussing context switching in processes, let’s take a look at the issues related to threads.

The biggest difference between threads and processes is that threads are the basic unit of scheduling, while processes are the basic unit of resource ownership. In other words, in terms of task scheduling in the kernel, the scheduling object is actually the thread, while the process only provides resources such as virtual memory and global variables to the thread. Therefore, we can understand threads and processes as follows:

  • When a process has only one thread, the process can be regarded as equivalent to a thread.

  • When a process has multiple threads, these threads will share the same virtual memory, global variables, and other resources. These resources do not need to be modified during context switching.

  • In addition, threads also have their own private data, such as stack and registers, which need to be saved during context switching.

With this in mind, the context switching of threads can be divided into two cases:

The first case is when the two threads belong to different processes. In this case, because the resources are not shared, the switching process is the same as the context switching in processes.

The second case is when the two threads belong to the same process. In this case, because the virtual memory is shared, the shared resources such as virtual memory remain unchanged during switching, and only the thread’s private data, registers, and other non-shared data needs to be switched.

By now, you may have also noticed that although both involve context switching, thread switching within the same process consumes fewer resources compared to switching between multiple processes. This is precisely one advantage of using multithreading instead of multiprocessing.

Context Switching in Interrupts #

In addition to the two types of context switching mentioned before, there is another scenario where CPU context switching occurs, and that is in interrupts.

In order to quickly respond to hardware events, interrupt handling interrupts the normal scheduling and execution of processes, and instead calls interrupt handlers to respond to device events. When interrupting other processes, the current state of the process needs to be saved, so that the process can continue running from its original state after the interrupt ends.

Unlike process context, interrupt context switching does not involve the user mode of a process. Therefore, even if the interrupt process interrupts a process that is in user mode, there is no need to save and restore user mode resources such as virtual memory and global variables. The interrupt context only includes the necessary state for the execution of the kernel-level interrupt service routine, including CPU registers, kernel stack, hardware interrupt parameters, etc.

For the same CPU, interrupt handling has a higher priority than processes, so interrupt context switching does not occur simultaneously with process context switching. Similarly, because interrupts interrupt the normal scheduling and execution of processes, most interrupt handlers are short and efficient, in order to finish their execution as quickly as possible.

Additionally, like process context switching, interrupt context switching also consumes CPU. Too many context switches can consume a large amount of CPU resources and even significantly degrade the overall performance of the system. Therefore, when you find that there are too many interrupts, you need to investigate whether they are causing serious performance problems in your system.

Summary #

To sum up, regardless of the scenario that leads to context switching, here are some key points you should know:

  1. CPU context switching is one of the core functionalities that ensures the normal operation of the Linux system, and in general, it does not require special attention from us.

  2. However, excessive context switching can consume CPU time in saving and restoring data such as registers, kernel stacks, and virtual memory, thereby reducing the actual running time of processes and significantly decreasing the overall system performance.

Today, I mainly introduced the working principles of these types of context switching. In the next section, I will continue with practical examples and discuss the analysis methods for context switching issues.

Reflection #

Finally, I would like to invite you to discuss the CPU context switch and your understanding of it. You can summarize your thoughts and opinions based on today’s content and write down your learning experience.

Feel free to discuss with me in the comments.