select() - Unix,Linux系统调用
广告
名称select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - 同步I/O多路复用概要
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#define _XOPEN_SOURCE 600
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
|
描述
select() 和 pselect() 允许程序监控多个文件描述符,等待直到一个或多个文件描述符准备好进行某种类型的I/O操作(例如,可能的输入)。如果可以执行相应的I/O操作(例如,read(2))而不会阻塞,则认为文件描述符已准备好。select() 和 pselect() 的操作相同,但有三点不同:
标签 | 描述 |
(i) |
select() 使用一个struct timeval(以秒和微秒为单位)作为超时,而 pselect() 使用一个struct timespec(以秒和纳秒为单位)。 |
(ii) |
select() 可能会更新timeout参数以指示剩余时间。pselect() 不会更改此参数。 |
(iii) |
select() 没有sigmask参数,其行为与使用NULL sigmask调用的pselect()相同。 |
监控三组独立的文件描述符。列在readfds中的文件描述符将被监控,以查看是否有字符可供读取(更准确地说,查看读取是否不会阻塞;特别是,在文件结束时文件描述符也已准备好),列在writefds中的文件描述符将被监控,以查看写入是否不会阻塞,而列在exceptfds中的文件描述符将被监控以查看异常。退出时,集合将就地修改,以指示哪些文件描述符实际上更改了状态。如果不需要监控任何文件描述符以进行相应的事件类别,则可以将这三组文件描述符中的每一组指定为NULL。提供了四个宏来操作集合。FD_ZERO() 清除集合。FD_SET() 和 FD_CLR() 分别向集合添加和删除给定的文件描述符。FD_ISSET() 测试文件描述符是否是集合的一部分;这在select()返回后非常有用。
nfds 是三组集合中编号最高的描述符加 1。
timeout 是select()返回前经过时间的上限。它可以为零,导致select()立即返回。(这对于轮询很有用。)如果timeout为NULL(无超时),select()可以无限期阻塞。
sigmask 是指向信号掩码的指针(参见sigprocmask(2));如果它不是NULL,则pselect()首先用sigmask指向的掩码替换当前信号掩码,然后执行“select”函数,然后恢复原始信号掩码。 除了timeout参数的精度不同之外,以下pselect()调用
ready = pselect(nfds, &readfds, &writefds, &exceptfds,
timeout, &sigmask);
|
等效于原子地执行以下调用:
sigset_t origmask;
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);
|
需要pselect()的原因是,如果要等待信号或文件描述符准备好,则需要原子测试以防止竞争条件。(假设信号处理程序设置全局标志并返回。那么,如果信号恰好在测试之后但在调用之前到达,则对该全局标志的测试之后调用select()可能会无限期挂起。相反,pselect()允许首先阻塞信号,处理已到达的信号,然后使用所需的sigmask调用pselect(),从而避免了竞争。) 超时所涉及的时间结构在<sys/time.h>中定义,如下所示:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
|
和
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
|
(但是,请参见下面的POSIX.1-2001版本。) 某些代码使用所有三个集合为空、n为零且timeout为非NULL的select()作为以亚秒精度睡眠的相当可移植的方式。 在Linux上,select()修改timeout以反映未睡眠的时间;大多数其他实现不会这样做。(POSIX.1-2001允许这两种行为。)这在将读取timeout的Linux代码移植到其他操作系统时以及将代码移植到在循环中对多个select()重用struct timeval而不重新初始化它的Linux时都会导致问题。认为select()返回后timeout未定义。 返回值成功时,select()和pselect()返回三个返回的描述符集合中包含的文件描述符的数量(即,在readfds、writefds、exceptfds中设置的位的总数),如果超时在发生任何有趣的事情之前过期,则该数量可能为零。出错时,返回-1,并适当地设置errno;集合和timeout变得未定义,因此在错误后不要依赖它们的内容。错误
标签 | 描述 |
EBADF | 在一组中给出了无效的文件描述符。(可能是已经关闭的文件描述符,或者在其上发生错误的文件描述符。) |
EINTR | 捕获了一个信号。 |
EINVAL |
nfds 为负,或者timeout中包含的值无效。 |
ENOMEM | 无法为内部表分配内存。 |
示例
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void) {
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don’t rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.\n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.\n");
return 0;
}
|
符合标准
select()符合POSIX.1-2001和4.4BSD(select()首次出现在4.2BSD中)。通常可以移植到/从支持BSD套接字层克隆(包括System V变体)的非BSD系统。但是,请注意,System V变体通常在退出前设置timeout变量,但BSD变体不会。
pselect()在POSIX.1g和POSIX.1-2001中定义。 备注fd_set是固定大小的缓冲区。使用负值或等于或大于FD_SETSIZE的fd值执行FD_CLR()或FD_SET()将导致未定义的行为。此外,POSIX要求fd是有效的文件描述符。关于所涉及的类型,经典情况是timeval结构的两个字段是长整型(如上所示),并且该结构在<sys/time.h>中定义。POSIX.1-2001的情况是
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
|
其中结构在<sys/select.h>中定义,数据类型time_t和suseconds_t在<sys/types.h>中定义。 关于原型,经典情况是应该为select()包含<time.h>。POSIX.1-2001的情况是应该为select()和pselect()包含<sys/select.h>。Libc4和libc5没有<sys/select.h>头文件;在glibc 2.0及更高版本中,此头文件存在。在glibc 2.0中,它无条件地为pselect()提供了错误的原型,在glibc 2.1-2.2.1中,它在定义_GNU_SOURCE时提供pselect(),在glibc 2.2.2-2.2.4中,它在定义_XOPEN_SOURCE且值为600或更大时提供它。毫无疑问,自从POSIX.1-2001以来,它应该默认提供原型。 版本
pselect()在内核2.6.16中添加到Linux。在此之前,在glibc中模拟了pselect()(但请参见BUG)。Linux备注Linux pselect()系统调用修改其timeout参数。但是,glibc包装器函数通过使用传递给系统调用的超时参数的局部变量来隐藏此行为。因此,glibc pselect()函数不会修改其超时参数;这是POSIX.1-2001所需的行为。错误Glibc 2.0提供了不带sigmask参数的pselect()版本。从2.1版本开始,glibc提供了一个使用sigprocmask(2)和select()实现的pselect()模拟。此实现仍然容易受到pselect()旨在防止的竞争条件的影响。在缺乏pselect()的系统上,可以使用自管道技巧(其中信号处理程序向管道的另一端(在主程序中由select()监控)写入一个字节)实现可靠的(且更可移植的)信号捕获。 在Linux下,select()可能会将套接字文件描述符报告为“准备好读取”,而随后的读取仍然会阻塞。例如,当数据到达但检查后校验和错误并被丢弃时,可能会发生这种情况。在其他情况下,文件描述符也可能被错误地报告为已准备好。因此,在不应阻塞的套接字上使用O_NONBLOCK可能更安全。 参见
select_tut(2).
有关大致相关的资料,请参见accept(2)、connect(2)、poll(2)、read(2)、recv(2)、send(2)、sigprocmask(2)、write(2)、epoll(7)、feature_test_macros(7)
广告
|