Implement Process Switching Based on Kernel stack switching in Linux-0.11, linux-0.11 Kernel
-
- The original lack of TSS-Based Task Switching
- Six-segment process switching Theory
- 1. interrupted access to the kernel
- 2. Find the PCB of the current process and the PCB of the new process.
- 3. Complete PCB Switching
- 4. Switch the kernel stack based on the PCB
- 5. Switch the running resource LDT.
- 6. Use the IRET command to switch the user Stack
1. Insufficient original TSS-Based Task Switching
The original Linux 0.11 uses TSS-based and one command. Although simple, the command takes a long time to run, and more than 200 clock cycles are required for task switching. The stack-based task switching may be fast, and the stack switching can also be implemented using the parallel Optimization Technology of instruction flow, which also simplifies the CPU design. Therefore, process/thread switching is not implemented using the TSS switching method provided by Intel, regardless of Linux or Windows, but is implemented through the stack.
2. Six sections of process switching
The basic idea of implementing process Switching Based on the kernel stack: when a process enters the kernel from the user State, stack switching will occur, and the user State information will be pushed to the kernel stack, this includes the command sequence EIP in user mode. For some reason, the process becomes congested, giving up the CPU and re-scheduling, the operating system will find the new process PCB, and complete the process and the new process PCB switch. If we associate the kernel stack with the PCB so that the operating system can complete the kernel stack switching during the PCB switching, executeIRETWhen the command is executed, the EIP of the new process is displayed, so that the user-state command sequence of the new process is executed, and the process switching is completed.The core of this switch is to build the kernel stack. You need to press the appropriate return address in the appropriate place and write the corresponding assembly code according to the kernel stack, precisely complete the inbound and outbound operations on the kernel stack, and bring up an appropriate return address in the appropriate place to ensure smooth process switching. At the same time, the kernel stack is associated with the PCB. During the PCB switch, the kernel stack is switched.
2.1 interrupt into Kernel
- Why should I go to the kernel?
Everyone knows,Operating SystemResponsible for process scheduling and switching, so process switching must be inKernel. To implement process switching, you must first enter the kernel. User Programs run in user mode. in Linux, the only way an application accesses the kernel isSystem CallThe application accesses the kernel through a number of system call functions provided by the operating system. When the process runs in the kernel, it may become blocked because it needs to access the disk file or because the time slice is exhausted, this leads to scheduling, giving you the right to use the CPU.
- Switch from user to kernel to stack
The core of system calls is commands.int 0x80This system call is interrupted. When a process is executed, there will be inter-function calls and variable storage, and these are all completed by stacks. A user stack is used when a process is running in the user mode and the kernel stack is used when the kernel mode is running. Therefore, when the execution system call is interruptedint 0x80Stack switching will occur when the user mode enters the kernel mode. Here we have to mention an important role of TSS. The address of the Process Kernel stack in the linear address space is specified by the ss0 and esp0 fields in the TSS segment of the task. the TSS of the current process can be found by using the TR register. That is to say,When the system enters the kernel state from the user State, the CPU automatically finds the TSS of the current process based on the TR register, and finds the position of the kernel stack based on the values of ss0 and esp0, switches the user stack to the kernel stack. TSS is a key bridge between user stack and kernel stack. It is very important to rewrite the process based on Kernel stack switching!
- What happened when a user enters the kernel?
When executedint 0x80The CPU automatically followsSS, ESP, EFLAGS, CS, EIPThese register values are pushed into the kernel stack.int 0x80The system has not yet entered the kernel, so the values of these five registers pushed into the kernel stack are values in the user State.EIPIsint 0x80Next statement"=a" (__res), The meaning of this statement isPut the register value represented by eax into the _ res variable. Therefore, when the application returns in the kernel, it will continue to execute the "= a" (_ res) statement.This process completes the first step in process switching,Through the ss and esp of the user stack in the kernel stack, the connection between the user stack and the kernel stack is established. In the image, a line is drawn between the user stack and the kernel stack, formed a set of stacks.
- Kernel stack details
What is the kernel stack of the parent process?
Runint 0x80Add SS, ESP, EFLAGS, CS, and EIP to the stack.
Add DS, ES, FS, EDX, ECX, and EBX to the stack in system_call.
system_call: cmpl $nr_system_calls-1,%eax ja bad_sys_call push %ds push %es push %fs pushl %edx pushl %ecx # push %ebx,%ecx,%edx as parameters pushl %ebx # to the system call movl $0x10,%edx # set up ds,es to kernel space mov %dx,%ds mov %dx,%es movl $0x17,%edx # fs points to local data space mov %dx,%fs call sys_call_table(,%eax,4) pushl %eax movl current,%eax cmpl $0,state(%eax) # state jne reschedule cmpl $0,counter(%eax) # counter je reschedule
InSystem_callComplete the corresponding system callSys_call_xxAnd then press the function's return value eax on the stack. If scheduling is triggered, the jump is executed.Reschedule. Otherwise, executeRet_from_sys_call.
1 reschedule:2 pushl $ret_from_sys_call3 jmp schedule
In executionScheduleBeforeRet_from_sys_callPress the stack becauseScheduleIs a c function, so}, EquivalentretCommand will pop upRet_from_sys_callJumpRet_from_sys_callRun.
In short, the kernel stack looks like this before the system call ends and the return is interrupted:
| Kernel stack |
| SS |
| ESP |
| EFLAGS |
| CS |
| EIP |
| DS |
| ES |
| FS |
| EDX |
| ECX |
| EBX |
| EAX |
| Ret_from_sys_call |
2.2 find the PCB of the current process and the PCB of the new process
- Current process PCB
The PCB of the current process is directed to by a global variable current.(Defined in sched. c)So current is the PCB of the current process.
- New process PCB
To obtain the PCB of the new process, we need to modify the schedule () function as follows:
void schedule(void){ int i,next,c; struct task_struct *pnext = &(init_task.task); struct task_struct ** p; /* add */ ...... while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS]; while (--i) { if (!*--p) continue; if ((*p)->state == TASK_RUNNING && (*p)->counter > c) c = (*p)->counter,next = i,pnext=*p; } /* edit */ if (c) break; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } switch_to(pnext,_LDT(next)); /* edit */}
In this way, pnext points to the PCB of the next process.
InSchedule ()When a function is calledSwitch_to (pent, _ LDT (next ))Will return the address in turn}Parameter 2_ LDT (next)Parameter 1PnextPress the stack. When executedSwitch_toCommandret, It will pop upSchedule ()Function}RunSchedule ()Function return instruction}. About executionSwitch_toKernel stack, which will be rewritten laterSwitch_toFunction is very important.
This will jumpSwitch_toThe kernel stack is as follows:
| Kernel stack |
| SS |
| ESP |
| EFLAGA |
| CS |
| EIP |
| DS |
| ES |
| FS |
| EDX |
| ECX |
| EBX |
| EAX |
| Ret_from_sys_call |
| Pnext |
| _ LDT (next) |
| } |
2.3 complete PCB switching 2.4 switch core Stack Based on PCB 2.5 switch operation resource LDT
These jobs will be rewritten.Switch_toComplete.
Remove the original switch_to implementation in Linux 0.11 and write a code based on Stack switching. To perform fine-grained operations on the kernel stack, You need to compile switch_to using assembly code,Since switch_to is implemented using assembly, it is most appropriate to put the implementation of switch_to in system_call.s.This function completes the following functions in turn: because it is a C language call assembly, you need to first process the stack frame in the Assembly, that is, to process the ebp register; then you need to retrieve the parameters that represent the PCB of the next process, and compare it with current. If it is equal to current, nothing needs to be done. If it is not equal to current, process switching starts, the PCB switching, Kernel stack pointer rewriting in TSS, Kernel stack switching, LDT switching, and PC pointer (CS: EIP) switching are completed in turn.
The basic framework of switch_to (system_call.s) is as follows:
1 switch_to: 2 pushl % ebp 3 movl % esp, % ebp 4 pushl % ecx 5 pushl % ebx 6 pushl % eax 7 movl 8 (% ebp ), % ebx 8 cmpl % ebx, current 9 je 1f10 switch rewrite of Kernel stack pointer in kernel 11 TSS 12 switch Kernel stack 13 switch LDT14 movl $0x17, % ecx15 mov % cx, % fs16 cmpl % eax, last_task_used_math // works with the following cuts to process coprocessor. Because it has little to do with the topic, 17 jne 1f18 clts19 1 is not discussed here: popl % eax20 popl % ebx21 popl % ecx22 popl % ebp23 ret
The core of understanding the above Code is to understand the stack frame structure and the transfer control method when calling functions.
Most programs on the CPU use stacks to support function call operations. Stack is used to pass function parameters, store the return address, temporarily Save the original register value for recovery, and store local data. The stack used by a single function call operation is calledStack frameStructure. Its structure is as follows:
1 movl % ebx, % eax2 xchgl % eax, current
- Overwrite the kernel stack pointer in TSS
As mentioned above, when the user State enters the kernel state, the CPU will automatically rely on the TR register to find the TSS of the current process, and then find the location of the kernel stack based on the values of ss0 and esp0, switches the user stack to the kernel stack. So we still need to have a current TSS, which we need to define in schedule. cstruct tss_struct *tss=&(init_task.task.tss)Such a global variable is the tss of the process no. 0. All processes share this tss and will not change during task switching.
Although all processes share one tss, the kernel stacks of different processes are different. Therefore, you must update the esp0 value in the tss during each process switchover, let it point to the kernel stack of the new process, and point to the bottom of the kernel stack of the new process, that is, ensure that the kernel stack is an empty stack at this time, both the frame pointer and the stack pointer point to the bottom of the kernel stack.
This is because each time a new process is interrupted and enters the kernel, its kernel stack should be an empty stack. For this reason, we also need to define:ESP0 = 4This is the Offset Value of esp0 in the kernel stack pointer in TSS so that esp0 can be found. The specific implementation code is as follows:
1 movl tss,%ecx2 addl $4096,%ebx3 movl %ebx,ESP0(%ecx)
- Kernel stack Switching
The kernel stack pointer (kernelstack) is not saved in the PCB definition of Linux 0.11. Therefore, the macro KERNEL_STACK is the Offset Value of the position you added, of course, you can add the kernelstack domain to any location in task_struct, but in some assembly files (mainly in system_call.s), there are some assembly hard codes about the operation structure, therefore, once the kernelstack is added, these hard encodings need to be modified. Since the first location, that is, the long state, has many hard encodings, The kernelstack must not be placed in the first location in task_struct, when it is placed in another location, modify the hard encoding in system_call.s.
Modify struct task_struct in schedule. h as follows:
1 struct task_struct {2 long state;3 long counter;4 long priority;5 long kernelstack;6 ......7 }
At the same time, define 'kernel _ STACK = 12' in system_call.s and modify the Assembly hard encoding. The modification code is as follows:
1 ESP0 = 4 2 KERNEL_STACK = 12 3 4 ...... 5 6 state = 0 # these are offsets into the task-struct. 7 counter = 4 8 priority = 8 9 kernelstack = 1210 signal = 1611 sigaction = 20 # MUST be 16 (=len of sigaction)12 blocked = (37*16)
The implementation code in switch_to is as follows:
1 movl %esp,KERNEL_STACK(%eax)2 movl 8(%ebp),%ebx3 movl KERNEL_STACK(%ebx),%esp
The definition of the PCB structure is changed here, so the PCB initialization of the 0 process also needs to be changed. The following changes must be made in schedule. h:
1 #define INIT_TASK \2 /* state etc */ { 0,15,15,PAGE_SIZE+(long)&init_task,\3 /* signals */ 0,{{},},0, \4 ......5 }
- LDT Switching
The implementation code in switch_to is as follows:
1 movl 12(%ebp),%ecx2 lldt %cx
Once the modification is complete, the ing table used by the next process to execute the user State program is its own LDT table, and address separation is implemented.
2.6 Use the IRET command to switch the user Stack
- PC Switching
For a switched process, when it is scheduled to run again, according to the kernel stack of the switched process, the last command of switch_toretThe command after switch_to () is displayed.}Continue execution as the return address.}Returned from the schedule () function.ret_from_sys_callExecute ret_from_sys_call as the return address, perform some processing in ret_from_sys_call, and finally ExecuteiretCommand to return the interruption. The command that appears where the user-state process is interrupted is used as the return address and continues to be executed from the interrupted process.
For a new process with a CPU, we need to modify the copy_process () function in fork. c and enter the kernel stack of the new process into a way to switch the PC. According to the experiment prompts, we can get the kernel stack of the new process ,:
1. align 2 2 2 switch_to: 3 pushl % ebp 4 movl % esp, % ebp 5 pushl % ecx 6 pushl % ebx 7 pushl % eax 8 movl 8 (% ebp ), % ebx 9 cmpl % ebx, current10 je 1f11 movl % ebx, % eax12 xchgl % eax, current13 movl tss, % ecx14 addl $4096, % ebx15 movl % ebx, ESP0 (% ecx) 16 movl % esp, KERNEL_STACK (% eax) 17 movl 8 (% ebp), % ebx18 movl KERNEL_STACK (% ebx ), % esp19 movl 12 (% ebp), % ecx 20 lldt % cx21 movl $0x17, % ecx22 mov % cx, % fs23 cmpl % eax, last_task_used_math24 jne 1f25 clts26 :27 popl % eax28 popl % ebx29 popl % ecx30 popl % ebp31 ret
The first_return_from_kernel code in system_call.s is as follows:
1 .align 2 2 first_return_from_kernel: 3 popl %edx 4 popl %edi 5 popl %esi 6 pop %gs 7 pop %fs 8 pop %es 9 pop %ds10 iret
The specific modification of copy_process () in fork. c is as follows:
1 ...... 2 p = (struct task_struct *) get_free_page(); 3 ...... 4 p->pid = last_pid; 5 p->father = current->pid; 6 p->counter = p->priority; 7 8 long *krnstack; 9 krnstack = (long)(PAGE_SIZE +(long)p);10 *(--krnstack) = ss & 0xffff;11 *(--krnstack) = esp;12 *(--krnstack) = eflags;13 *(--krnstack) = cs & 0xffff;14 *(--krnstack) = eip;15 *(--krnstack) = ds & 0xffff;16 *(--krnstack) = es & 0xffff;17 *(--krnstack) = fs & 0xffff;18 *(--krnstack) = gs & 0xffff;19 *(--krnstack) = esi;20 *(--krnstack) = edi;21 *(--krnstack) = edx;22 *(--krnstack) = (long)first_return_from_kernel;23 *(--krnstack) = ebp;24 *(--krnstack) = ecx;25 *(--krnstack) = ebx;26 *(--krnstack) = 0;27 p->kernelstack = krnstack;28 ......29 }
Finally, note that because switch_to () and first_return_from_kernel are both implemented in system_call.s. c and fork. c, the two labels must be declared as global in system_call.s and referenced to their. the c file declares that they are an external variable.
The Code is as follows:
Global declaration in system_call.s
1 .globl switch_to2 .globl first_return_from_kernel
Corresponds to the external variable declaration in the. c file:
1 extern long switch_to;2 extern long first_return_from_kernel;