Prof Prof - 1 month ago 19
C Question

Context switching - Is makecontext and swapcontext working here (OSX)

I'm having some fun with context switching. I've copied the example code into a file
http://pubs.opengroup.org/onlinepubs/009695399/functions/makecontext.html

and i defined the macro _XOPEN_SOURCE for OSX.

#define _XOPEN_SOURCE
#include <stdio.h>
#include <ucontext.h>


static ucontext_t ctx[3];


static void
f1 (void)
{
puts("start f1");
swapcontext(&ctx[1], &ctx[2]);
puts("finish f1");
}


static void
f2 (void)
{
puts("start f2");
swapcontext(&ctx[2], &ctx[1]);
puts("finish f2");
}


int
main (void)
{
char st1[8192];
char st2[8192];


getcontext(&ctx[1]);
ctx[1].uc_stack.ss_sp = st1;
ctx[1].uc_stack.ss_size = sizeof st1;
ctx[1].uc_link = &ctx[0];
makecontext(&ctx[1], f1, 0);


getcontext(&ctx[2]);
ctx[2].uc_stack.ss_sp = st2;
ctx[2].uc_stack.ss_size = sizeof st2;
ctx[2].uc_link = &ctx[1];
makecontext(&ctx[2], f2, 0);


swapcontext(&ctx[0], &ctx[2]);
return 0;
}


I build it

gcc -o context context.c -g

winges at me about get, make, swap context being deprecated. Meh.

When I run it it just hangs. It doesn't seem to crash. It just hangs.

I tried using gdb, but once I step into the swapcontext, it just is blank. It doesn't jump into f1. I just keep hitting enter and it will just move the cursor into a new line on the console?

Any idea what's a happening? Something to do with working on the Mac/deprecate methods?

Thanks

Answer

It looks like your code is just copy/pasted from the ucontext documentation, which must make it frustrating that it's not working...

As far as I can tell, your stacks are just too small. I couldn't get it to work with any less than 32KiB for your stacks.

Try making these changes:

#define STACK_SIZE (1<<15) // 32KiB

// . . .

    char st1[STACK_SIZE];
    char st2[STACK_SIZE];

yup fixed it. why did it fix it though?

Well, let's dig into the problem a bit more. First, let's find out what's actually going on.

When I run it it just hangs. It doesn't seem to crash. It just hangs.

If you use some debugger-fu (be sure to use lldb—gdb just doesn't work right on os x), then you will find that when the app is "hanging", it's actually spinning in a weird loop in your main function, illustrated by the arrow in the comments below.

int
main (void)
{
    char st1[8192];
    char st2[8192];


    getcontext(&ctx[1]);
    ctx[1].uc_stack.ss_sp = st1;
    ctx[1].uc_stack.ss_size = sizeof st1;
    ctx[1].uc_link = &ctx[0];
    makecontext(&ctx[1], f1, 0);


    getcontext(&ctx[2]);// <---------------------+ back to here
    ctx[2].uc_stack.ss_sp = st2;//               |
    ctx[2].uc_stack.ss_size = sizeof st2;//      |
    ctx[2].uc_link = &ctx[1];//                  |
    makecontext(&ctx[2], f2, 0); //              |
    //                                           |
    puts("about to swap...");//                  |
    //                                           |
    swapcontext(&ctx[0], &ctx[2]);// ------------+ jumps from here
    return 0;
}

Note that I added an extra puts call above in the middle of the loop. If you add that line and compile/run again, then instead of the program just hanging you'll see it start spewing out the string "about to swap..." ad infinitum.

Obviously something screwy is going on based on the given stack size, so let's just look for everywhere that ss_size is referenced...

(Note: The authoritative source code for the Apple ucontext implementation is at https://opensource.apple.com/source/, but there's a GitHub mirror that I'll use since it's nicer for searching and linking.)

If we take a look at makecontext.c, we see something like this:

if (ucp->uc_stack.ss_size < MINSIGSTKSZ) {
   // fail without an error code since makecontext is a void function
   return;
}

Well, that's nice! What is MINSIGSTKSZ? Well, let's take a look in signal.h:

#define MINSIGSTKSZ 32768   /* (32K)minimum allowable stack */
#define SIGSTKSZ    131072  /* (128K)recommended stack size */

Apparently these values are actually part of the POSIX standard. Although I don't see anything in the ucontext documentation that references these values, I guess it's kind of implied since ucontext preserves the current signal mask.

Anyway, this explains the screwy behavior we're seeing. Since the makecontext call is failing due to the stack size being too small, the call to getcontext(&ctx[2]) is what is setting up the contents of ctx[2], so the call to swapcontext(&ctx[0], &ctx[2]) just ends up swapping back to that line again, creating the infinite loop...

Interestingly, MINSIGSTKSZ is 32768 bytes on os x, but only 2048 bytes on my linux box, which explains why it worked on linux but not os x.

Based on all of that, it looks like a safer option is use the recommended stack size from sys/signal.h:

char st1[SIGSTKSZ];
char st2[SIGSTKSZ];

That, or switch to something that isn't deprecated. You might take a look at Boost.Context if you're not averse to C++.