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

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

Для этого я использовал функции 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()?


person Somebody    schedule 22.08.2016    source источник
comment
Учитывая, что ваши функции асинхронно обращаются к объектам, не предназначенным только для чтения, не атомарным без блокировок и не volatile sigatomic_t, поведение не определено.   -  person EOF    schedule 22.08.2016
comment
@EOF Это правда, но это также бесполезная стена жаргона.   -  person zwol    schedule 22.08.2016
comment
@zwol: Почему? Это точно и ясно. В нем используются стандартные термины, а не какие-то расплывчатые предложения.   -  person too honest for this site    schedule 22.08.2016
comment
@Olaf Вам нужно перестать предполагать, что все запомнили все стандарты.   -  person zwol    schedule 22.08.2016
comment
@zwol: нет (вы должны перестать предполагать, что я имею в виду). Aber ES ist einfacher wenn wir alle die gleiche Sprache sprechen. Im Zweifel muss man halt die Bedeutung nachschlagen. (не понял, что я имею в виду? Видите? проще использовать общепринятый язык и термины. Здесь был бы английский, а не немецкий, в программировании на C термины из стандарта. Каждый, кто спрашивает здесь, может бне ожидать, что будет использовать google и прочитайте книгу C. В конце концов, мы не обучающий сайт!)   -  person too honest for this site    schedule 22.08.2016
comment
@Olaf В каком-то смысле мы являемся обучающим сайтом, по крайней мере, частично. Я бы искал здесь золотую середину. Используйте определенные термины, но старайтесь оставаться понятными для большей аудитории. Просто из любопытства: сколько программистов на C сразу понимают non-lock-free-atomic и нельзя ли написать это немного проще? Я сейчас погуглю. (Также не только для чтения означает то же, что и для записи, не так ли?)   -  person Trilarion    schedule 22.08.2016
comment
@Trilarion: Сколько программистов на C разбираются в non-lock-free-atomic? Возможно, не все, кто должен знать о них. Но вы поменяли местами причину и следствие. Любой программист, работающий на этом уровне с параллелизмом, должен знать об этих проблемах. Для отсутствующих/неизвестных терминов: есть гугл, википедия и куча специализированных сайтов вроде переполнения стека. Я ESL, так что, по-вашему, я сделаю в первую очередь, если прочитаю здесь неизвестное слово? Спросить? Нет, я смотрю! И это то, что я действительно ожидаю от любого программиста, будь то начинающий или опытный. Баста!   -  person too honest for this site    schedule 22.08.2016
comment
@Trilarion: не только для чтения — это не то же самое, что доступный для записи. Это две разные и, во-первых, не связанные друг с другом вещи. Но не стесняйтесь спрашивать автора комментария, я уверен, что он сможет высказаться.   -  person too honest for this site    schedule 23.08.2016


Ответы (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 */
}
person zwol    schedule 22.08.2016