在两个函数上下文的执行之间切换

时间:2016-08-22 12:49:58

标签: c signals ucontext

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

我已经使用了函数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 个答案:

答案 0 :(得分:2)

这个程序包含两个完全错误和几个不足之处。

第一个错误非常简单:

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

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

关于不足之处。您应始终使用sigaction设置信号处理程序,而不是signal,因为signal未指定。 (请注意,sigaction在Windows上不可用。这是因为Windows上的信号是,根本不应该使用。)您也不应该使用alarm或{ {1}},因为它们未指定,并且可能互相灾难性地互动。相反,使用sleep(或setitimer,但这是POSIX.1-2008中的新内容,而ucontext函数在-2008中被撤回)和{{1 }}。这也有一个优点,你可以设置重复计时器而忘记它。

此外,通过意识到您只需要两个上下文而不是三个上下文,可以大大简化您的程序。将timer_settime用于原始上下文,然后直接调用nanosleep。这消除了c2nextOdd以及first_callcmain中复杂的切换逻辑。

最后,nextOddnextEven中的循环索引变量应该是nextOdd,这样行为在环绕时就可以很好地定义(如果你需要等待2 ^ 31秒) ,你应该将stdout设置为line-buffered,这样即使重定向到文件,每行输出也会立即出现。

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

nextEven