cmultithreadingoperating-systemriscvxv6

Threading using jmp_buf array in C (test in xv6)


Have you guys ever tried to put a bunch of jmp_buf together and trigger it sequentially?

I was trying to use an array of jmp_buf to schedule threads. Following is a simple example. I was running and compiling this code in xv6 and get error as shown.

I was expecting a output like:

3 2 1

Here's my code:

struct threads{
    jmp_buf env;
};
int main(){
    struct threads **t = (struct threads**) malloc(sizeof(struct threads*)*3);
    for (int i = 0; i < 3; i++){
        if (setjmp(t[i]->env)!=0){
            printf("%d \n",i);
            if (i+1<3) longjmp(t[i+1]->env,-1);
        }
    }
    longjmp(t[0]->env,-1);
    return 0;
}

error:

enter image description here


Solution

  • Have you guys ever tried...

    Yes. I did it, sometime back in the 1980s. It was more of a proof of concept than anything else. I never used it in any real project.

    Everything I did back then would be called "undefined behavior" today, but back then, experimentation and reading library and OS source code was business-as-usual for figuring out what would work with a given toolchain and, on a given platform.


    Eric Postpischil said,

    ...the values of automatic objects local to the function are indeterminate.

    That wasn't too hard to deal with. The setjmp and longjmp calls in my home-made threading library were inside functions that were written so as not to depend on the value of any local variable surviving a context switch. The client functions that called the library didn't have to worry about it for the same reason that client functions never have to worry about their local variables being corrupted by any arbitrary function call.


    Ikegami said,

    ...each thread needs a stack. So you need something that swaps stacks in addition to changing the instruction pointer.

    That's a bit misleading. "swaps stacks" means changing the stack pointer. longjmp does that. How could it not? When used as intended, it has to restore the context of some function call further down the same stack. It can't do that without restoring a saved stack pointer. My library used it to restore the context of some function call that was saved from a different stack.

    But still, each thread must have its own stack, and at the base of each thread's stack, there must be an activation record for some function, as if the function was called from somewhere, but called from where?

    The "create new thread" function of my library allocated a block of memory to be used as the stack, and then it synthesized a stack frame for the first function to be called, and it synthesized a jmp_buf containing a "saved" context such that the first instruction of the function would be executed on the new stack whenever the program longjmp()ed to it.


    Like I said, all of the above was UB. You can't depend on it to work with any given compiler, with any given runtime library, on any given OS or hardware. And also, doing it on a multi-CPU host probably will be tricker than when I did it back in the day.

    But you can try. Even if you fail, you might learn something.