I am trying to write a program where I have defined two functions and one prints odd numbers while the other prints even numbers. The program executes a function for a certain amount of time and when it receives an alarm signal, it starts executing the second function after saving the current function's context. When it receives the next alarm signal, it resumes the execution of the first function from its last saved context.
I have used the functions getcontext and swapcontext for this.
Here is my code:
#include<stdio.h>
#include<signal.h>
#include<ucontext.h>
ucontext_t c1, c2, cmain;
int switch_context = 0, first_call = 1;
void handler(int k)
{
switch_context = 1;
}
void nextEven()
{
int i;
for(i = 0; ; i += 2)
{
if(switch_context)
{
alarm(2);
switch_context = 0;
if(first_call)
{
first_call = 0;
swapcontext(&c1, &cmain);
}
else
{
swapcontext(&c1, &c2);
}
}
printf("even:%d\n", i);
sleep(1);
}
}
void nextOdd()
{
int j;
for(j = 1; ; j += 2)
{
if(switch_context)
{
alarm(2);
switch_context = 0;
if(first_call)
{
first_call = 0;
swapcontext(&c2, &cmain);
}
else
{
swapcontext(&c2, &c1);
}
}
printf("odd:%d\n", j);
sleep(1);
}
}
int main()
{
signal(SIGALRM, handler);
alarm(2);
getcontext(&cmain);
if(first_call) nextOdd();
nextEven();
}
The output I received is:
odd:1
odd:3
even:0
even:2
odd:4
odd:6
even:8
even:10
odd:12
Why is it restoring contexts every time but still printing the values of the function nextEven() ?
This program contains two outright bugs and several infelicities.
The first bug is very simple:
int switch_context = 0, first_call = 1;
The variable switch_context
is used to communicate from an asynchronous signal handler to the main program. Therefore, for correct operation, it must be given the type volatile sig_atomic_t
. (If you don't do this, the compiler may assume that nobody ever sets switch_context
to 1, and delete all the calls to swapcontext
!) sig_atomic_t
might be as small as char
, but you only ever set switch_context
to 0 or 1, so that's not a problem.
The second bug is more involved: you aren't initializing your coroutine contexts at all. This is finicky and poorly explained by the manpages. You must first call getcontext
on each context. For each context other than the original context, you must then allocate a stack for it, and apply makecontext
to define the entry point. If you don't do all of these things, swapcontext
/setcontext
will crash. A full initialization looks something like this:
getcontext(&c1);
c1.uc_stack.ss_size = 1024 * 1024 * 8;
c1.uc_stack.ss_sp = malloc(1024 * 1024 * 8);
if (!c1.uc_stack.ss_sp) {
perror("malloc");
exit(1);
}
makecontext(&c1, nextEven, 0);
(There's no good way to know how much stack to allocate, but eight megabytes ought to be enough for anyone. I suppose you could use getrlimit(RLIMIT_STACK)
. In a production-grade program I would use mmap
so that I could then use mprotect
to define guard bands on both sides of the stack, but that's a lot of extra code for a demo like this.)
On to the infelicities. You should always use sigaction
to set signal handlers, not signal
, because signal
is underspecified. (Note that sigaction
is not available on Windows. That is because signals are fake on Windows and should not be used at all.) You should also not use alarm
nor sleep
, because they are also underspecified, and may interact catastrophically with each other. Instead use setitimer
(or timer_settime
, but that's new in POSIX.1-2008, whereas the ucontext functions were withdrawn in -2008) and nanosleep
. This also has the advantage that you can set a repeating timer and forget about it.
Also, your program can be substantially simplified by realizing that you only need two contexts, not three. Use c2
for the original context, and directly call nextOdd
. This eliminates first_call
and cmain
and the complicated switching logic in nextOdd
and nextEven
.
Finally, your loop index variables in nextOdd
and nextEven
should be unsigned
so that the behavior is well-defined upon wrapping around (if you care to wait 2^31 seconds), and you should set stdout to line-buffered so that each line of output appears immediately even if redirected to a file.
Putting it all together I get this:
#define _XOPEN_SOURCE 600 /* ucontext was XSI in Issue 6 and withdrawn in 7 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <ucontext.h>
#include <unistd.h>
#ifdef __GNUC__
#define UNUSED(arg) arg __attribute__((unused))
#else
#define UNUSED(arg) arg
#endif
static ucontext_t c1, c2;
static volatile sig_atomic_t switch_context = 0;
static void
handler(int UNUSED(signo))
{
switch_context = 1;
}
static void
nextEven(void)
{
struct timespec delay = { 1, 0 };
for (unsigned int i = 0;; i += 2) {
if (switch_context) {
switch_context = 0;
swapcontext(&c1, &c2);
}
printf("even:%d\n", i);
nanosleep(&delay, 0);
}
}
static void
nextOdd(void)
{
struct timespec delay = { 1, 0 };
for (unsigned int i = 1;; i += 2) {
if (switch_context) {
switch_context = 0;
swapcontext(&c2, &c1);
}
printf("odd:%d\n", i);
nanosleep(&delay, 0);
}
}
int
main(void)
{
/* flush each printf as it happens */
setvbuf(stdout, 0, _IOLBF, 0);
/* initialize main context */
getcontext(&c2);
/* initialize coroutine context */
getcontext(&c1);
c1.uc_stack.ss_size = 1024 * 1024 * 8;
c1.uc_stack.ss_sp = malloc(1024 * 1024 * 8);
if (!c1.uc_stack.ss_sp) {
perror("malloc");
exit(1);
}
makecontext(&c1, nextEven, 0);
/* initiate periodic timer signals */
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGALRM, &sa, 0)) {
perror("sigaction");
exit(1);
}
struct itimerval it;
memset(&it, 0, sizeof it);
it.it_interval.tv_sec = 2;
it.it_value.tv_sec = 2;
if (setitimer(ITIMER_REAL, &it, 0)) {
perror("setitimer");
exit(1);
}
nextOdd(); /* does not return */
}