2012-12-26 38 views
2

我想實現一個基本的事件循環與pselect,所以我已經阻止了一些信號,保存了信號掩碼並將其與pselect一起使用,以便信號只在該呼叫期間傳遞。在OSX和Linux上不同的pselect()行爲?

如果在pselect呼叫之外發送信號,它將被阻止,直到它應該爲止,但是它不會中斷pselect呼叫。如果一個信號在pselect被阻塞時發送,它將被處理並且pselect將被中斷。這種行爲只存在於OSX中,在linux中似乎正常工作。

這裏是一個代碼示例:

#include <stdio.h> 
#include <string.h> 
#include <sys/select.h> 
#include <errno.h> 
#include <unistd.h> 
#include <signal.h> 

int shouldQuit = 0; 

void signalHandler(int signal) 
{ 
    printf("Handled signal %d\n", signal); 
    shouldQuit = 1; 
} 

int main(int argc, char** argv) 
{  
    sigset_t originalSignals;  
    sigset_t blockedSignals; 
    sigemptyset(&blockedSignals); 
    sigaddset(&blockedSignals, SIGINT); 
    if(sigprocmask(SIG_BLOCK, &blockedSignals, &originalSignals) != 0) 
    { 
     perror("Failed to block signals"); 
     return -1; 
    } 

    struct sigaction signalAction; 
    memset(&signalAction, 0, sizeof(struct sigaction)); 

    signalAction.sa_mask = blockedSignals; 

    signalAction.sa_handler = signalHandler; 

    if(sigaction(SIGINT, &signalAction, NULL) == -1) 
    { 
     perror("Could not set signal handler"); 
     return -1; 
    } 

    while(!shouldQuit) 
    { 
     fd_set set; 
     FD_ZERO(&set); 
     FD_SET(STDIN_FILENO, &set); 
     printf("Starting pselect\n"); 
     int result = pselect(STDIN_FILENO + 1, &set, NULL, NULL, NULL, &originalSignals); 
     printf("Done pselect\n"); 
     if(result == -1) 
     { 
      if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) 
      { 
       perror("pselect failed"); 
      } 
     } 
     else 
     { 
      printf("Start Sleeping\n"); 
      sleep(5); 
      printf("Done Sleeping\n"); 
     } 
    } 

    return 0; 
} 

程序等待,直到你輸入什麼標準輸入,然後睡5秒。爲了創建問題,在「stdin」上鍵入「a」來創建數據。然後,當程序正在休眠時,INT信號與Crtl-C一起發送。

在Linux上:

Starting pselect 
a 
Done pselect 
Start Sleeping 
^CDone Sleeping 
Starting pselect 
Handled signal 2 
Done pselect 

在OSX:

Starting pselect 
a 
Done pselect 
Start Sleeping 
^CDone Sleeping 
Starting pselect 
Handled signal 2 
^CHandled signal 2 
Done pselect 

回答

2

證實,它作用於OSX這樣的,如果你看一下源PSELECT(HTTP://www.opensource .apple.com/source/libc/libc-320.1.3/gen/FreeBSD/pselect.c),你會明白爲什麼。

在sigprocmask()恢復信號掩碼後,內核將信號傳遞給進程,並且調用您的處理程序。這裏的問題是,信號可以在調用select()之前傳遞,所以select()不會返回一個錯誤。

http://lwn.net/Articles/176911/有一些關於此問題的更多討論 - linux用於使用類似的具有相同問題的用戶空間實現。

如果你想在所有平臺上安全模式,你必須使用libev或libevent之類的東西,並讓它們處理混亂,或者使用sigprocmask()和select()。

例如

sigset_t omask; 
    if (sigprocmask(SIG_SETMASK, &originalSignals, &omask) < 0) { 
     perror("sigprocmask"); 
     break; 
    } 

    /* Must re-check the flag here with signals re-enabled */ 
    if (shouldQuit) 
     break; 

    printf("Starting select\n"); 
    int result = select(STDIN_FILENO + 1, &set, NULL, NULL, NULL); 
    int save_errno = errno; 
    if (sigprocmask(SIG_SETMASK, &omask, NULL) < 0) { 
     perror("sigprocmask"); 
     break; 
    } 

    /* Recheck again after the signal is blocked */ 
    if (shouldQuit) 
     break; 

    printf("Done pselect\n"); 

    if(result == -1) 
    { 
     errno = save_errno; 
     if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) 
     { 
      perror("pselect failed"); 
     } 
    } 

有一對夫婦的其他東西,你應該用你的代碼做:

  • 宣告你的 'shouldQuit' 變量揮發性sig_atomic_t

    揮發性sig_atomic_t shouldQuit = 0;

  • 總是在調用任何其他函數(如printf())之前保存errno,因爲該函數可能會導致errno被另一個值覆蓋。這就是爲什麼在select()調用之後,上面的代碼立即產生了errno。

真的,我強烈建議使用現有的事件循環處理庫像libev或libevent的 - 我做的,即使我可以寫我自己的,因爲它是那麼容易出錯。

相關問題