该程序包含两个彻底的错误和一些不合理之处。
第一个错误非常简单:
int switch_context = 0, first_call = 1;
该变量switch_context
用于从异步信号处理程序与主程序进行通信。因此,为了正确操作,必须给它 type 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 MB 对任何人来说都应该足够了。我想你可以使用getrlimit(RLIMIT_STACK)
. 在我将使用的生产级程序mmap
中,这样我就可以mprotect
在两者上定义保护带堆栈的两侧,但对于这样的演示来说,这是很多额外的代码。)
关于不合理之处。您应该始终使用sigaction
设置信号处理程序,而不是signal
,因为signal
未指定。(请注意,sigaction
这在 Windows 上不可用。那是因为信号在 Windows 上是假的,根本不应该使用。)你也不应该使用alarm
nor sleep
,因为它们也没有被指定,并且可能相互之间发生灾难性的交互。而是使用setitimer
(或timer_settime
,但这在 POSIX.1-2008 中是新的,而 ucontext 函数在 -2008 中被撤销) 和nanosleep
. 这还具有您可以设置重复计时器并忘记它的优点。
此外,通过意识到您只需要两个上下文而不是三个上下文,您的程序可以大大简化。用于c2
原始上下文,直接调用nextOdd
. 这消除了first_call
和中的cmain
复杂切换逻辑。nextOdd
nextEven
最后,你的循环索引变量应该是这样的nextOdd
,以便在环绕时行为是明确定义的(如果你想等待 2^31 秒),你应该将 stdout 设置为 line-buffered 以便每一行输出立即出现即使重定向到文件。nextEven
unsigned
把它们放在一起,我得到了这个:
#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 */
}