Переключение между выполнением двух контекстов функции

Я пытаюсь написать программу, в которой я определил две функции, и одна печатает нечетные числа, а другая печатает четные числа. Программа выполняет функцию в течение определенного периода времени, и когда она получает сигнал тревоги, она начинает выполнять вторую функцию после сохранения контекста текущей функции. Когда он получает следующий сигнал тревоги, он возобновляет выполнение первой функции из последнего сохраненного контекста.

Для этого я использовал функции getcontext и swapcontext.

Вот мой код:

#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();
}

Вывод, который я получил:

odd:1
odd:3
even:0
even:2
odd:4
odd:6
even:8
even:10
odd:12

Почему он восстанавливает контексты каждый раз, но все еще печатает значения функции nextEven()?

1 ответ

Решение

Эта программа содержит две ошибки и несколько ошибок.

Первая ошибка очень проста:

int switch_context = 0, first_call = 1;

Переменная switch_context используется для связи от асинхронного обработчика сигнала к основной программе. Поэтому для правильной работы необходимо указать тип volatile sig_atomic_t, (Если вы этого не сделаете, компилятор может предположить, что никто никогда не устанавливает switch_context на 1, и удалите все звонки на swapcontext!) sig_atomic_t может быть как маленький char, но вы только когда-либо установить switch_context до 0 или 1, так что это не проблема.

Вторая ошибка более сложна: вы вообще не инициализируете контексты сопрограмм. Это привередливо и плохо объясняется руководствами. Вы должны сначала позвонить getcontext в каждом контексте. Для каждого контекста, отличного от исходного контекста, вы должны выделить для него стек и применить makecontext определить точку входа. Если вы не делаете все эти вещи, swapcontext/setcontext потерпит крах. Полная инициализация выглядит примерно так:

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);

(Нет хорошего способа узнать, сколько стека выделить, но восьми мегабайт должно хватить для любого. Я полагаю, вы могли бы использовать getrlimit(RLIMIT_STACK), В производственной программе я бы использовал mmap чтобы я мог потом использовать mprotect определить защитные полосы на обеих сторонах стека, но для демонстрации, подобной этой, очень много дополнительного кода.)

На погрешности. Вы должны всегда использовать sigaction устанавливать обработчики сигналов, а не signal, так как signal недостаточно указан. (Обратите внимание, что sigaction недоступно в Windows. Это потому, что сигналы являются ложными в Windows и не должны использоваться вообще.) Вы также не должны использовать alarm ни sleepпотому что они также не указаны и могут катастрофически взаимодействовать друг с другом. Вместо этого используйте setitimer (или же timer_settime, но это ново в POSIX.1-2008, тогда как функции ucontext были сняты в -2008) и nanosleep, Это также имеет то преимущество, что вы можете установить повторяющийся таймер и забыть об этом.

Кроме того, ваша программа может быть существенно упрощена, если вы поймете, что вам нужно только два контекста, а не три. использование c2 для оригинального контекста, и непосредственно позвоните nextOdd, Это устраняет first_call а также cmain и сложная логика переключения в nextOdd а также nextEven,

Наконец, ваши переменные индекса цикла в nextOdd а также nextEven должно быть unsigned так, чтобы поведение было четко определено после переноса (если вы хотите подождать 2^31 секунд), и вы должны установить для stdout буферизацию строки, чтобы каждая строка вывода появлялась немедленно, даже если она перенаправлена ​​в файл.

Собрав все это вместе, я получаю это:

#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 */
}
Другие вопросы по тегам