我目前正在研究网络编程的概念,其中我遇到了一个函数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被调用时与之相反阻止中断和任何其他信号,因此应该导致竞争条件,就像选择那样。
请任何人解释这是如何可能的,因为我是这些概念的新手。
答案 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的链接,这个链接更为详尽。