As we all know, Linux is a multitasking operating system that supports a much larger number of CPUs running at the same time. Of course, these tasks are not really running at the same time, but rather because the system allocates the CPU to them in turn in a very short period of time, creating the illusion of multiple tasks running at the same time.
Before running each task, the CPU needs to know where to load it and where to start running it, i.e., the CPU registers and program counters need to be set for it in advance.
What is CPU context?
The basic cycle of every CPU is to fetch the first instruction from memory, decode it to determine its type and operands, execute it, and then fetch, decode, and execute subsequent instructions. The cycle is repeated until the program finishes. In this way, programs are carried out.
Each CPU has a specific set of instructions that it can execute. Because accessing memory to get an instruction or data word takes much longer than executing an instruction, all CPUs contain some registers inside to hold key variables and temporary results.
Most computers have several special registers that are visible to the programmer. One of these is the program counter, which contains the memory address of the next instruction to be fetched. After that instruction has been fetched, the program counter is updated to point to its successor.
Another register is the stack pointer, which points to the top of the current stack in memory. The stack contains one frame for each procedure that has been entered but not yet exited. A procedure’s stack frame holds those input parameters, local variables, and temporary variables that are not kept in registers.
Yet another register is the PSW (Program Status Word). This register contains the condition code bits, which are set by comparison instructions, the CPU priority, the mode (user or kernel), and various other control bits. User programs may normally read the entire PSW but typically may write only some of its fields. The PSW plays an important role in system calls and I/O.
These text passages are excerpted from Modern Operating Systems book.
These registers are the CPU context.
What is CPU context switching?
This means that the CPU context of the previous task is saved, and then the context of the new task is loaded into these registers, before jumping to the new location indicated by the program counter to run the new task.
These saved contexts are stored in the system kernel and loaded again when the task is rescheduled for execution. This ensures that the original status of the task remains intact and that the task still appears to be running continuously.
Types of CPU context switching
There are generally three types(of course there are also concurrent context switches, etc., depending on the task):
- process context switching
- thread context switching
- interrupt context switching
Process context switching
Processes can run both in user space and in kernel space. When a process runs in user space, it is called the user mode of the process, and when it is trapped in kernel space, it is called the kernel mode of the process.
The transition from user mode to kernel mode is accomplished by system calls. For example, when we view the contents of a file, we need to make several system calls: first, open() to open the file, then read() to read the file contents, then write() to write the contents to standard output, and finally close() to close the file.
In this process, the CPU context switching occurs, and the whole process looks like this:
- Save the original user mode instruction bits in the CPU register.
- In order to execute the kernel mode code, the CPU registers need to be updated to the new location of the kernel mode instructions.
- Jump to the kernel mode to run the kernel task.
- When the system call is finished, the CPU register needs to be restored to the original saved user mode, and then switch to user space to continue running the process.
So, the process of a system call is actually two CPU context switches. (user mode ->kernel mode -> user mode)
However, it is important to note that the system call process does not involve process user mode resources such as virtual memory, nor does it switch processes. This is different from what we usually refer to as process context switching: process context switching refers to switching from one process to another process, whereas the system call process is always the same process running.
Therefore, the system call process is usually called privileged mode switching, not context switching. A system call is a CPU context switching within the same process. In practice, however, CPU context switching is unavoidable during a system call.
What is the difference between a process context switching and a system call?
First of all, processes are managed and scheduled by the kernel, and process switching can only occur in the kernel mode. Therefore, the process context includes not only resources in user space such as virtual memory, stack, and global variables, but also the mode of kernel space such as kernel stacks and registers.
Therefore, the process context switching is one more step than the system call: before saving the kernel mode resources (kernel mode and CPU registers of the current process), the user mode resources (virtual memory, stack, etc.) of the process need to be saved first; after loading the kernel mode of the next process, the virtual memory and user stack of the process need to be refreshed.
Potential performance problems with process context switching
According to Tsuna’s test report, each context switching requires tens of nanoseconds to several microseconds of CPU time. This is still a significant amount of time, especially when the number of process context switches is high, which can easily cause the CPU to spend a lot of time on saving and restoring resources such as registers, kernel stack, and virtual memory, which greatly reduces the time it takes to actually run the process. This is an important factor that leads to higher average load.
In addition, we know that Linux manages the mapping of virtual memory to physical memory via TLB (Translation Lookaside Buffer). When the virtual memory is updated, the TLB also needs to be refreshed and the memory access will be slowed down. Especially on multiprocessor systems where the cache is shared by multiple processors, refreshing the cache will affect not only the processes of the current processor, but also the processes of other processors sharing the cache.
Scenarios in which process context switching occurs
- the process terminates normally or due to some kind of error
- a new process is created or a waiting process becomes ready.
- when a process moves from the running status to the blocking status
- when a process changes from a running to a ready status
Thread context switching
The biggest difference between a thread and a process is that a thread is the basic unit of scheduling, while a process is the basic unit of resource ownership. To be clear, the so-called task scheduling in the kernel is actually scheduling for threads, while processes only provide virtual memory, global variables, and other resources to threads.
When a process has only one thread, it is considered to be a thread. — When a process has multiple threads, these threads share the same resources such as virtual memory and global variables. These resources do not need to be modified during context switching. — In addition, threads also have their own private data, such as stacks and registers, which need to be saved during context switching.
Scenarios in which thread context switching occurs
- The two threads before and after belong to different processes. In this case, since resources are not shared, the switching process is the same as process context switching.
- The two threads before and after belong to the same process. At this time, because the virtual memory is shared, the virtual memory resources remain unchanged during the switch, and only the private data, registers, and other unshared data of the switching thread are required.
Interrupt context switching
In order to quickly respond to hardware events, interrupt handling interrupts the normal scheduling and execution of processes and instead invokes an interrupt handler to respond to device events.
Unlike the process context, the interrupt context switching does not involve the user mode of the process. Therefore, even if the interrupt process interrupts a process that is in user mode, there is no need to save and restore the process’s virtual memory, global variables, and other user mode resources. The interrupt context, in fact, includes only the status necessary for the execution of the kernel mode interrupt service, including CPU registers, kernel stack, hardware interrupt parameters, and so on.(Under x86, the user mode and kernel mode address spaces are isolated, because the user mode context remains unchanged, and after the interrupt is complete, if there is no process switch, then the original process continues to execute.)
For the same CPU, the interrupt processing has a higher priority than the process, so the interrupt context switching does not occur at the same time as the process context switching. By the same token, since interrupts interrupt the scheduling and execution of normal processes, most interrupt handlers are short and compact in order to finish the execution as fast as possible.
In addition, just like process context switching, interrupt context switching also consumes CPU, and too many interrupts can consume a lot of CPU and even seriously degrade the overall performance of the system. So, if you find that the number of interrupts is too high, you need to pay attention to troubleshoot whether it will bring serious performance problems to your system.