(There have been some more posts since I started this one, but I've spent all this time typing now...
![Smile :)](./images/smilies/icon_smile.gif)
)
XCHG wrote:Combuster,
That information really helped. Thank you so much.
Alrighty! I created a Call Gate in my GDT for the first time and actually demonstrated the fact that the control is given to my process. What I am confused about is this: suppose I am now in the kernel and IRQ0 is fired:
1) IRQ0 is fired and my IRQ0Handler procedure takes control.
2) In my IRQ0Handler, at the beginning, I am creating the stack frame by pushing all the registers that I know I am going to be destroying, into the stack. Therefore, the kernel's stack is now altered.
3) Now my scheduler, which resides in IRQ0Handler, picks one process up from the Process Queue and has now access to its structure that contains the process's CS/DS/ES/FS/GS/EIP/EFLAGS/Base Address and etc.
So far so good.
4) Now my scheduler in IRQ0Handler, pushes the process's SS, ESP, CS, EIP and EFLAGS respectively into the kernel's stack and issues an IRET. This IRET is issued before my IRQ0handler issues its main IRET that returns to the kernel. You see, I am issuing one IRET in order to switch to the process and one IRET is supposed to be issued at the end of the IRQ0Handler.
My problem and the confusion is that the IRET at the end of my IRQ0Handler will never be issued unless there are no proesses in the Process Queue to be given control to.
[snip]
In this case, You can see that if a process is picked to be given control to, the IRQ0Handler will never reach the .SendEOI label in order to pop the regisers it has already pushed into the kernel's stack. I am really confused. Is it just me or is multitasking complicated?
This is why I mentioned that was not how I was actually doing it, just an example of how IRET works.
First of all, about EOI: you should issue it before switching to anything else. Be sure to keep interrupts disabled to prevent the interrupt from re-entering prematurely.
Now, what I do:
4) The scheduler calls a function to switch stacks.
5) That function pushes all registers it needs to preserve for next time the thread is switched to (saving them to a task structure is also an option). In your case, this should be all registers that weren't pushed in your step (2) (because your thread/process expects them to be preserved) plus whatever registers your IRQ handler expects to be preserved after you return to it. The only registers that may need special handling are SS:ESP, since saving the stack pointer on the stack makes it hard to find afterwards
![Razz :P](./images/smilies/icon_razz.gif)
. (This is not an issue if all registers are stored into a special task structure).
6) The function actually loads a new SS:ESP from wherever they were saved for the next thread, and loads all other preserved registers.
7) The function returns, automatically using the return address from the new stack. This hands control over to the thread being switched to.
To create a new ring 3 process:
1) Allocate a kernel stack
2) Set up the kernel stack (from the top) like this:
* IretData
* Address of a stub function that perhaps clears some more registers and then performs an IRET(D). (Will be used as return address by task switch function)
* Whatever data the task-switch function pushes onto the stack and expects to be present on the new stack (none if all registers are saved to a task structure).
3) Initialize a task structure with any registers saved into it by the task switch procedure (and whatever else it contains, obviously). Be sure to set SS:ESP to point to the lowest initialized entry in the new kernel stack (the last-pushed register or (if none) the return address).
4) Perform whatever other actions need to be done to set up a new process (address space initialization etc.)
5) Add it to the scheduler queue
When this new task is switched to, it'll initially be in ring 0. The task switch function treats it like it would any other task, "restoring" register values and "returning" to the stub function set up in step 3 above, which is then free to perform some last-minute register initialization and perform an IRETD to jump to the ring 3 code.
When you switch to a thread that has already run, the task switch function restores the register values and returns to whatever code called the task switch function last time the thread was run. If that was an interrupt handler it can then restore registers to the values expected by the user process (or kernel thread) interrupted and execute an IRETD to return control to it.