【问题标题】:Different pselect() behaviour on OSX vs Linux?OSX 和 Linux 上的不同 pselect() 行为?
【发布时间】:2012-12-26 20:21:12
【问题描述】:

我正在尝试使用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

【问题讨论】:

    标签: c select signals


    【解决方案1】:

    确认它在 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-03-22
      • 1970-01-01
      • 2019-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-04
      • 2021-04-26
      • 2019-05-24
      相关资源
      最近更新 更多