2016-08-22 89 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 */ 
}