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
Implementing setjmp/longjmp
Re:Implementing setjmp/longjmp
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:
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:
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.
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;");
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;
}
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.