0%

引言

在上一篇文章 【select 函数】 中主要讲解了 select 相关的知识,本篇文章讲解它的增强版 pselect 函数。

1.0 pselect 函数

pselect 函数是 POSIX 发明的,如今有许多 Unix 变种支持它。(ps: 具体有哪些,还没调研)

1.1 函数声明

1
2
3
4
5
6
7
8
9
#include <sys/select.h>

int pselect( int nfds,
fd_set *restrict readset,
fd_set *restrict writeset,
fd_set *restrict exceptset,
const struct timespec *restrict timeout,
const sigset_t *restrict sigmask
)

相对于 select 函数的变化主要围绕最后两个参数展开,下面分别进行介绍。

1.2 参数

  • timeout 超时时间
1
2
3
4
5
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

相比于 select 函数,该超时时间精确到了纳秒(select 超时时间是微妙)。

  • sigmask 信号掩码
    指定需要屏蔽的信号集,为什么会有这个参数?像 select 这一类系统调用不能很好的与信号进行交互。比如有如下场景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void handler(int sig) {}

int main(int argc, char *argv[])
{
fd_set readfds;
struct sigaction sa;
int nfds, ready;

sa.sa_handler = handler; /* Establish signal handler */
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
/* ... */
ready = select(nfds, &readfds, NULL, NULL, NULL);
/* ... */

当 select 函数返回的时候,我们就可以根据返回值以及错误码 errno 来确定到底发生了什么。如果 errno 的值等于 EINTR,我们就知道 select 函数是被信号中断的。但是这里有个问题,就是当信号在 sigaction 函数调用之后 select 函数调用之前传递过去的,那么它将无法中断 select 调用,也就是说本来由信号中断的,但是它错过了(因为发生了竞态条件)。当然,专家们也想了其他的处理手段,但是成本太大了。为此,select 的增强版本 pselect 诞生了。

1.3 sigmask

sigmask 参数指定了一个应该在 pselect 调用期间阻塞的信号集合,它会在调用期间覆盖当前的信号掩码,当函数返回之后在恢复之前的信号掩码。当我们做以下调用时:

1
ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask);

这就相当于内核原子性的执行以下系统调用:

1
2
3
4
5
sigset_t sigsaved;

sigprocmask(SIG_SETMASK, &sigmask, &sigsaved);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &sigsaved, NULL);

我们举个例子进行说明:
目标:我们想当 SIGINTR 信号(假如为:1)发生的时候中断 pselect 函数调用,那么流程是这样的:

  1. 假如当前的信号掩码为:0,说明不阻塞任何信号传递。
  2. 我们设置信号掩码 sigmask 为 1 (即:SIGINTR 信号),以阻塞该信号传递。
  3. sigprocmask(SIG_SETMASK, &sigmask, &sigsaved) 函数调用之后,sigsaved 保存的是 0,SIGINTR 信号被阻塞。
  4. ready = select(nfds, &readfds, &writefds, &exceptfds, timeout) 函数开始调用,期间 SIGINTR 信号是一直阻塞状态,避免 select 函数执行期间被传递,造成 select 错过该信号。
  5. 当 select 函数返回后,sigprocmask(SIG_SETMASK, &sigsaved, NULL) 函数调用将之前的信号掩码恢复(sigsaved 为 0),SIGINTR 阻塞状态被取消,它可以正常传递了。
  6. 如果 SIGINTR 在发生,select 函数就可以捕获到了。

1.4 返回值

  • 0
    函数执行超时,即 timeout 参数。
  • -1
    函数执行出错。
  • n
    就绪的描述符数量。

    1.5 注意

  • 相比 select 函数,pselect 是不会修改 timeout 参数的

参考