Octocontrabass wrote:bloodline wrote:1 Save the CPU state the the stack.
2. Save the User stack pointer somewhere.
3. The CPU to enter ring0,
4. Load the Supervisor stack pointer.
When an interrupt occurs in user mode, the CPU switches to supervisor mode, loads the supervisor stack from the TSS, and pushes the user mode return address and stack pointer on the supervisor stack.
I understand now why there is a preference for per task kernel stacks now on the x86. The task state is stored on the kernel stack not the task stack. I'm slowly getting this. Appreciate your patience.
bloodline wrote:5 Restore the Supervisor state from the stack.
6. Execute some supervisor function.
7. Save the Supervisor state on the stack.*
There should be no supervisor state to restore before calling your supervisor function, and there should be no supervisor state to save afterwards. If there is, you're doing something very strange! There is user state that must be saved before you can call a supervisor function, and it must be restored afterwards (although system calls may wish to modify the saved user state).
By "save the supervisor state", I really meant clean up the stack... I was also trying to think how I could allow the interrupts to nest... but I completely agree, no supervisor state needs to be saved in normal circumstances. If you look at my earlier context switching mechanism, it should allow interrupts to preempt each other...
bloodline wrote:8. Save the Supervisor stack pointer somewhere.*
If you save the user state on the supervisor stack, switching to a different supervisor stack is all that's necessary to switch to a different task. In this model, you have one supervisor stack per user thread. (This is a very common arrangement.)
Even if you choose to store the user state elsewhere, this is the point where you should perform a context switch if one is necessary.
Ok, so working this through (generally, I know there are error codes and int numbers to be stored, etc...), using a per task kernel stack model...
Let's assume we've just entered an ordinary interrupt;
1. The CPU has loaded %esp with the value found in TSS.esp0;
2. The user mode state (eip, cs, eflags, useresp, ss) has been pushed on to the stack
3. pusha to save the cpu state.
4. Do the supervisor function.
5. popa the cpu state.
6. iret
Now assume we've just entered an interrupt which can task switch (usually timer);
1. The CPU has loaded %esp with the value found in TSS.esp0.
2. The user mode state (eip, cs, eflags, useresp, ss) has been pushed on to the stack.
3. pusha to save the cpu state.
4. Determine if a task switch needed. If NO task switch needed goto 7.
5. Load TSS.esp0 with new task kernel stack pointer.
6. Load %esp with new task kernel stack pointer.
7. popa the cpu state.
8. iret.
Is my mental model correct?
Having two stacks per task seems a little wasteful, but inconsequential on x86 systems which come with hundreds/thousands of megabytes of ram as standard.
bloodline wrote:9. Return to ring3.
10. Load the User stack pointer (here I might chose to load a different stack pointer, i.e. a context switch).
11. Restore the User state from the stack.
12. Continue execution, as before the interrupt.
The IRET instruction loads the return address from the stack and (if the return address indicates ring 3) restores the user stack pointer and switches to ring 3. That's all one instruction, so anything else you want to do with the user context has to happen before that point.
bloodline wrote:As you can see from my earlier posts, I already have a working mechanism for this with USP and SSP variables where I can save and swap the mode stacks... but all this happens in ring0.
If you're in ring 0, you don't have user stacks, they're all supervisor stacks.
Apologies for being unclear, I have been using task stack and user stack interchangeably... My terminology is incorrect when talking about x86, I will try to be clearer.
bloodline wrote:With a valid TSS, when an interrupt occurs, the esp0 field is used for the supervisor stack pointer... What happens to the user mode stack pointer? I see that the interrupt stack frame pushes the user esp onto the stack... ok, so I can save that value somewhere.
Now where do I put the new stack pointer, for the user mode context I wish to return to?
You switch to another supervisor stack, which has the new user stack pointer already on it ready for you to IRET into.
Unless you're going for an alien design with only one supervisor stack per CPU core.
That is a good link. My original design was as described at the bottom of that page (writing microkernels for embedded systems is the experience I have), but I can't really think of any advantage to that design on the x86.