The way I do my context switching is that when I decide I need to switch, I call the switch function that:
pushes the registers onto the stack,
saves the current stack pointer,
loads the new one,
[optionally copies some memory] <---- used by fork
pops the registers,
sets the curr_process variable.
So, I implemented fork by:
copying all user memory, and creating new pages tables,
allocating a kernel stack for the new process (all the kernel stacks are always mapped in the higher half),
making a "fake" switch, a switch to myself, that uses the copy option in the context switcher to copy the parent kernel stack to the child kernel stack.
adjusting the saved stack pointer of the child (child is switched out) to point to his own kernel stack.
after this, the child process looks exactly the way the parent looked paused, so I can now switch to it, and it would run just like the parent did (except different pid).
This currently works, but I have realized there is a problem. Although It hasn't occurred me, that is just pure luck, and probably won't last.
The problem is that since the kernel stack moved, all the pointers to it are invalid if made from the child, since they point to the parent stack, which we can now gurantee nothing about. the stack pointer itself I can easily adjust, but it is not the only one.
Explicit pointers to variables on the stack are obviously broken, but they are not all that breaks, so avoiding them wouldn't even help.
All the saved bp values, in all function frames, are now ruined (for the child), since they point to the parent. since all local variables are referenced relative to bp, ALL the local variables are messed up now.
To confirm my worries, running the following code in the kernel:
Code: Select all
int x = 10;
Process::fork();
klog("(pid: %d, x: %d)", getpid(), x);
To solve this, I considered several approaches.
1: implementing fork by saving the context of the parent, and jumping directly to the child, while setting ax = 0, and in doing so avoiding any further use of the kernel stack until the next interrupt, at which point everything will be alright. However, since I want to be able to fork kernel processes too, this approach cannot work.
2: Instead of mapping all the kernel stacks at the same time to different kernel virtual addresses, mapping all kernel stacks(other than the special ones e.g. double fault stack) to the same virtual address. This does complicate the matter of copying the kernel stack, but I can always use the mapped physical memory (I have a mapping for all physical memory in continuous virtual address space).
Thus, my question is: Is there a better way (than option 2) to implement fork here?