Implementing setjmp/longjmp

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
purevoid

Implementing setjmp/longjmp

Post by purevoid »

Hi,

Can someone help me to implement setjmp/longjmp for my OS? I've been looking at linux+freebsd sources, but I'm getting confused with all the machine-dependant versions, as well as the signalling crap (which I don't want or need).

I want to make setjmp & longjmp safe for implementing pre-emptive multitasking, which I hear needs to save eflags, and use iret as well. Anyways, I'm having a very difficult time putting it all together.

Could someone give me a headstart? This multi-threading stuff is giving me a headache =(

Jonathan
mystran

Re:Implementing setjmp/longjmp

Post by mystran »

My advice: implement co-operative multitasking with one kernel stack per thread (kernel or user) and then implement pre-emptive multitasking by calling the co-operative multitasking task-switch when you get relevant interrupts.

That said, I found it easier to implement stack switching directly, rather than going with setjmp/longjmp. What I use is simply:

Code: Select all

// This is pretty simple. Basicly, we push all callee-saved registers
// into stack, then switch ESP, and then pop all the registers.
//
// It's written in inline-asm so that we know the exact stack-layout
// and can initialize new threads easily.
//
// The function prototype is:
//     volatile void cpu_switch_stack(void *oldstack, void *newstack);
//
asm (".global cpu_switch_stack;"
        "cpu_switch_stack:"
        /* load arguments */
        "movl 4(%esp), %eax;" // oldstack
        "movl 8(%esp), %edx;" // newstack
        /* save all registers */
        "pushl %ebx;"
        "pushl %esi;"
        "pushl %edi;"
        "pushl %ebp;"
        /* do the stack switch */
        "movl %esp, (%eax);"
        "movl (%edx), %esp;"
        /* restore all registers */
        "popl %ebp;"
        "popl %edi;"
        "popl %esi;"
        "popl %ebx;"
        "ret;");
If you read it, you notice that it works by saving the old ESP where the 'oldstack' pointer points to. If you want to do setjmp/longjmp, you basicly:

1. split the routine in two parts
2. use real context-structs instead of stack directly

Other than that it's the same.

The comment about initializing new threads: because the routine will happily use any kind of pointer, creating a new thread is more or less like:

Code: Select all

// Initialize a new thread stack.
// The new thread calls fn when scheduled for the first time.
//
// In fact, what happens is that the return from the stack-switch
// jumps directly into the beginning of whatever function was requested.
//
// The userdata is passed as an argument to the function called.
//
void cpu_init_stack(void * s, int ssize,
        void (*fn)(void*), void * userdata) {

    // first clear the stack
    memset(s, 0, ssize);

    // Then calculate proper stack top.
    // Four registers, a function and argument, and a panic return
    int top = (ssize/sizeof(unsigned long)) - 7;

    // get us a more useful pointer
    void ** stack = (void **) s;

    // fill in the stack top
    *stack = stack + top;

    // the function to call and it's argument
    stack[top+4] = fn;
    stack[top+5] = cpu_stack_panic;
    stack[top+6] = userdata;

}
What it actually does is to cheat a little: it just puts a function pointer, and an "userdata" argument for that, finally filling a panic-function as the return address. When this stack is first switched to, I simply use the RET (in cpu_switch_stack) to cause a jump to the function, which gets us running with the new thread.

In short: nothing fancy here.

If you want/need to save interrupt context (in my kernel interrupts are always disable when the stack switching is done) then you naturally need to push/pop eflags as well (and adjust the initialization for that).

But really, it's nothing harder than that. Oh, and if you use something like I do, then remember that if you also use the "stack" for thread-structure (have it live in the beginning, like I do, and like Linux IIRC does), you need to have space for the saved stacktop there. If you use setjmp/longjmp, then this naturally is no issue.

Then again, you can do multi-threading in a thousand ways, so what I'm advocating is simply the solution I'm (currently) settled with, after trying a few different schemes, and finding them painful.

Happy hacking.
Post Reply