我正在尝试使用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
答案 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这样的现有事件循环处理库 - 我这样做,即使我可以自己编写,因为它很容易弄错了。