OSX与Linux上的不同pselect()行为?

时间:2012-12-26 20:21:12

标签: c select signals

我正在尝试使用pselect实现基本事件循环,因此我阻止了一些信号,保存了信号掩码并将其与pselect一起使用,以便仅在该呼叫期间传递信号。

如果在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;
}

程序一直等到你在stdin上输入一些东西,然后睡了5秒钟。要创建问题,键入“a”以在stdin上创建数据。然后,当程序休眠时,与Crtl-C一起发送INT信号。

在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

1 个答案:

答案 0 :(得分: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()。

e.g。

    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'变量声明为volatile sig_atomic_t

    volatile sig_atomic_t shouldQuit = 0;

  • 总是在调用任何其他函数(例如printf())之前保存errno,因为该函数可能导致errno被其他值覆盖。这就是为什么上面的代码在select()调用之后立即发生错误。

真的,我强烈建议使用像libev或libevent这样的现有事件循环处理库 - 我这样做,即使我可以自己编写,因为它很容易弄错了。