Process switching (process context and interrupt context)
Process context VS interrupt Context1. Inner Space and user space
Kernel space and user space are two working modes of modern operating systems. kernel modules run in kernel space, while user-Mode Applications run in user space. They represent different levels and have different access permissions to system resources. The kernel module runs at the highest level (kernel state). All operations at this level are trusted by the system, while application programs run at a lower level (User State ). At this level, the processor controls direct access to hardware and unauthorized access to memory. The kernel state and user State have their own memory ing, that is, their own address space.
The processor is always in one of the following states:
1. the kernel state runs in the process context. The kernel indicates that the process runs in the kernel space;
2. the kernel state runs in the interrupt context. The kernel indicates that the hardware runs in the kernel space;
3. User State, running in user space.
The user space application enters the kernel space through the system call. The kernel indicates that the process runs in the kernel space, which involves context switching. The user space and the kernel space have different address ing, general or dedicated register groups, the user space processes need to pass a lot of variables and parameters to the kernel. The kernel also needs to save some registers and variables of the user process so that the system can return to the user space for further execution after the call is completed.
The so-called "process context" refers to the value in all the registers of the CPU, the status of the process, and the content on the stack when a process is executed. When the kernel needs to switch to another process, it needs to save all the statuses of the current process, that is, to save the context of the current process, so that when the process is re-executed, it can restore the status of the switch and continue to execute.
The hardware calls the interrupt handler through the trigger signal and enters the kernel space. In this process, some hardware variables and parameters must also be passed to the kernel. The kernel uses these parameters for interrupt processing.
The so-called "interrupt context" can also be seen as the parameters passed by the hardware and other environments that the kernel needs to save (mainly the currently interrupted process environment ).
When a process is executed, the values in all the registers of the CPU, the status of the process, and the content in the stack are called the context of the process. When the kernel needs to switch to another process, it needs to save all the statuses of the current process, that is, the context of the current process, so that when the process is re-executed, the status of the switch must be executed. In LINUX, the current process context is stored in the task data structure of the process. In the event of interruption, the kernel executes the interrupt service routine in the kernel state in the context of the interrupted process. However, all required resources are retained to resume the execution of interrupted processes when the relay service ends.
The Linux kernel works in the process context or interrupt context. The kernel code that provides the system call service indicates that the application initiating the system call runs in the process context; on the other hand, the interrupt processing program runs asynchronously in the interrupt context. The interrupt context is irrelevant to a specific process.
Context: the context is simply an environment, which is the environment when the process is executed. Specifically, it refers to various variables and data, including all register variables, files opened by processes, and memory information.
The context of a process can be divided into three parts: user-level context, register context, and system-level context.
User-level context: body, Data, user stack, and shared storage area;
Register context: General registers, program registers (IP), processor status registers (EFLAGS), and stack pointers (ESP );
System-level context: Process Control Block task_struct, memory management information (mm_struct, vm_area_struct, pgd, and pte), Kernel stack
When a process is scheduled, context switch is used for process switching. The operating system must switch all the information mentioned above before the newly scheduled process can run. The system calls mode switch ). Compared with process switching, mode switching is much easier and saves time, because the main task of mode switching is to switch the context of the process register.
Process context mainly involves exception handlers and kernel threads. The kernel enters the process context because some work of the process needs to be done in the kernel. For example, a system call serves the current process, and an exception is usually an error state caused by processing the process. So it makes sense to reference current in the process context.
The kernel enters the interrupt context because of the interrupt signal. The interrupt signal is random. The interrupt processing program and Soft Interrupt cannot predict which process is currently running in advance. Therefore, you can reference current in the interrupt context, but it does not make sense. In fact, the interruption signal that process A wants to wait may occur during the execution of process B. For example, process A starts the write operation on the disk, and process B is running after process A is sleeping. After the disk is written, the disk interruption signal interrupts process B, and process A is awakened during the process of interruption.
The kernel can be in two contexts: process context and interrupt context. After the system call, the user application enters the kernel space, and then the representatives of the corresponding processes in the user space run in the process context. Asynchronous interruptions may cause the interrupt handler to be called, and the interrupt handler runs in the interrupt context. The interrupt context and process context cannot occur simultaneously.
The kernel code running in the process context can be preemptible, but the interrupt context will continue to run until the end, and will not be preemptible. Therefore, the kernel restricts the interruption of context work and does not allow the kernel to perform the following operations:
(1) enter the sleep state or voluntarily discard the CPU;
Because the interrupt context does not belong to any process, it has nothing to do with current (although current points to the interrupted process at this time), once the interrupt context sleeps or gives up the CPU, it cannot be awakened. It is also called the atomic context ).
(2) occupying mutex;
Mutexes cannot be used to protect the resource in the critical section of the interrupt handle. If the semaphore is not obtained, the code will sleep and produce the same situation as above. If the lock must be used, use the spinlock.
(3) Execution of time-consuming tasks;
Interrupt Processing should be as fast as possible, because the kernel needs to respond to a large number of services and requests, and the interruption context takes too long to occupy the CPU, which seriously affects the system functions. When executing time-consuming tasks in the Interrupt Processing routine, it should be handled by the bottom half of the interrupt processing routine.
(4) access user space virtual memory.
Because the interrupt context is irrelevant to a specific process, it indicates that the hardware runs in the kernel space, so the virtual address of the user space cannot be accessed in the interrupt context.
(5) The Interrupt Processing routine should not be set to reentrant (a routine that can be called in parallel or recursively ).
When the interrupt occurs, both preempt and irq are disable until the interrupt is returned. The interrupt context and process context are different. Different instances of the interrupt processing routine cannot run concurrently on SMP.
(6) interrupt processing routines can be interrupted by more advanced IRQ.
If you want to disable the interrupt, you can define the interrupt processing routine as a fast processing routine, which is equivalent to telling the CPU that all interrupt requests on the local CPU are prohibited when the routine is running. This directly results in a system performance degradation due to delays in response to other interruptions.
Permission level:
RING0-> RING3: because a process belongs to a user process, it should not run on the privileged level 0 (ring0), so it is specified to run on ring3, switching from ring0 to ring3 is the primary solution. First, jmp cannot be used directly, in the 386 protection mode, this low-privilege jump will trigger a protection exception (# PF), so you can only seek another path. When a process requests a system call or the manufacturing software is interrupted, a switchover from ring3 to ring0 will occur. When the returned result is returned, you can use iretd to jump back to the original privileged level, the privileged access rules of the broken door are further proved. Therefore, the field can be forged and the ired method can be used to transfer to the low-privileged level. A simple code example is as follows:
Push ss
Push esp
Push eflags
Push cs
Push eip
Iretd
RING3-> RING0: After Entering ring3, there is also a problem of how to return data. This problem seems very simple. Simply using the call command is not enough? However, in order to protect the stack of each privileged level, the switch of the privileged level must be accompanied by the stack switch, which is also the reason why ss and esp should be pushed to the stack, however, when ring0 is returned, our user program cannot find the position of the system stack, so the system stack status can only be set under ring0. The following also gave rise to another question: Where can they store their values? Somewhere under ring0? OR a location in ring3? In fact, none of them work, because if the user program is not accessible under ring0, but under ring3, this violates the premise that we do not take the initiative to destroy stacks at different privileged levels. But there is always a solution, isn't it? The protection mode of 386 has already mentioned a mechanism for us to use TSS (Task status segment). Simply put, we store the stack information of ring0 by using some bits in tss, then load the tss segment in ring0. In the code
Xor eax, eax
Mov ax, SELECTOR_TSS
Ltr ax
Use the following command to modify stack information in tss:
Lea esp, [esp + P_STACKTOP]
Mov dword [tss + TSS3_S_SP0], eax
After the interrupt process is interrupted, the system automatically pushes ss, esp, eflags, cs, and eip to the user stack. Then, we save the values of other registers and push them ***, enable the interrupt, so that the high-priority interrupt can continue to be generated, then interrupt processing, clear the interrupt bit, recover the register value before the interrupt, and return the interrupt. The interrupt processing process is likely to be used in the stack. Therefore, you need to switch to the kernel stack. Note that the kernel stack here and the stack specified in tss are not one, the stack in tss is used for process return. It can be understood as an intermediary. Obviously, it is unwise to let the intermediary do too many things. Therefore, another kernel stack is used for interrupt processing.
Of course, there are still many details in this process. For example, during call save, stack switching has been implemented during the save process, because you cannot directly return ret, in this case, a ret_addr in the process struct is used to redirect. During the interrupt processing, the restart address is pushed into the stack, so that ret instead of iretd is used to return the code, which makes the code concise, save and return the code in the save and restart functions.