该程序包含两个彻底的bug和几个infelicities。
第一错误是非常简单的:
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,所以这不是问题。
第二个错误是更多的参与:你根本没有初始化你的协同程序上下文。这是非常挑剔的,并且很少被manpages解释。您必须先在每个环境下拨打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
定义在叠层两侧的防护带,但就是在这样一个演示了很多额外的代码。)
上至infelicities。您应始终使用sigaction
来设置信号处理程序,而不是signal
,因为signal
未指定。 (请注意,sigaction
并不适用于Windows,这是因为信号在Windows上是假,不应在所有使用。)您也不要使用alarm
也不sleep
,因为他们是也尚未得以确认,并可能灾难性互动与彼此。取而代之的是使用setitimer
(或timer_settime
,但这在POSIX.1-2008中是新的,而在2008年撤销的上下文函数是)和nanosleep
。这也有一个好处,你可以设置一个重复计时器并忘记它。
此外,您的程序可以通过意识到您只需要两个上下文而不是三个。原始上下文使用c2
,并直接拨打nextOdd
。这消除了first_call
和cmain
以及nextOdd
和nextEven
中的复杂切换逻辑。
最后,在nextOdd
和nextEven
你的循环索引变量,应该是unsigned
,这样的行为是在卷绕时左右(如果你愿意等待2^31秒),明确的,你应该设置标准输出到行缓冲这样即使重定向到文件,每行输出也会立即出现。
全部放在一起我得到这个:
#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 */
}
鉴于你的函数异步访问非只读,非,自由原子锁的非挥发性'对象sigatomic_t',该行为是未定义* *。 – EOF
@EOF这是真的,但它也是一个无用的墙专业术语。 – zwol
@zwol:为什么?它确切而清晰。它使用标准的术语,而不是一些无意义的句子。 – Olaf