Page 1 of 1

x86 Context Switching

Posted: Fri May 15, 2009 3:49 am
by Andy1988
Hi there,
yesterday I got my thread scheduling in the kernel to do something without crashing :D

I've got some problems with interrupt disabling and enabling when switching the contexts.
I disable them, when entering my scheduling method and want to enable them after the context switch.
And here is the tricky thing: I'm storing the old context and loading a fresh, new one just created. It has an own stack, on this stack is the first return address which points to the entry point etc. All fine.
But after the context switch, I'm jumping straight to the entry point. I've got no chance to enable interrupts afterwards since I'm now in the thread. This means my scheduling timer never fires, so the task runs forever. Not good ;)

Here is how I switch contexts (I took a bit from exlaim and other projects to learn how to do it, thx!):
A context is defined by this structure:

Code: Select all

typedef struct {
        Address ip;         //EIP (Instruction Pointer)
        Address sp;         //ESP (Stack Pointer)
        unsigned int bp;    //EBP (Stack Frame)
        unsigned int bx;    //EBX
        unsigned int edi;   //EDI
        unsigned int esi;   //ESI
        
        Address *kernelStack;   //KernelStack
    } context_t;
The switching itself is done here:

Code: Select all

[BITS 32]

CTX_OFF_EIP     equ     0   ;EIP offset
CTX_OFF_ESP     equ     4	;ESP offset
CTX_OFF_EBP		equ     8	;EBP offset
CTX_OFF_EBX		equ     12	;EBX offset
CTX_OFF_EDI		equ     16	;EDI offset
CTX_OFF_ESI		equ     20	;ESI offset

;int context_save(context_t *ctx)
[GLOBAL context_save]
context_save: 
        mov     eax, [esp+4]

        ;Save old instruction/stack pointers
        mov     ecx, [esp]
        mov     [CTX_OFF_EIP+eax],ecx
        mov     [CTX_OFF_ESP+eax],esp

        ;Save old callee-save registers
        mov     [CTX_OFF_EBP+eax],ebp
        mov     [CTX_OFF_EBX+eax],ebx
        mov     [CTX_OFF_EDI+eax],edi
        mov     [CTX_OFF_ESI+eax],esi

        xor     eax,eax
        ret

;int context_restore(context_t *ctx)
[global context_restore]
context_restore: 
        mov     eax, [esp+4]

        ;Restore new callee-save registers
        mov     esi, [CTX_OFF_ESI+eax]
        mov     edi, [CTX_OFF_EDI+eax]
        mov     ebx, [CTX_OFF_EBX+eax]
        mov     ebp, [CTX_OFF_EBP+eax]

        ;Restore new instruction/stack pointers
        mov     esp, [CTX_OFF_ESP+eax]
        mov     ecx, [CTX_OFF_EIP+eax]
        mov     [esp],ecx

        ;xor     eax,eax    ;WTF???
        ;inc     eax
        ret
A context switch is invoked from the c++ code like this:

Code: Select all

void CPUContext::SwitchTo(CPUContext *old)
{
    //TODO: Set kernel stack in TSS for usermode later
    
    if(context_save(old->GetContext()) == 0) {
        context_restore(&context);
    }
}
Also I'm quite confused about three things in this code:
Doesn't I have to save all the registers like eax, edx, the eflags and all the segments? I don't know when I'm interrupted, so I definetly need the eflags. This would also solve my Interrupt problem, since I can pass an interrupt status to the context switch and set the bit on the eflags, right?

The other thing is the return value of context_save. When does it return 0? When a thread just woke up, because it was picked by the scheduler?

The third thing is the return value in eax of context_restore. Do I need this? I think it would be fatal. I need a restored value in eax when continuing the execution of the thread. Nobody know what the code of the thread moved in there before the switch. So what is this code (the two "WTF??"-lines ;) ) for?

It would be nice if somebody could help me!
(And hopefully you understand my english... It still has much of this "german strangeness" in it ;) )

Re: x86 Context Switching

Posted: Fri May 15, 2009 4:37 am
by NickJohnson
Well, if you are returning from an interrupt (which I'm assuming you are), you should be using the iret instruction, because it also allows you to go back to user mode if that's where the interrupt happened. Because iret reloads a copy of EFLAGS from the stack, you can modify that copy to have the interrupt flag set. Then turning on interrupts will be completely atomic. I'm actually surprised that it lets you just return to an instruction from an interrupt.

Code: Select all

        ;xor     eax,eax    ;WTF???
Oh, and by the way, "xor eax, eax" does make sense - it just clears the eax register. It takes less space and is faster than "mov eax, 0", so you should use it if possible. It's generally recognized, so nobody will be confused about what you're doing.

Re: x86 Context Switching

Posted: Fri May 15, 2009 5:38 am
by Andy1988
NickJohnson wrote:Well, if you are returning from an interrupt (which I'm assuming you are), you should be using the iret instruction, because it also allows you to go back to user mode if that's where the interrupt happened. Because iret reloads a copy of EFLAGS from the stack, you can modify that copy to have the interrupt flag set. Then turning on interrupts will be completely atomic. I'm actually surprised that it lets you just return to an instruction from an interrupt.
Yes, you are right.
It's all very hacky and unstable.

Another thing I don't know how to do is starting the scheduler for the first time.
I've got no context to save, yet. So I've got to set up a context for my idle thread by hand and just restore it. Then I'll set up my timer to call the scheduling method after an amount of time.

But what happens, when I'm calling the scheduler method from an interrupt?
My Interrupt Dispatcher expects that all calls the other methods from the ISR return. After that the PIC will be notified that the interrupt has been handled properly.
If I switch the context, the context switch method will never return, since I'm jumping straight into the thread. How can I handle that?
NickJohnson wrote:

Code: Select all

        ;xor     eax,eax    ;WTF???
Oh, and by the way, "xor eax, eax" does make sense - it just clears the eax register. It takes less space and is faster than "mov eax, 0", so you should use it if possible. It's generally recognized, so nobody will be confused about what you're doing.
Yes, I understand that. After these two instructions eax contains 1.
I don't understand the purpose of this. For me it makes no sense.
But never mind... I have to rewrite these methods anyway to use iret.

Re: x86 Context Switching

Posted: Fri May 15, 2009 5:53 am
by salil_bhagurkar
Well I don't know if there is a better way to do this, but this is how I do it:

I make the schedule call the last call in the interrupt dispatcher. So the EOI also, is sent to the PICs before the schedule call is done. This makes sure that all the interrupt handlers and the EOI get called.

So if your interrupt handler is the one that calls all interrupts, then at the end of it you just check if it is IRQ0 and then call the scheduler. If not, then just add a call to the IRQ0 handler.

Re: x86 Context Switching

Posted: Fri May 15, 2009 12:11 pm
by xyzzy
Right so that is essentially my code converted to Intel syntax ASM.

The context_save/context_restore functions behave exactly the same as setjmp/longjmp. Look up man pages for them to see how they work and what their return values mean. The xor/inc instructions are simply to set the return value, which is passed in the EAX register.
Andy1988 wrote:But after the context switch, I'm jumping straight to the entry point. I've got no chance to enable interrupts afterwards since I'm now in the thread. This means my scheduling timer never fires, so the task runs forever. Not good ;)
Seeing as you're looking at my code, you should know that the first thing I do when entering a thread (the thread creation code points the thread's initial context at a wrapper) is to enable interrupts.