Page 1 of 1

Implement multitasking

Posted: Sat Jan 30, 2016 8:54 am
by TheLittleWho
I want to implement multitasking in my OS, but I don't know exactly how to do the context switch. I am thinking to put my context switch function in the Tick Interrupt Handler, but the interrupt handlers works like this:

TURN OFF INTERRUPTS
SAVE REGISTERS
HANDLER CODE
RELOAD REGISTERS
TURN ON INTERRUPTS

So, I am searching a solution. How I can implement multitasking? If I put context switcher in an interrupt handler it doesn't affect any register, because all registers are reloaded before iret. Could you help me?

Thanks!

PS. My OS source code: https://github.com/JustBeYou/PhotonOS/tree/devel

Re: Implement multitasking

Posted: Sat Jan 30, 2016 9:08 am
by iansjack
With multiple tasks you need some sort of structure to hold details of processes. Amongst those details will be the register contents at the last context switch. That's where you save/reload your registers. Of course you may be saving your registers on the stack; in that case your context switch will restore the stack for the new process so, again, the correct register contents are loaded (the ones that were pushed onto that stack last time a context switched was made away from that process).

Re: Implement multitasking

Posted: Sat Jan 30, 2016 10:01 am
by Brendan
Hi,
TheLittleWho wrote:So, I am searching a solution. How I can implement multitasking? If I put context switcher in an interrupt handler it doesn't affect any register, because all registers are reloaded before iret. Could you help me?
Task switching rarely has anything to do with IRQs at all.

Task switches happen because:
  • The currently running task has to wait for something and blocks; or the currently running task is terminated (either voluntarily or because it crashed); causing the OS to find some other task to run. This is typically the result of calling a function (e.g. "sleep()", "read()", "open()", "getMessage()") and never involves any IRQ.
  • A task unblocks (whatever it was waiting for happens); or is spawned/created; causing the kernel to compare the task's priority with the currently running task's priority and decide if the current running task should be pre-empted. In this case the task switch is always caused by the code that determines if the currently running task should be pre-empted; which never directly involves an IRQ. However, the original code that unblocked the task may have involved an IRQ (e.g. from disk controller, or network card, or...), but may not have involved an IRQ (e.g. a thread called a "sendMessage()" function, or sent a packet to 127.0.0.1, or wrote data to a pipe, etc).
  • In rare cases involving CPU bound tasks, for some (badly designed) schedulers and not other (better designed) schedulers; if and only if there are other tasks waiting to use the CPU; when the task consumes all of the CPU time it was given the scheduler might force a task switch to give other task/s a turn.
As far as kernels go; there's 2 common options. Either every task has its own kernel stack (common for monolithic), or there's one kernel stack per CPU (more common for micro-kernels). How things work is very different for these cases.

For "every task has its own kernel stack"; something causes a switch from CPL=3 to CPL=0 (IRQ, system call, exception); then the kernel does things (which may or may not include a task switch); then there's a switch from CPL=0 back to CPL=3 (which might return to a different task). The switching between CPL=0 and CPL=3 (in both directions) has nothing to do with task switching. Task switches only ever happen between kernel code and kernel code.

For this case I strongly recommend implementing some sort of "goto_task(task_ID)" function. This function would save the old task's kernel registers on its kernel stack; switch kernel stacks; then load the new task's kernel registers from the new task's kernel stack. When the kernel knows it needs to switch to a specific task (e.g. a higher priority task has pre-empted a lower priority task) it uses "goto_task()" directly. When the kernel doesn't know which task to switch to it calls a different function that chooses a task before calling "goto_task()" to switch to the chosen task.

For "single kernel stack per CPU"; something causes a switch from CPL=3 to CPL=0 and the kernel saves the CPL=3 state into the thread's "thread data structure", and loads the kernel's "initial state" (where most registers are undefined, which mostly means only loading segment registers). From this point the kernel is officially not running any task at all. Eventually kernel runs out of things to do and decides to return to CPL=3; the kernel finds the information for whichever task it's supposed to return to and loads that task's state (the kernel's state is discarded because its no longer needed) then it returns to CPL=3.

For this case a task switch means changing a single "which task to return to eventually" variable and doesn't involve saving or loading any state (because that is handled when switching between CPL=3 and CPL=0).


Cheers,

Brendan

Re: Implement multitasking

Posted: Sat Jan 30, 2016 10:11 am
by SpyderTL
On my first attempt at multitasking, I put all of the registers on the stack, along with a reference to the next task's SP value, and made sort of a task "ring buffer".

Although this worked, it made keeping up with adding and removing tasks a little more difficult. But, strictly speaking, all you need to keep track of is the stack pointer for each thread to get you started. Everything else can be stored on the stack for each thread.