0

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() ?

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Somebody
  • 165
  • 5
  • Given that your functions asynchronously access non-readonly, non lock-free-atomic, non `volatile sigatomic_t` objects, the behavior is *undefined*. – EOF Aug 22 '16 at 13:10
  • @EOF That is true but it is also an unhelpful wall of jargon. – zwol Aug 22 '16 at 13:26
  • @zwol: Why that? It is precise and clear. It uses standard terms, not some wishy-washy sentence. – too honest for this site Aug 22 '16 at 14:03
  • @Olaf You need to stop assuming that everyone has memorized all of the standards. – zwol Aug 22 '16 at 14:21
  • @zwol: I don't (you should stop assuming what I mean). Aber es ist einfacher wenn wir alle die gleiche Sprache sprechen. Im Zweifel muss man halt die Bedeutung nachschlagen. (didn't get what I mean? See? it is easier to use commonly accepted language and terms. Here it would be English, not German, in C programming the terms from the standard. Everyone asking here can bne expected to use google and read a C book. After all, we are **no** tutoring site!) – too honest for this site Aug 22 '16 at 14:53
  • @Olaf In a way we are a tutoring site, at least partly. I would seek a middleground here. Use specific termini but try to remain understandable to a larger audience. Just out of curiosity: How many C programmers understand "non lock-free-atomic" right away and couldn't it be written a bit easier? I will google it now. (Also "non-readonly" means the same as "writable", doesn't it?) – NoDataDumpNoContribution Aug 22 '16 at 20:46
  • @Trilarion: "How many C programmers understand "non lock-free-atomic" - Possibly not all who **should** know about them. But you swapped cause and effect. Any programmer working at that level with concurrency should be aware about those issues. For missing/unknown terms: there is google, Wikipedia and a lot of specialised sites like stack overflow. I'm ESL, so what do you think I do first if I read an unknow word here? Ask? No, I look it up! And that's what I **do expect** from any programmer - beginner or senior. Basta! – too honest for this site Aug 22 '16 at 20:57
  • @Trilarion: "Non-readonly" is not the same as "writeable". These are two different and - in the first place - not related things. But feel free to ask the author of the comment, I'm confident he can express himself. – too honest for this site Aug 22 '16 at 21:00

1 Answers1

2

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 */
}
zwol
  • 135,547
  • 38
  • 252
  • 361