Hi Kees,

> Perhaps I should further check your code to see when and where the timer
> interrupt returns coz I still don't understand the mechanism on how to
> switch. When the timer interrupt returns when the original thread is
> scheduled back in, then something, some interrupt, should cause that.
> Since interrupts are disabled during the timer interrupt (unless I used
> nested interrupts) I don't see how is working.

I've made a note to add some more detail to the documentation
regarding context switches. But I'll try to give you a flavour now
(this applies to the current range of ports, new ports may context
switch slightly differently):

First of all, cooperative context switches occur with no interrupt at
all. If a thread schedules itself out (for example by calling
atomTimerDelay() to go to sleep) then the scheduler decides which
thread should be up next, and calls archContextSwitch() in order to
switch the register context to the new thread.

Now consider a thread (Thread B) is asleep on a timer, and is due to
be scheduled in when the next timer tick interrupt occurs:
* Thread A is currently running
* The timer tick interrupt occurs and the scheduler decides during the
timer interrupt that Thread B should be woken up. It calls
archContextSwitch() to do this.
* archContextSwitch saves the context of the previously running thread
(Thread A) and restores the context of the new thread (Thread B). On
exit from the timer interrupt, Thread B is now running. However the
latter instructions of the original interrupt handler are not
executed, because context has switched to Thread B during
archContextSwitch(). That thread went to sleep using atomTimerDelay()
so on exiting archContextSwitch() it goes back up the call stack
through atomTimerDelay(). The actual remaining instructions of the
original interrupt handler are not executed yet, because the call
stack of that interrupt was saved on the interrupted thread's stack
(Thread A). When Thread A is eventually scheduled back in, it returns
through the interrupt handler call stack.

This scheme is used for both the ATmega and STM8 ports - it may not be
possible to do it on all architectures. It is possible on both of
these architectures because interrupts can be exited much like
returning from regular functions. Taking ATmega as an example, the
difference between RETI and RET is whether interrupts are re-enabled
or not. Therefore you can exit the timer interrupt using just a RET,
because the new thread context restore re-enables interrupts as part
of its context restore anyway.

This is convenient because it reduces the context-switch routine and
allows it to be used for both cooperative switches and preemptive (via
interrupt) switches. On a cooperative switch it only needs to save
those registers which are not saved by the compiler on a function
call, because the call to archContextSwitch() is a regular C call. For
preemptive switches, the interrupt handler may already have saved some
registers, and when it calls out to the C routine archContextSwitch()
it will do the same as for cooperative switches (save registers which
cannot be clobbered by the called routine). This means that the same
small context-switch routine can be used in both cases, reducing
development and debug time, as well as avoiding (in the cooperative
case) unnecessary register saves where registers are already saved by
the compiler on calling out to archContextSwitch().

This is slightly complicated on architectures with separate stacks for
interrupts, and I intend to provide an example of this shortly.
Different context-switch schemes could be supported in architecture
ports while maintaining the same core kernel.

I have just started a Cortex M3 port, and intend to keep adding ports
which will cover some of the more unusual architectures to provide
wider examples for those porting to such architectures.

Thanks,
Kelvin.

Reply via email to