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 为零以及非 NULL timeout 调用 select() 作为一种相当便携的方式来以亚秒级精度休眠。 在 Linux 上,select() 修改 timeout 以反映未休眠的时间;大多数其他实现都不会这样做。(POSIX.1-2001 允许这两种行为。)这会导致 Linux 代码读取 timeout 时移植到其他操作系统时出现问题,以及当代码移植到 Linux 时重用 struct timeval 在循环中进行多次 select() 调用而无需重新初始化它时出现问题。在 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 套接字层克隆的非 BSD 系统(包括 System V 变体)。但是,请注意,System V 变体通常在退出之前设置超时变量,但 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。在此之前,pselect() 在 glibc 中被模拟(但请参见 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)
广告
|