The easiest way to use the context handling functions is as a replacement for setjmp and longjmp. The context contains on most platforms more information which might lead to less surprises but this also means using these functions is more expensive (beside being less portable).
int random_search (int n, int (*fp) (int, ucontext_t *)) { volatile int cnt = 0; ucontext_t uc; /* Safe current context. */ if (getcontext (uc) 0) return -1; /* If we have not tried n times try again. */ if (cnt++ n) /* Call the function with a new random number and the context. */ if (fp (rand (), uc) != 0) /* We found what we were looking for. */ return 1; /* Not found. */ return 0; }
Using contexts in such a way enables emulating exception handling. The search functions passed in the fp parameter could be very large, nested, and complex which would make it complicated (or at least would require a lot of code) to leave the function with an error value which has to be passed down to the caller. By using the context it is possible to leave the search function in one step and allow restarting the search which also has the nice side effect that it can be significantly faster.
Something which is harder to implement with setjmp and longjmp is to switch temporarily to a different execution path and then resume where execution was stopped.
#include signal.h #include stdio.h #include stdlib.h #include ucontext.h #include sys/time.h /* Set by the signal handler. */ static volatile int expired; /* The contexts. */ static ucontext_t uc[3]; /* We do only a certain number of switches. */ static int switches; /* This is the function doing the work. It is just a skeleton, real code has to be filled in. */ static void f (int n) { int m = 0; while (1) { /* This is where the work would be done. */ if (++m % 100 == 0) { putchar ('.'); fflush (stdout); } /* Regularly the expire variable must be checked. */ if (expired) { /* We do not want the program to run forever. */ if (++switches == 20) return; printf ("\nswitching from %d to %d\n", n, 3 - n); expired = 0; /* Switch to the other context, saving the current one. */ swapcontext (uc[n], uc[3 - n]); } } } /* This is the signal handler which simply set the variable. */ void handler (int signal) { expired = 1; } int main (void) { struct sigaction sa; struct itimerval it; char st1[8192]; char st2[8192]; /* Initialize the data structures for the interval timer. */ sa.sa_flags = SA_RESTART; sigfillset (sa.sa_mask); sa.sa_handler = handler; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 1; it.it_value = it.it_interval; /* Install the timer and get the context we can manipulate. */ if (sigaction (SIGPROF, sa, NULL) 0 || setitimer (ITIMER_PROF, it, NULL) 0 || getcontext (uc[1]) == -1 || getcontext (uc[2]) == -1) abort (); /* Create a context with a separate stack which causes the function f to be call with the parameter 1. Note that the uc_link points to the main context which will cause the program to terminate once the function return. */ uc[1].uc_link = uc[0]; uc[1].uc_stack.ss_sp = st1; uc[1].uc_stack.ss_size = sizeof st1; makecontext (uc[1], (void (*) (void)) f, 1, 1); /* Similarly, but 2 is passed as the parameter to f. */ uc[2].uc_link = uc[0]; uc[2].uc_stack.ss_sp = st2; uc[2].uc_stack.ss_size = sizeof st2; makecontext (uc[2], (void (*) (void)) f, 1, 2); /* Start running. */ swapcontext (uc[0], uc[1]); putchar ('\n'); return 0; }
This an example how the context functions can be used to implement co-routines or cooperative multi-threading. All that has to be done is to call every once in a while swapcontext to continue running a different context. It is not allowed to do the context switching from the signal handler directly since neither setcontext nor swapcontext are functions which can be called from a signal handler. But setting a variable in the signal handler and checking it in the body of the functions which are executed. Since swapcontext is saving the current context it is possible to have multiple different scheduling points in the code. Execution will always resume where it was left.