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

how does pselect blocks signal using signal mask in network programming

我目前正在研究网络编程的概念,其中我遇到了一个函数 pselect() ,它解决了 select 的问题,即。使用select(),有可能出现问题,即在intr_flag的测试和对select的调用之间,如果信号出现,如果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 时,它用空集(即 zeromask)替换进程的信号掩码,然后检查描述符,可能会进入休眠状态。但是当 pselect returns 时,进程的信号掩码被重置为调用 pselect 之前的值(即,SIGINT 被阻塞)。

但是在上面提到的pselect的代码中,我们屏蔽了信号,那么我们如何检查错误EINTR呢?由于 pselect 阻止所有信号,因此当中断发生时,它应该阻止中断或被传递到进程。只有当pselect returns时才可以传递信号。

根据前面提到的评论这里的行,中断信号仍然可以在调用 pselect 之前或在第一次检查和pselect 或当 pselect 被调用时与阻止中断和任何其他信号的目的相矛盾,因此应该导致竞争条件,因为 select 在那里。

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

嗯,主要思想是 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);

因此,我将再次概述这一点 - 在我们调用 pselect() 之前,我们不会收到 SIGINT。我们不需要它。一旦我们输入 pselect(),信号 SIGINT 将被解锁(因为提供给 pselect() 的空信号集),并且 pselect() 可以被 [=15= 中断]. 在 pselect() returns 时,我们将不会再收到任何进一步的 SIGINT,但如果 期间发生 pselect(),那么我们会根据 errno 检测到它是 EINTR,如果我们检查 intr_flag,我们会发现它是 1。我们会明白我们需要相应地行事。所以,很明显,一旦信号被解锁,信号处理程序就可以完成它的工作,而后者发生在 pselect() 调用自身中。

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

总而言之,pselect()原子性就是对其使用的说明。 如果您对这样的概念不是很熟悉,可以参考维基百科article或计算机科学专题的专门书籍。

此外,我将在 LWN 上用 link 对 article 进行更详尽的回答。