select(2) - 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);

#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);
glibc 的功能测试宏要求(参见 feature_test_macros(7))
pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

描述

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() 阻塞等待文件描述符就绪的最短间隔。(该间隔会向上取整到系统时钟精度,且由于内核调度延迟,阻塞间隔可能会超出一点时间。)如果 timeval 结构的两个字段均为零,则 select() 会立即返回。(这对于轮询非常有用。)如果 timeout 为 NULL(无超时),select() 可以无限期阻塞。

sigmask 是指向信号掩码的指针(参见 sigprocmask(2));如果它不为 NULL,则 pselect() 首先用 sigmask 指向的掩码替换当前信号掩码,然后执行“select”函数,最后恢复原始的信号掩码。

除了 timeout 参数的精度差异外,以下 pselect() 调用

ready = pselect(nfds, &readfds, &writefds, &exceptfds,
                timeout, &sigmask);
等同于原子性地执行以下调用
 sigset_t origmask;

pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
 ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
 pthread_sigmask(SIG_SETMASK, &origmask, NULL);
需要 pselect() 的原因是:如果程序希望同时等待信号或等待文件描述符就绪,则需要一个原子测试来防止竞争条件。(假设信号处理程序设置了一个全局标志并返回。那么在测试该全局标志后紧接着调用 select(),如果信号恰好在测试之后、调用之前到达,程序可能会无限期挂起。相比之下,pselect() 允许先阻塞信号,处理已到达的信号,然后调用带有所需 sigmaskpselect(),从而避免了竞争。)

超时

涉及的时间结构定义在 <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 版本的说明。)

一些代码调用 select() 时将所有三个集合置空,nfds 为零,并提供一个非 NULL 的 timeout,这是一种实现亚秒级精度睡眠的相当可移植的方法。

在 Linux 上,select() 会修改 timeout 以反映未睡眠的时间;大多数其他实现不会这样做。(POSIX.1-2001 允许这两种行为。)当读取 timeout 的 Linux 代码移植到其他操作系统时,或者当代码被移植到 Linux 并在循环中重复使用 struct timeval 而未重新初始化时,这会产生问题。请将 timeout 视为在 select() 返回后处于未定义状态。

返回值

成功时,select() 和 pselect() 返回三个返回的描述符集合中包含的文件描述符总数(即 readfdswritefdsexceptfds 中被置位的比特总数),如果超时在发生任何有趣的事情之前到期,该值可能为零。出错时,返回 -1,并适当地设置 errno;此时集合和 timeout 的内容将变为未定义,因此错误发生后请勿依赖它们的内容。

错误

EBADF

集合中给出了一个无效的文件描述符。(可能是已经关闭的文件描述符,或者是发生错误的文件描述符。)

EINTR

捕获到了信号;参见 signal(7)。

EINVAL

nfds 为负数,或者 timeout 中包含的值无效。

ENOMEM

无法为内部表分配内存。

版本

pselect() 是在内核 2.6.16 中加入 Linux 的。在此之前,pselect() 在 glibc 中是通过模拟实现的(但请参见 BUGS 部分)。

符合

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_SETSIZEfd 值执行 FD_CLR() 或 FD_SET() 将导致未定义的行为。此外,POSIX 要求 fd 必须是一个有效的文件描述符。

关于涉及的类型,经典情况是 timeval 结构的两个字段类型为 long(如上所示),且该结构定义在 <sys/time.h> 中。POSIX.1-2001 的情况是:

struct timeval {
    time_t         tv_sec;     /* seconds */
    suseconds_t    tv_usec;    /* microseconds */
};
结构定义在 <sys/select.h> 中,数据类型 time_tsuseconds_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 起,要求如 SYNOPSIS(概要)所示。

多线程应用程序

如果在另一个线程中关闭了正在被 select() 监视的文件描述符,结果是不确定的。在某些 UNIX 系统上,select() 会解除阻塞并返回,指示文件描述符已就绪(随后的 I/O 操作很可能会以错误失败,除非在 select() 返回和执行 I/O 操作之间该文件描述符被重新打开)。在 Linux(以及其他一些系统)上,在另一个线程中关闭文件描述符对 select() 没有影响。总之,任何依赖于此场景中特定行为的应用程序都应被视为有缺陷的。

Linux 注意

此页面中描述的 pselect() 接口由 glibc 实现。底层的 Linux 系统调用名为 pselect6()。该系统调用的行为与 glibc 封装函数略有不同。

Linux 的 pselect6() 系统调用会修改其 timeout 参数。然而,glibc 封装函数通过使用传递给系统调用的超时参数的局部变量来隐藏这种行为。因此,glibc 的 pselect() 函数不会修改其 timeout 参数;这是 POSIX.1-2001 所要求的行为。

pselect6() 系统调用的最后一个参数不是 sigset_t * 指针,而是一个以下形式的结构:

struct {
    const sigset_t *ss;     /* Pointer to signal set */
    size_t          ss_len; /* Size (in bytes) of object pointed
                               to by 'ss' */
};
这使得系统调用能够同时获取信号集及其大小的指针,同时考虑到大多数体系结构最多支持 6 个系统调用参数的事实。

错误

Glibc 2.0 提供了一个不带 sigmask 参数的 pselect() 版本。

从 2.1 版本开始,glibc 提供了一个使用 sigprocmask(2) 和 select() 实现的 pselect() 模拟版本。该实现仍然容易受到 pselect() 设计初衷所要防止的竞态条件的影响。现代版本的 glibc 在提供该系统调用的内核上会使用(无竞态条件的)pselect() 系统调用。

在缺乏 pselect() 的系统上,可以使用自管道(self-pipe)技巧实现可靠(且更具可移植性)的信号捕获。在此技术中,信号处理程序向管道写入一个字节,管道的另一端由主程序中的 select() 监视。(为了避免在向可能已满的管道写入或从可能为空的管道读取时阻塞,在读取和写入管道时应使用非阻塞 I/O。)

在 Linux 下,select() 可能会将套接字文件描述符报告为“可读就绪”,但随后的读取操作仍会阻塞。例如,这可能发生在数据已经到达,但在检查时发现校验和错误并被丢弃的情况下。可能还有其他文件描述符被虚假报告为就绪的情况。因此,在不应阻塞的套接字上使用 O_NONBLOCK 可能更安全。

在 Linux 上,如果调用被信号处理程序中断(即返回 EINTR 错误),select() 也会修改 timeout。POSIX.1-2001 不允许这样做。Linux 的 pselect() 系统调用也有相同的行为,但 glibc 封装函数通过内部将 timeout 复制到局部变量并将该变量传递给系统调用来隐藏此行为。

示例

#include <stdio.h>
#include <stdlib.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");

   exit(EXIT_SUCCESS);
}

参见

accept(2), connect(2), poll(2), read(2), recv(2), send(2), sigprocmask(2), write(2), epoll(7), time(7)

有关包含讨论和示例的教程,请参阅 select_tut(2)。

引用自

accept4(2), alarm(2), avc_netlink_open(3), coroipc_overview(8), curl_easy_recv(3), curl_easy_send(3), distcache(8), dnet(3), epoll_wait(2), ev(3), event(3), eventfd(2), explain(1), explain(3), explain_select(3), explain_select_or_die(3), fcntl(2), haveged(8), ident(3), ieee1284_get_irq_fd(3), imclient(3), inotify(7), iopause(3), ipq_read(3), jstest(1), ldap.conf(5), ldap_result(3), ldap_set_option(3), libnids(3), libxradius(3), migrate_pages(2), mq_overview(7), nal_connection_new(2), nal_selector_new(2), nfsd(7), nss_ldap(5), nttcp(1), pam_ldap(5), pause(2), pcap_get_selectable_fd(3), perf_event_open(2), perfmonctl(2), perlfunc(1), pipe(7), pmrecordsetup(3), pmtimerecv(3), prctl(2), proc(5), pth(3), pty(7), random(4), rpc(3), rpc_soc(3), rpc_svc_calls(3), rpc_svc_reg(3), rtc(4), sctp_connectx(3), session_api(3), shellout(3), signalfd(2), slapd-ldap(5), slapd-meta(5), snmp_agent_api(3), snmp_alarm(3), snmp_api(3), snmp_sess_timeout(3), socket(2), socket(7), ssh-ldap.conf(5), sssd-ldap(5), syscalls(2), tcp(7), timerfd_create(2), tty_ioctl(4), ualarm(3), udp(7), usleep(3), vga_waitevent(3), zshmodules(1)