Эта программа содержит две откровенные ошибки и несколько неточностей.
Первая ошибка очень проста:
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
volatile sigatomic_t
, поведение не определено. - person EOF   schedule 22.08.2016