参考:《UNIX 网络编程 · 卷1 : 套接字联网API》
pselect 函数
pselect 函数是由 POSIX 发明的,如今许多 Unix 变种都支持它。pselect 函数原型:
#include <sys/select.h>
#include <signal.h>
#include <time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);
pselect 相对于通常的 select 有两个区别:
-
pselect 使用 timespec 结构,而不使用 timeval 结构。timespec 结构是 POSIX 的又一个发明。
struct timespec { time_t tv_sec; //秒 long tv_nsec; //纳秒 };
这两个结构的区别在于第二个成员:timespec的成员 tv_nsec 指定纳秒数,而 timeval 的该成员 tv_usec 指定微秒数。
-
pselect 函数增加了第六个参数:一个指向信号掩码的指针。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止的信号处理函数设置的全局变量,然后调用 pselect,告诉它重新设置信号掩码。
考虑下面的例子,这个程序的 SIGINT 信号处理函数仅仅设置全局变量 intr_flag 并返回。如果我们的进程阻塞于select 调用,那么从信号处理函数的返回将导致 select 返回 EINTR 错误。然而调用 select 时,代码看起来大体如下:
if( intr_flag ) handle_intr(); if( (nready = select(...)) < 0 ) { if( errno == EINTR ) { if( intr_flag ) handle_intr(); } ... }
问题是在测试 intr_flag 和调用 select 之间如果有信号发生,那么要是 select 永远阻塞,该信号就会丢失。
有了 pselect 后,我们可以如下可靠地编写这个例子的代码:
sigset_t newmask, oldmask, zeromask; sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGINT); sigprocmask(SIG_BLOCK, &newmask, &oldmask); //block SIGINT if(intr_flag) handle_intr(); if( (nready = pselect(..., &zeromask)) < 0 ) { if(errno == EINTR) { if(intr_flag) handle_intr(); } ... }
在测试 intr_flag 变量之前,我们阻塞 SIGINT。当 pselect 被调用时,它先以空集(zeromask)取代进程的信号掩码,再检查描述字,并可能进入睡眠。然而当 pselect 函数返回时,进程的信号掩码又被重置为调用 pselect 之前的值(即SIGINT被阻塞),也就是说只有在 pslecet 中,SIGINT 是不被阻塞的。
注意:pselect(…, &zeromask) 与 pselect(…, NULL) 意义完全不同,前者是在调用期间不阻塞任何新信号,后者和调用 select 相同,保持原来的信号 mask。
有 sigmask 的时候,pselect 相当于如下的 select() 函数,在进入 select() 函数之前手动将信号的掩码改变,并保存之前的掩码值。select() 函数执行之后,再恢复为之前的信号掩码值。
sigset_t origmask;
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);
示例说明
- 用 pselect 屏蔽信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t sigmask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
//set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
//initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
if (pselect(maxfdp1, &rset, NULL, NULL, NULL, &sigset) <= 0)
//if (select(maxfdp1, &rset, NULL, NULL, NULL) <= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}
上段代码如果没有 CTRL+C 送上一个 SIGINT 信号,将永远阻塞在与用户的交互上,ALARM 产生的 SIGALRM 信号永远打断不了 PSELECT,ALARM 信号被成功屏蔽。
- 用 sigprocmask 屏蔽信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t zeromask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
//set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
if (sigemptyset(&zeromask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
//initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
//block SIGALRMsignal
if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)
err_sys("sigprocmask");
//generate SIGALRM signal
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
if (select(maxfdp1, &rset, NULL, NULL, NULL) <= 0)
//if (pselect(maxfdp1, &rset, NULL, NULL, NULL, &zeromask)<= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}
上段代码如果没有 CTRL+C 送上一个 SIGINT 信号,将永远阻塞在与用户的交互上,ALARM 产生的 SIGALRM 信号永远打断不了 PSELECT,ALARM 信号被成功屏蔽
- 如果上面代码改成下面则无法屏蔽信号,因为我们使用 zeromask,它不屏蔽任何信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t zeromask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
//set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
if (sigemptyset(&zeromask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
//initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
//block SIGALRMsignal
if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)
err_sys("sigprocmask");
//generate SIGALRM signal
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
//if (select(maxfdp1, &rset, NULL, NULL, NULL)<= 0)
if (pselect(maxfdp1, &rset, NULL, NULL, NULL, &zeromask) <= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}
- 仅仅使用 select,不使用 sigprocmask 也无法屏蔽信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t sigmask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
//set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
//initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
//if (pselect(maxfdp1, &rset, NULL, NULL, NULL,&sigset) <= 0)
if (select(maxfdp1, &rset, NULL, NULL, NULL) <= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}
- 用 pselect,但信号屏蔽参数为 NULL, 同使用 select 一样,无法屏蔽信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#define BUFFSIZE 80
void sig_int(int signo);
void err_sys(const char *p_error);
void sig_alrm(int signo)
{
char s[] = "receive";
psignal(signo, s);
return;
}
int main(int argc, char **argv)
{
int maxfdp1;
fd_set rset;
sigset_t sigmask;
ssize_t nread;
char buf[BUFFSIZE];
sigset_t sigset;
struct sigaction act;
//set SIGALRM signal handler
act.sa_handler = sig_alrm;
if (sigemptyset(&act.sa_mask) == -1)
err_sys("sigemptyset");
act.sa_flags = 0;
if (sigaction(SIGALRM, &act, NULL) == -1)
err_sys("sigaction");
//initialize signal set and addition SIGALRM into sigset
if (sigemptyset(&sigset) == -1)
err_sys("sigemptyet");
if (sigaddset(&sigset, SIGALRM) == -1)
err_sys("sigaddset");
alarm(1);
FD_ZERO(&rset);
FD_SET(STDIN_FILENO, &rset);
maxfdp1 = STDIN_FILENO + 1;
//if (pselect(maxfdp1, &rset, NULL, NULL, NULL,&sigset) <= 0)
if (pselect(maxfdp1, &rset, NULL, NULL, NULL, NULL) <= 0)
//if (select(maxfdp1, &rset, NULL, NULL, NULL) <= 0)
err_sys("pselect error");
if (FD_ISSET(STDIN_FILENO, &rset))
{
if ((nread = read(STDIN_FILENO, buf, BUFFSIZE)) == -1)
err_sys("read error");
if (write(STDOUT_FILENO, buf, nread) != nread)
err_sys("write error");
}
exit(0);
}
void sig_int(int signo)
{
char s[] = "received";
psignal(signo, s);
return;
}
void err_sys(const char *p_error)
{
perror(p_error);
exit(1);
}