我试图用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
最佳答案
确认了它在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上有更多关于这个问题的讨论,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
易失性sig_-atomic_t shouldQuit=0;
在调用任何其他函数(如printf())之前,请始终保存errno,因为该函数可能会导致用其他值覆盖errno这就是为什么上面的代码在select()调用之后立即出现错误。
实际上,我强烈建议使用一个现有的事件循环处理库,比如LiBeV或LiBevun-I,即使我可以自己编写,因为它很容易出错。
关于c - OSX与Linux上不同的pselect()行为?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14045801/