Transplantation and Analysis of uClinux (3)

Source: Internet
Author: User

Code implementation for process switching

After Linux is transplanted, the modification mainly involves the platform-related code. the platform-related code in Linux includes many aspects, such as the boot process, system calling, interrupt processing, device drive, and some signal (Soft Interrupt) processing, process switching also has a small part of platform-related code. compared with other parts, I think this part of the platform-related code is relatively simple.

Schedule () is a function for implementing process scheduling in uClinux. use Certain algorithms for scheduling. assume that schedule () is called when two processes A, B, and a are running. Then, the OS selects a proper process from the process readiness queue. If no proper process exists, then, run a. If process B is found, switch from process a to process B. this switching process is performed in switch_to.

Switch_to () appears in the Schedule () function. Call form: switch_to (prev, next, last); Prev, next is the pointer of the Process Control Block task_struct. Prev points to the currently running process, and next points to the process to be switched.

Let's talk about the code I transplanted. Since the code is a assembler, first introduce the CPU structure. The cpu I use uses 16-bit commands, 32-bit addresses and data. There are 16 General registers recorded as R0-R15. R0 as the stack pointer register sp, R1 usage is not fixed, r2-r6 as the parameter transfer register, function call if there are no more than five parameters, the parameters are placed in R2-R6 from left to right. At the same time, R2 is also used as the function return value register, and all the function return values are placed in R2. The r7-r14 is a local variable register. R15 is the function return address register, also called Link register. It stores the return address of the function call location.

# Define switch_to (prev, next, last ){/
(1) register void * _ Prev _ ASM _ ("R2") = (prev );/
(2) register void * _ next _ ASM _ ("R3") = (next );/
(3) register void * _ last ;/
(4) _ ASM _ volatile __(/
(5) "jbsr" symbol_name_str (resume) "/n/t "/
(6) "mfcr % 0, SS4 "/
(7): "= r" (_ last )/
(8): "R" (_ PREV ),/
(9) "R" (_ next )/
(10): "R2", "R2", "R3 ");/
(11) (last) = _ last ;/
}
Switch_to () is equivalent to preparing for calling resume. (1)-(2) means to put the Variable _ Prev and _ next in register R2 and R3 respectively. Their values are equal to Prev and next, which are the pointers of two task_struct. In this case, prepare parameters for calling resume. The third row declares a register Temporary Variable _ last.

Line (5) calls the Resume function to implement process switching. Jbsr is a jump command, literally jump to a subroutine (jump to subroutine ), this command saves the current PC + 2 to R15 (because it is a 16-bit command, so + 2), which is equivalent to saving the return value of the function, then, set the PC to the address given in the Assembly command parameters (that is, jump, where is the resume address ).

Row (6) places the content of the control register SS4 in the register corresponding to _ last. There are some trick commands in this line. Let's talk about the operations performed by the commands first, and then why. Mfcr is a command to move from a control register to a general register. In addition to 16 General registers, the CPU also has 16 control registers. All operations involving control registers must be performed in the superuser mode of the CPU. To switch the CPU mode, you can set the 0th control register. The 16 control registers are cr0-cr15, where Cr0 is also called the state register of the program. Cr6-cr10
A ss0-ss4 is also called a register used to save status. The Code (6) puts the SS4 content in the register corresponding to Variable _ last.

For the meaning of line (7)-(10), see at&t assembly.

(11) The row is a value assignment, last = _ last.

Switch_to () is equivalent to preparing for calling resume. (1)-(2) means to put the Variable _ Prev and _ next in register R2 and R3 respectively. Their values are equal to Prev and next, which are the pointers of two task_struct. In this case, prepare parameters for calling resume. The third row declares a register Temporary Variable _ last.

Line (5) calls the Resume function to implement process switching. Jbsr is a jump command, literally jump to a subroutine (jump to subroutine ), this command saves the current PC + 2 to R15 (because it is a 16-bit command, so + 2), which is equivalent to saving the return value of the function, then, set the PC to the address given in the Assembly command parameters (that is, jump, where is the resume address ).

Row (6) places the content of the control register SS4 in the register corresponding to _ last. There are some trick commands in this line. Let's talk about the operations performed by the commands first, and then why. Mfcr is a command to move from a control register to a general register. In addition to 16 General registers, the CPU also has 16 control registers. All operations involving control registers must be performed in the superuser mode of the CPU. To switch the CPU mode, you can set the 0th control register. The 16 control registers are cr0-cr15, where Cr0 is also called the state register of the program. Cr6-cr10
A ss0-ss4 is also called a register used to save status. The Code (6) puts the SS4 content in the register corresponding to Variable _ last.

For the meaning of line (7)-(10), see at&t assembly.

(11) The row is a value assignment, last = _ last.

In fact, the above is not a very optimized approach. You can save the _ last variable. However, when I first started, I saw that the _ last variable was used in the m68k version, but I was not very clear about its role. To prevent errors, I did it as usual. After further analysis, we can see that this variable is actually redundant.

So why do we need code with lines (6) and (11? Let's look back at the schedule () code. After switch_to () is called, schedule () calls the schedule_tail (prev) function. Obviously, Prev should be placed in R2. therefore, line (11) of switch_to () code is available. So why does Prev come from SS4?

Prev is stored in R2. The content in R2 belongs to the context of the process. During process switching, it must be stored in the stack. When switching to another process, you must load the context of the other process into the register. When a new process is loaded, the value of R2. For example, you have created a new process through a fork system call. As we know, if the fork return value is 0, it indicates the child process. If it is greater than 0, it indicates the parent process. For sub-processes, R2 in this stack is 0 (as mentioned earlier, R2 is used as the function return value). If schedule selects a fork sub-process to start execution, after switching to this sub-process, its R2 is obviously 0, of course it is not Prev. Therefore, my implementation is to store the R2 value in SS4 during the process switch. After the switch is complete, we will make a difference. If two processes have been switched, return to the original place of switch_to. If it is a new fork process, it is called for the first time. When resume returns, ret_from_fork is returned. This is another process.

(11) The row is a value assignment, last = _ last.

In fact, the above is not a very optimized approach. You can save the _ last variable. However, when I first started, I saw that the _ last variable was used in the m68k version, but I was not very clear about its role. To prevent errors, I did it as usual. After further analysis, we can see that this variable is actually redundant.

So why do we need code with lines (6) and (11? Let's look back at the schedule () code. After switch_to () is called, schedule () calls the schedule_tail (prev) function. Obviously, Prev should be placed in R2. therefore, line (11) of switch_to () code is available. So why does Prev come from SS4?

Prev is stored in R2. The content in R2 belongs to the context of the process. During process switching, it must be stored in the stack. When switching to another process, you must load the context of the other process into the register. When a new process is loaded, the value of R2. For example, you have created a new process through a fork system call. As we know, if the fork return value is 0, it indicates the child process. If it is greater than 0, it indicates the parent process. For sub-processes, R2 in this stack is 0 (as mentioned earlier, R2 is used as the function return value). If schedule selects a fork sub-process to start execution, after switching to this sub-process, its R2 is obviously 0, of course it is not Prev. Therefore, my implementation is to store the R2 value in SS4 during the process switch. After the switch is complete, we will make a difference. If two processes have been switched, return to the original place of switch_to. If it is a new fork process, it is called for the first time. When resume returns, ret_from_fork is returned. This is another process.

As I have said so much above, the readers may be confused. I also feel that I have not made it clear, so the implementation here is a little trick, you must be familiar with the CPU abi and Linux kernel code.
(11) LDW R7, (R0)/* restore R7 */
(12) LDW R8, (r0, 4)/* restore R8 */
(13) addi r0, 8
(14) save_switch_stack
(15) lrw R8, task_thread/* the position of thread in task_struct */
(16) Addu R8, r2
(17) mfcr R6, SS1/* Get current USP */
(18) STW R6, (R8, thread_usp)/* save USP in task struct */
(19) STW r0, (R8, thread_ksp)/* save KSP in task struct */

(20) lrw R8, task_thread
(21) lrw R7, symbol_name (_ current_task)
(22) STW R3, (r7)/* set new task */
(23) Addu R8, R3/* pointer to thread in task_struct */

/* Set up next process to run */
(24) LDW r0, (R8, thread_ksp)/* set next KSP */
(25) LDW R6, (R8, thread_usp)/* set next USP */
(26) MTCR R6, SS1
(27) LDW R7, (R8, thread_sr)/* set next PSR */
(28) MTCR R7, SRS
(29) restore_switch_stack
----------------
| R11 |
----------------
| R10 |
----------------
| R9 |
----------------
| R8 |
----------------
| R7 |
----------------
| R6 |
----------------
| R5 |
----------------
| R4 |
----------------
| R3 |
----------------
| R2 |
----------------
0x1effc4 | R1 |
----------------
0x1f0000 and 0x1effc4 are the R0 values before and after execution (14. This is a contex save operation.

Note: lrw indicates the number loading operation immediately, Addu indicates the unsigned addition, mfcr and MTCR indicate the register moving operation, bclri indicates the bit clearing operation, and LDW indicates the load word operation, addi is an immediate addition operation.

(15)-(19) is the stack pointer saving operation. Save the current process user stack and kernel stack to the corresponding data structure of the Process Control Block. In Linux, each process except the kernel thread (only the kernel stack) has two stacks, one in the user space and the other in the kernel space. If it is a kernel thread, you don't have to worry about its user stack. It won't be used anyway. It can be anything. If a user process is called by the user process execution system or is interrupted during execution of the user process, the user space needs to enter the kernel space, the previous user space pointer will be temporarily stored in ss1. Therefore, the rows (17)-(18) Fetch the user space pointer from SS1 and store it in task_struct. (15)-(19) operations can be summarized:
Prev-> thread. USP = SS1 Save User pointer
Prev-> thread. KSP = R0 Save the Kernel Pointer

So someone may ask, can SS1 ensure that it is the correct current user Stack pointer? Of course, this value does not matter because the kernel thread does not have a user stack. For user processes, the only entry to resume is schedule, which is the kernel code of the operating system. When a user process enters the kernel, there is a system call and interruption, and the user stack is saved to ss1, as long as all kernel stacks are used in the kernel, the user's stack pointer will not change.

(20)-(23) the operation executed is equivalent to _ current_task = next. I will not explain it in detail.

(24)-(28) Prepare for loading the context of the new process, that is, prepare for Loading next.

(24)-(25) is the kernel and user stack of the next process. Because the process context is stored in the kernel stack of the process, the first step is to load the stack pointer of the process. (27)-(28) is the status information before the next process is loaded. (26) Update SS1. Now we have to mount a new process. Of course we have to set up a new user stack.

(29) is the context for loading the next process. The next process has the same context in the stack and is loaded now.

(30) is returned by the function call. If the process is a child process that has just been fork, R15 = ref_from_fork In the context (see the copy_thread function); otherwise, the position of the sentence (6) in switch_to is returned.

The above is the process switching part. This part is related to the platform. The above is the code I implemented. I feel that the efficiency is not very high, but the function is correct. Maybe I have not made it very clear in some places. If you have any questions, please submit them.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.