2016-08-22 51 views
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()的值是多少?

+0

鉴于你的函数异步访问非只读,非,自由原子锁的非挥发性'对象sigatomic_t',该行为是未定义* *。 – EOF

+0

@EOF这是真的,但它也是一个无用的墙专业术语。 – zwol

+0

@zwol:为什么?它确切而清晰。它使用标准的术语,而不是一些无意义的句子。 – Olaf

回答

2

该程序包含两个彻底的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_callcmain以及nextOddnextEven中的复杂切换逻辑。

最后,在nextOddnextEven你的循环索引变量,应该是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 */ 
}