Task switch that can be called from interrupt and directly

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Werzi2001
Posts: 3
Joined: Thu Jul 28, 2016 7:43 am

Task switch that can be called from interrupt and directly

Post by Werzi2001 »

Hi everyone,

I started a very simple OS (well actually something that can boot) quite a few years ago and somehow I'd like to extend it now :-) At the moment i have the following "chain": boot sector loads boot loader loads 16 bit kernel loads 32 bit kernel where the 32 bit kernel is written in C and the rest in assembler. So far that works well but is still done on a very simple basis (e.g. no paged memory or different segments).

What I'd like to do now is a very basic multi tasking. Therefore I read in the forum and the wiki and decided to base my implementation on a mixture of the following information:
http://wiki.osdev.org/Kernel_Multitasking
http://forum.osdev.org/viewtopic.php?f=1&t=24452

I have created a basic Process structure in C which only contains the SS and ESP values. All other segments, registers and flags are pushed to the stack. Using that technique I have to create and prepare a fake stack when a new process is created and although that's not pretty, it's fine for me at the moment, is quite easy and works fine.

At the moment the task switch is only called from the timer interrupt but in the future I'd like to add a directly triggered task switch e.g. for sleep() or if a lock is not available. The forum post from above contains the following:
Brendan wrote:
eryjus wrote:For example: ProcessA is in the ready queue with its own stack and the stack was left in a state where it was preempted through the timer interrupt. ProcessB, the current process, now enters a blocked state (such as sleep()), and therefore needs to give up the processor to the next process on the ready queue. Since the kernel function will call Reschedule without going through the interrupt handler, ProcessA will receive control and will exit in a different manner than it went in, meaning the code path in and the code path out will not match.
The code path into and out of the "switch_to_task()" function will remain the same in all cases; and that's all that matters. The "switch_to_task()" function switches to the new task, then the new task returns to whatever that new task was doing before it called "switch_to_task()" (which may be an IRQ handler, "Reschedule()", any other kernel function, an exception handler or any anything else).
That part confuses me a little bit as I don't understand how this (at least in my case) should work. I understand that no matter where the new task was suspended (being in switch_to_task() called by interrupt or direct call) it will just continue at that point but in my case (and as far as I understood it should be that way) the interrupt routine contains the signal to the PIC to allow interrupts again after the routine was finished:

Code: Select all

mov al, 20h
out A0h, al
out 20h, al
Let's imagine the following example: TaskA calls sleep() and its state is suspended (return address being within the sleep method) and TaskB starts to run. At some point in the future a timer interrupt triggers, interrupts TaskB and want's to go back to TaskA. In that case, as TaskA was suspended in sleep(), the signal to the PIC to allow interrupts again won't happen and therefore interrupts will be blocked => scheduler breaks.

I can't figure out how this should work especially as Brendan wrote that it shouldn't be a problem.

Maybe anybody could give me a few hints and point me to the right direction :-) Thanks a lot!

Best regards
Werzi2001
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Task switch that can be called from interrupt and direct

Post by Brendan »

Hi,
Werzi2001 wrote:Let's imagine the following example: TaskA calls sleep() and its state is suspended (return address being within the sleep method) and TaskB starts to run. At some point in the future a timer interrupt triggers, interrupts TaskB and want's to go back to TaskA. In that case, as TaskA was suspended in sleep(), the signal to the PIC to allow interrupts again won't happen and therefore interrupts will be blocked => scheduler breaks.
The normal solution to this problem is to send the EOI before you do the task switch. For example; do everything needed to handle the interrupt; then disable IRQs (if they aren't disabled already), send the EOI, do your task switch (if necessary), then "IRET".


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Werzi2001
Posts: 3
Joined: Thu Jul 28, 2016 7:43 am

Re: Task switch that can be called from interrupt and direct

Post by Werzi2001 »

Hi Brendan,

thanks for your reply. Actually very nice to get the reply directly from you even 5 years later :-)

I have to admit that I didn't think of just disabling the interrupts and sending the EOI before. So I'll try to disable the interrupts at the start of the IRQ, send the EOI and then handle the interrupt (which includes the task switch if needed). As the task switch method also disables interrupts during its call that should be fine :-)

Best regards
Werzi2001
User avatar
JAAman
Member
Member
Posts: 879
Joined: Wed Oct 27, 2004 11:00 pm
Location: WA

Re: Task switch that can be called from interrupt and direct

Post by JAAman »

Werzi2001 wrote: I have to admit that I didn't think of just disabling the interrupts and sending the EOI before. So I'll try to disable the interrupts at the start of the IRQ, send the EOI and then handle the interrupt (which includes the task switch if needed). As the task switch method also disables interrupts during its call that should be fine :-)
in the case of an interrupt handler, the interrupt itself should disable interrupts (if you used an interrupt gate in the IDT, the CPU will do this for you) and you shouldn't need to worry about it unless you re-enable interrupts during the handler (to support interrupt nesting)

also the EOI really shouldn't be sent until after you handle the interrupt -- signal EOI after processing the interrupt, but before switching tasks -- this shouldn't be hard to do, since you need to finish processing the interrupt (which would include sending EOI) before switching tasks anyway, otherwise the processing won't conclude correctly if the destination thread was switched from a different location (a different interrupt, or from a syscall, etc.)
User avatar
SpyderTL
Member
Member
Posts: 1074
Joined: Sun Sep 19, 2010 10:05 pm

Re: Task switch that can be called from interrupt and direct

Post by SpyderTL »

You could swap to a different stack immediately at the beginning of your timer interrupt handler, and pass the "saved" stack to your swap task function. Then your swap task function could swap back to the task stack, and push all of the registers, store the stack for that task in memory, then get the next task stack, restore all of the registers, then swap back to the stack it started with, then return.

This would separate your single stack into two stacks: One for the task, and one for the "return" to caller address.
Project: OZone
Source: GitHub
Current Task: LIB/OBJ file support
"The more they overthink the plumbing, the easier it is to stop up the drain." - Montgomery Scott
Werzi2001
Posts: 3
Joined: Thu Jul 28, 2016 7:43 am

Re: Task switch that can be called from interrupt and direct

Post by Werzi2001 »

Thanks for your replay but I'm not sure if I fully understand it.

Let's assume TaskA is running when the interrupt occurs. In that case the IRQ would be called with the return address to TaskA on the stack of TaskA in a format that can be used by IRET (e.g. including the flags). Now the IRQ would switch to a IRQ stack and determine which task should run next (let's assume TaskB). Using that information (stack of TaskA and TaskB) the switch task function can be called placing the return address onto the IRQ stack in a format that can be used by RET (e.g. without the flags). The switch task function now can switch back to the stack of TaskA and store the state onto the stack.

Here the first question occurs: How can this be done as quite a lot of code might have been executed so far in the IRQ and the state was lost a long time ago. I guess the state has to be saved directly on entering the IRQ onto the IRQ stack or directly onto the TaskA stack? Actually my current code pushes the state directly onto the TaskA stack (as there is not IRQ stack now) using pusha and is restored after the IRQ finished using popa. The more I think about it the more I believe that this is the state that really matters in my current task switch as the state that was stored in the process object will be overwritten by the state from the popa in the IRQ anyway. Have to look at that again.

Anyway after that the switch task function would switch to the stack of TaskB and restore the state from there. Afterwards it would switch back to the IRQ stack and return to the IRQ handler that can send the EOI and switch again back to the now current TaskB stack. To return from the IRQ IRET is used.

Here the next question occurs: What if the TaskB was not interrupted by an interrupt but by a direct task switch? In that case the return address on the stack of TaskB wouldn't be IRET compatible. So I don't really understand how the two stack approach can solve the problem of returning to the correct place. It enabled to always go back to the IRQ handler after switching the task and send the EOI in every case. But I don't see how it can manage to return from the IRQ to the right place.

Maybe you could tell me where I missed something :-) But anyway I guess that this might be too complicated for now to implement.
onlyonemac
Member
Member
Posts: 1146
Joined: Sat Mar 01, 2014 2:59 pm

Re: Task switch that can be called from interrupt and direct

Post by onlyonemac »

Werzi2001 wrote:Let's assume TaskA is running when the interrupt occurs. In that case the IRQ would be called with the return address to TaskA on the stack of TaskA in a format that can be used by IRET (e.g. including the flags). Now the IRQ would switch to a IRQ stack and determine which task should run next (let's assume TaskB). Using that information (stack of TaskA and TaskB) the switch task function can be called placing the return address onto the IRQ stack in a format that can be used by RET (e.g. without the flags). The switch task function now can switch back to the stack of TaskA and store the state onto the stack.
You want to save the state before you do anything else. So as soon as you get the IRQ, call your task switch routine. The very first thing that your task switch routine should do is save the state on the stack. Then the task switch routine can determine the next task to run, pop the state off the stack, and return (which will return to the IRQ handler). The IRQ handler can then send EOI for the interrupt (without affecting the state of anything else) and IRET straight into TaskB.

Alternatively, you could PUSHA as soon as you enter the IRQ handler and POPA as the very last thing before the IRET, except that this is moving part of your "task switch routine" out of the task switch routine (which may or may not be a problem to you).
When you start writing an OS you do the minimum possible to get the x86 processor in a usable state, then you try to get as far away from it as possible.

Syntax checkup:
Wrong: OS's, IRQ's, zero'ing
Right: OSes, IRQs, zeroing
User avatar
SpyderTL
Member
Member
Posts: 1074
Joined: Sun Sep 19, 2010 10:05 pm

Re: Task switch that can be called from interrupt and direct

Post by SpyderTL »

How can this be done as quite a lot of code might have been executed so far in the IRQ and the state was lost a long time ago.
Your interrupt handlers are not allowed to lose the state of the CPU, or else your system will quickly become unstable.

You don't necessarily have to call a task swap function immediately at the beginning of your timer IRQ handler. You just have to make sure that the initial state gets stored in memory somewhere, and that it can be restored later. (Or just don't change any registers...)

If you want to allow code other than your timer handler to swap tasks, then you'll have to be a little clever. I just wrote separate timer and non-timer swap functions, and "inlined" them so that no return address is pushed onto the stack.
Project: OZone
Source: GitHub
Current Task: LIB/OBJ file support
"The more they overthink the plumbing, the easier it is to stop up the drain." - Montgomery Scott
Post Reply