This is the fork in the UNIX standard system call, and the core is, of course, the copy on write technology
As for why copy on write is used because the child process is likely to execute exec () immediately after it is created, the series of copies previously made is useless
So, when creating a new subprocess, it is only necessary to copy the parent process's memory Map (page table), and to mark all of the parent process's memory-mapped pages as read-only, so that when the child process or parent process attempts to read it is safe, and when trying to write, it will go page fault, In the page fault processing routine, a separate copy of the page (such as a stack) is written, and the corresponding mapping of the page table of the process that made the write behavior is modified.
So, the first step should be to define or establish a page fault processing routine.
Each process needs to register this processing routine with the kernel, just pass a function pointer to
Sys_env_set_pgfault_upcall function
Sets the page fault processing routine of the current process to the function that the Func points to
Normal and exception stacks in user environments
There is a third stack, the exception stack
In the normal operation of the user program, out of the normal user stack, the user stack at the top of the Ustacktop
The exception stack is created for the exception handling routines set above. When an exception occurs, and the user process registers the exception's processing routine, it goes to the exception stack and runs the exception-handling routine
There are three stacks in the current position:
- [Kstacktop, Kstacktop-kstksize]
Kernel-State system stack
- [Uxstacktop, Uxstacktop-pgsize]
User-state error handling stack
- [Ustacktop, Utext]
User state run Stack
- For kernel-State system stacks
is the stack running kernel-related programs, after the interrupt is triggered, the CPU will automatically switch the stack to the kernel stack, and the kernel stack is set in the Kern/trap.c trap_init_percpu ()
You can see that the size of the kernel stack is fixed and is set when the system is initialized.
After the interrupt is triggered, enter Kern/trapentry. s code, the stack at this point has switched to the kernel stack, and a series of content is pressed into the kernel stack, manually forming a trapframe
is the stack used in the user's run, which is initialized at the beginning of the user process creation.
The actual application process in the case of initialization is only one page size, if a stack overflow occurs, the page error will be triggered, but the user space in the page error is a handler, will be the bottom of the stack page map to the current memory space
So, the user runs the stack is on one side of the growth, while the kernel stack is fixed size, the error stack is also
After the user has registered their own interrupt handlers, the corresponding routines run at the stack
The process is as follows
- First into the kernel, the stack position from the user run stack switch to the kernel stack, into the trap, interrupt processing distribution, into the Page_fault_handler ()
- When the user program triggers the page fault (the kernel triggers a direct panic), it assigns a utrapframe size to the user error stack.
- Switch the stack to the user error stack and run the response user interrupt Handler
- The interrupt handler may trigger another interrupt of the same type, which will result in recursive processing
- After processing is complete, return to the user run stack
Invoking the user page fault handler
When the user process runs out of error, and for this error, the user has defined their own exception handling routines, according to the previous argument, it is necessary to switch to the exception stack to perform
So how do you switch the past?
A user-defined user process can be treated as a function call, and when an error occurs, a function is called, but actually the current process is not changed.
So when switching to the exception stack, still running the current process, but just run the interrupt handler function, so that the stack pointer has changed, and the program counter EIP has changed, but also need to know is the address where the error is thrown. These are the messages that need to be passed when switching to the exception stack.
And before switching from the user stack to the kernel stack, this is done by constructing the struct on the stack, passing the pointer
Here, a new structure is defined to record the information when a user-defined error occurs utrapframe
There are more utf_fault_va here than Utrapframe, because the memory address that triggered the error is logged
At the same time also less es,ds,ss and so on. Because switching from the user state stack to the exception stack, or from the exception stack to switch back, is actually a user process, so does not involve the segment of the switch, no record
In practice, Trapframe is present as a struct that records the complete state of a process, and is passed as a function parameter, while Utrapframe only uses when handling user-defined errors.
Overall, when a page fault occurs during normal execution, the switch of the stack is
User Run Stacks---> Kernel stacks---> Exception stacks
If an error occurs in the exception handler, the switch to the stack is
Exception Stacks---> Kernel stacks---> Exception stacks
The next step is to implement the Page_fault_handler function
If you are already on the user error stack, then need to set aside 4 bytes, otherwise do not need, specific and jump mechanism is related
Simply put aside the space for saving Utrapframe at the top of the current error stack, and then copy the parameters from the TF.
Modify the program counter and stack pointer for the current process, and then restart the process, and the interrupt handler will be run on the user error stack.
Of course, after the interrupt handler has finished running, it needs to go back to the user run stack, which is what the exception handler needs to do.
Here's one more question.
What happens if the exception stack overflow?
Just look at memlayout.h and you'll know.
You can see that the user exception stack on the size of a page, once overflow, access is the kernel does not have access to the space, will occur in the Kernel Space page fault, this time will be directly panic, will not cause more serious consequences
The next step is to write a part of the assembler, the main implementation of the function is: After the user-defined processing function returned, how to return from the user error stack directly to the user run stack. This Part I have a headache, direct reference to Zhang Yishi Brother's code
There is also the Set_pgfault_handler () function, mainly for the process set processing process, while allocating the error stack
Next is the most important part: implementing the Copy-on-write Fork
Unlike the previous dumbfork, when a child process is forked, the first thing to do is to copy the entire map of the parent process's page table to the child process's address space.
This time the physical page will be mapped at the same time by two processes, but it should be isolated at the time of writing. The way to do this is to mark all the parts of the parent process space that can be written in the page table as readable when the child process is mapped, and cow
And when the parent process or the child process any of the time the write, because the page is not writable, so it will trigger an exception, into the page we set the fault processing routine, when it detects that the cow page is a write operation, you can write the contents of the page will be copied one copy, remap.
At first I was thinking that when you go to page fault to process a routine, after you copy it, you can mark the page as writable, so that another process does not trigger an exception when it is written. But I'm still naive, because the fork operation can be nested multiple layers, so I don't know how many processes have mapped to this page.
So the way to do this is, when the process triggers page fault, after the corresponding page has been copied, the mapping of the original page needs to be solved, the page_remove can be called, and the function will decide whether to recycle based on the reference count of the page, so there is no memory leak.
First look at page fault processing routines
This logic is still quite clear, here borrowed a certain will not be used in the location of Pftemp, dedicated to the occurrence of page fault when the copy content.
It is important to note that before the map must be unmap, otherwise, if the child process and the parent process are written to the same location, all copied a copy, then the original copy is useless, and without unmap operation, this page of the reference count will never return to zero.
And then look at the Duppage function.
The function is to map the physical page corresponding to the PN page of the current process to the PN page of Envid and mark the page as Cow
Fork function
The mapping needs to be copied in the past, where the range of addresses to consider is from Utext to Uxstacktop, and the scope above is because they are all the same, the Env_alloc has been set up, so there is no need to consider
The first step is to set the error-handling routine for the parent process, where the Set_pgfault_handler function is called because it is not currently known whether the parent process has established an error stack, and if it does not, it will create a
And Sys_env_set_pgfault_upcall doesn't build the wrong station.
Call Sys_exofork to prepare a child process with the same state as the parent process, and the state is temporarily set to Env_not_runnable
Then copy the part of the mapping, in the page table of the current process all the pages marked as pte_p need to be copied into the child process space
However, one exception is the need for a new page to copy the content, that is, the user error stack
Because the copy-on-write is to rely on user error stack implementation, so that this stack to fork when the completion of each process has one, so to hard copy over
It looks rather awkward here, the process is:
- Request a new physical page, mapped to the (uxstacktop-pgsize) location of the child process
- The pftemp location of the parent process is also mapped to the physical page of the child process's new request, so that the parent process can also access the page.
- In the parent process space, copy the user error stack to the error stack of the child process, which is the page you just requested.
- The parent process then de-pftemp the map
Finally, the state of the handle process is set to run.
MIT 6.828 jos/xv6 LAB4-PARTB