1. 引言
在 select 基础中已经提到过,select 函数可能会返回错误,比如使用了错误的描述符,或者被信号打断。第一种情况使用了错误的描述符,这一般是人犯的错,而第二种被信号打断,谈不上是一种真正的错误,对这种情况,程序要能考虑到。
有人说,要防止 select 被信号打断,可不可以在注册信号的时候,将其设置成自动重启的?不行,对于所有的 IO 利用类函数都不支持自动重启,也就是说一旦有信号到来,会被立即中断。如果你忘记了什么是自动重启,请参考《中断系统调用与自动重启动》。
当 select 被信号打断后,返回值 < 0,同时 errno 会被置成 EINTR. 接下来,通过实验来进行验证。
2. 实验
程序 select_sig.c 捕捉了 SIGALRM 信号,如果程序在运行的时候收到了 SIGALRM 信号,select 函数会出错返回。
2.1 代码
// select_sig.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define PERR(msg) do { perror(msg); exit(1); } while(0);
void handler(int sig) {
if (sig == SIGALRM)
puts("Hello SIGALRM");
}
int process(char* prompt, int fd) {
int n;
char buf[64];
char line[64];
n = read(fd, buf, 64);
if (n < 0) {
// error
PERR("read");
}
else if (n == 0) {
// peer close
sprintf(line, "%s closed\n", prompt);
puts(line);
return 0;
}
else if (n > 0) {
buf[n] = 0;
sprintf(line, "%s say: %s", prompt, buf);
puts(line);
}
return n;
}
int main () {
int n, res, fd0, maxfd;
char buf[64];
struct sigaction sa;
fd_set st;
// 打印 pid
printf("pid = %d\n", getpid());
// 安装信号处理函数
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
// 为了简化程序,这里只管理一个描述符
FD_ZERO(&st);
fd0 = STDIN_FILENO;
FD_SET(fd0, &st);
maxfd = fd0 + 1;
while(1) {
fd_set tmpset = st;
res = select(maxfd, &tmpset, NULL, NULL, NULL);
if (res < 0) {
// 如果被信号打断的,不让程序退出,直接 continue
if (errno == EINTR) {
perror("select");
continue;
}
// 其它情况的错误,直接让程序退出
PERR("select");
}
else if (res == 0) {
// timeout
continue;
}
if (FD_ISSET(fd0, &tmpset)) {
n = process("fd0", fd0);
if (n == 0) FD_CLR(fd0, &st);
}
}
}
2.2 编译和运行
- 编译
$ gcc select_sig.c -o select_sig
- 运行
启动 select_sig 后,在另一个终端给它发 SIGALRM 信号。
图1 程序收到信号后,select_sig 被中断
3. 总结
- 知道使用 select 函数的时候,要处理被信号中断的情况。