我正在尝试编写一个程序,其中我定义了两个函数,一个打印奇数,而另一个打印偶数。程序执行一段时间的功能,当接收到报警信号时,程序在保存当前功能的上下文后开始执行第二功能。当它收到下一个报警信号时,它将从上次保存的上下文中恢复执行第一个功能。
我已经使用了函数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()的值?
答案 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
。这消除了c2
和nextOdd
以及first_call
和cmain
中复杂的切换逻辑。
最后,nextOdd
和nextEven
中的循环索引变量应该是nextOdd
,这样行为在环绕时就可以很好地定义(如果你需要等待2 ^ 31秒) ,你应该将stdout设置为line-buffered,这样即使重定向到文件,每行输出也会立即出现。
把它们放在一起我得到了这个:
nextEven