2

我正在尝试用 实现一个基本的事件循环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;
}

程序一直等到您在标准输入上输入内容,然后休眠 5 秒。为了产生问题,键入“a”以在标准输入上创建数据。然后,当程序处于休眠状态时,使用 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
4

1 回答 1

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

    volatile sig_atomic_t shouldQuit = 0;

  • 始终在调用任何其他函数(例如 printf())之前保存 errno,因为该函数可能导致 errno 被另一个值覆盖。这就是为什么上面的代码在 select() 调用之后立即出现 errno。

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

于 2012-12-26T22:27:15.767 回答