pselect如何在网络编程中使用信号掩码阻止信号

时间:2017-09-04 19:01:36

标签: sockets unix network-programming multiplexing

我目前正在研究网络编程的概念,其中我遇到了一个函数pselect(),它解决了select的问题。使用select(),有可能出现问题,即在intr_flag的测试和select之间的调用之间,如果信号发生,如果永远选择块,它将会丢失。

if (intr_flag)
 handle_intr(); /* handle the signal */
if ( (nready = select( ... )) < 0) {
 if (errno == EINTR) {
 if (intr_flag)
 handle_intr();
 }

然而,它说,使用pselect,我们现在可以将此示例可靠地编码为

sigset_t newmask, oldmask, zeromask;
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* block SIGINT */
if (intr_flag)    //here
 handle_intr(); /* handle the signal */ 
if ( (nready = pselect ( ... , &zeromask)) < 0) {
 if (errno == EINTR) {      //here
 if (intr_flag)
 handle_intr ();
 }
 ...
}

它给出的代码可靠的解释是 - 在测试intr_flag变量之前,我们阻止SIGINT。当调用pselect时,它用空集(即零掩码)替换进程的信号掩码,然后检查描述符,可能会进入休眠状态。但是当pselect返回时,进程的信号掩码会在调用pselect之前重置为其值(即,SIGINT被阻止)。

但是在上面提到的带有pselect的代码中,我们阻止信号然后我们如何检查错误EINTR?由于pselect阻塞所有信号,因此当中断发生时,它应该阻止中断或传递给进程。只有当pselect返回时,才能传递信号。

根据前面提到这里的注释,INTERRUPT信号仍然可以在调用pselect之前发生,或者在第一次检查和pselect之间或者当pselect被调用时与之相反阻止中断和任何其他信号,因此应该导致竞争条件,就像选择那样。

请任何人解释这是如何可能的,因为我是这些概念的新手。

1 个答案:

答案 0 :(得分:1)

好吧,主要的想法是,ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask);等同于执行以下操作原子

sigset_t sigsaved;

sigprocmask(SIG_SETMASK, &sigmask, &sigsaved);
/* NB: NOTE-1 */
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &sigsaved, NULL);

要使用此功能,我们首先阻止,例如SIGINT

sigset_t emptyset, blockset;

sigemptyset(&blockset);
sigaddset(&blockset, SIGINT);
sigprocmask(SIG_BLOCK, &blockset, NULL);

此时我们无法收到SIGINT,因此我们无法处理它。但是在我们输入pselect()之前我们不需要它。我们想做什么 我们阻止SIGINT之后就是设置一个合适的信号处理程序。

说,我们在主代码之外声明了一个标志static volatile int intr_flag = 0;,我们定义了一个名为handler()的处理程序,它只执行intr_flag = 1;。因此,我们将其设置为处理程序:

sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);

然后我们配置readfs(此处未显示声明), 初始化空信号集并调用pselect()

sigemptyset(&emptyset);
ready = pselect(nfds, &readfds, NULL, NULL, NULL, &emptyset);

因此,我将再次概述这一点 - 在致电SIGINT之前,我们不会收到pselect()。我们不需要它。我们输入pselect()后,信号SIGINT将被取消阻止(因为提供给pselect()的空信号集),pselect()可能被SIGINT中断}。 在pselect()返回时,我们再次不会再收到SIGINT,但如果{/ 1}}在 SIGINT期间发生,那么我们将会根据{{​​1}} pselect()检测到这一点,如果我们检查errno,我们会发现它是EINTR。我们将理解我们需要相应地行事。因此,很明显信号处理程序可以在信号解除阻塞后立即执行其工作,而后者则在intr_flag调用本身内发生。

这里最重要的细节是如果我们没有原子方式实现的特殊1调用,那么我们必须执行这些步骤使用通常的pselect()时,在我们自己的代码段中的pselect()标签周围。并且,由于不是原子,我们将有机会在/* NB: NOTE-1 */提示所在的两个操作之间将select()传递给我们,即在我们取消阻止信号传递之后和输入SIGINT之前。那时信号确实会丢失。这就是为什么我们需要/* NB: NOTE-1 */来电。

总而言之,select()原子性是其使用的解释。 如果您对这样的概念不太熟悉,可以参考维基百科上的article或计算机科学专题书。

此外,我将在LWN上提供article的链接,这个链接更为详尽。