0

我正在尝试编写一个程序,其中定义了两个函数,一个打印奇数,另一个打印偶数。程序执行一个函数一定的时间,当它收到警报信号时,它会在保存当前函数的上下文后开始执行第二个函数。当它接收到下一个警报信号时,它会从上次保存的上下文中恢复第一个函数的执行。

为此,我使用了 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() 的值?

4

1 回答 1

2

该程序包含两个彻底的错误和一些不合理之处。

第一个错误非常简单:

int switch_context = 0, first_call = 1;

该变量switch_context用于从异步信号处理程序与主程序进行通信。因此,为了正确操作,必须给它 type volatile sig_atomic_t。(如果您不这样做,编译器可能会假设没有人设置switch_context为 1,并删除所有对 ! 的调用swapcontextsig_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);

(没有好办法知道要分配多少堆栈,但 8 MB 对任何人来说都应该足够了。我想你可以使用getrlimit(RLIMIT_STACK). 在我将使用的生产级程序mmap中,这样我就可以mprotect在两者上定义保护带堆栈的两侧,但对于这样的演示来说,这是很多额外的代码。)

关于不合理之处。您应该始终使用sigaction设置信号处理程序,而不是signal,因为signal未指定。(请注意,sigaction这在 Windows 上不可用。那是因为信号在 Windows 上是的,根本不应该使用。)你也不应该使用alarmnor sleep,因为它们没有被指定,并且可能相互之间发生灾难性的交互。而是使用setitimer(或timer_settime,但这在 POSIX.1-2008 中是新的,而 ucontext 函数在 -2008 中被撤销) 和nanosleep. 这还具有您可以设置重复计时器并忘记它的优点。

此外,通过意识到您只需要两个上下文而不是三个上下文,您的程序可以大大简化。用于c2原始上下文,直接调用nextOdd. 这消除了first_call和中的cmain复杂切换逻辑。nextOddnextEven

最后,你的循环索引变量应该是这样的nextOdd,以便在环绕时行为是明确定义的(如果你想等待 2^31 秒),你应该将 stdout 设置为 line-buffered 以便每一行输出立即出现即使重定向到文件。nextEvenunsigned

把它们放在一起,我得到了这个:

#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 */
}
于 2016-08-22T13:25:26.760 回答