备注

fd_set是固定大小的缓冲区。如果fd值为负或等于或大于FD_SETSIZE,则执行FD_CLR()或FD_SET()会导致不确定的行为。而且,POSIX要求fd是有效的文件描述符。

select()和pselect()的操作不受O_NONBLOCK标志的影响。

在其他一些UNIX系统上,如果系统无法分配内核内部资源而不是像Linux那样分配ENOMEM,则select()可能会失败,并显示错误EAGAIN。 POSIX为poll(2)指定此错误,但没有为select()指定此错误。可移植程序可能希望像EINTR一样检查EAGAIN和循环。

The self-pipe trick

在缺少pselect()的系统上,可以使用自管道技巧来实现可靠(且更具可移植性)的信号捕获。在这种技术中,信号处理程序将一个字节写入一个管道,该管道的另一端由主程序中的select()监视。 (为避免在写入可能已满的管道或从可能为空的管道中进行读取时可能发生阻塞,在对管道进行读取和写入时使用非阻塞I / O。)

Emulating usleep(3)

usleep(3)出现之前,一些代码采用了对select()的调用,其中所有三个集合均为空,nfds为零,并且非NULL超时是一种相当便携的睡眠方式,具有亚秒级的精度。

Correspondence between select() and poll() notifications

在Linux内核源代码中,我们找到以下定义,这些定义显示select()的可读性,可写性和异常情况通知与poll(2)和epoll(7)提供的事件通知之间的对应关系:

#define POLLIN_SET  (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
                     EPOLLHUP | EPOLLERR)
                   /* Ready for reading */
#define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |
                     EPOLLERR)
                   /* Ready for writing */
#define POLLEX_SET  (EPOLLPRI)
                   /* Exceptional condition */

Multithreaded applications

如果在另一个线程中关闭了由select()监视的文件描述符,则结果不确定。在某些UNIX系统上,select()解除阻塞并返回,并指示文件描述符已准备就绪(后续的I / O操作可能会失败并显示错误,除非另一个进程在select()返回的时间与重新启动文件之间重新打开文件描述符。执行I / O操作)。在Linux(和某些其他系统)上,在另一个线程中关闭文件描述符对select()无效。总而言之,在这种情况下依赖于特定行为的任何应用程序都必须视为错误的。

C library/kernel differences

Linux内核允许使用任意大小的文件描述符集,并根据nfds的值确定要检查的文件描述符集的长度。但是,在glibc实现中,fd_set类型的大小是固定的。另请参阅错误。

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

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

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

struct {
    const kernel_sigset_t *ss;   /* Pointer to signal set */
    size_t ss_len;               /* Size (in bytes) of object
                                    pointed to by 'ss' */
};

这允许系统调用获得指向信号集及其大小的指针,同时考虑到大多数体系结构最多支持系统调用的6个参数。有关信号集的内核和libc概念之间的区别的讨论,请参见sigprocmask(2)。

Historical glibc details

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

在2.1至2.2.1的glibc版本中,必须定义_GNU_SOURCE才能从中获取pselect()的声明。

说明

select()允许程序监视多个文件描述符,等到一个或多个文件描述符对某种I / O操作(例如,可能输入)"准备就绪"。如果可以执行相应的I / O操作(例如read(2)或足够小的write(2))而不会阻塞,则认为文件描述符已准备就绪。

select()只能监视小于FD_SETSIZE的文件描述符号; poll(2)和epoll(7)没有此限制。参见错误。

File descriptor sets

select()的主要参数是文件描述符的三个"集合"(以fd_set类型声明),允许调用者在指定的文件描述符集合上等待三类事件。如果对于相应的事件类无需监视文件描述符,则可以将每个fd_set参数指定为NULL。

请注意:返回时,每个文件描述符集都会被修改以指示当前"就绪"的文件描述符。因此,如果在循环中使用select(),则必须在每次调用之前重新初始化这些集合。将fd_set参数作为value-result参数实现是一种设计错误,在poll(2)和epoll(7)中可以避免这种错误。

可以使用以下宏来操作文件描述符集的内容:

FD_ZERO()
该宏清除(从中删除所有文件描述符)设置。应该将它用作初始化文件描述符集的第一步。
FD_SET()
该宏添加文件描述符fd进行设置。添加集合中已经存在的文件描述符是一项禁止操作,并且不会产生错误。
FD_CLR()
该宏从集合中删除文件描述符fd。删除集合中不存在的文件描述符是no-op,并且不会产生错误。
FD_ISSET()
select()根据下面描述的规则修改集合的内容。调用select()之后,可以使用FD_ISSET()宏来测试文件描述符是否仍然存在于集合中。如果文件描述符fd存在于集合中,则FD_ISSET()返回非零;否则,则返回零。

Arguments

select()的参数如下:

readfds
监视该集中的文件描述符,以查看它们是否已准备好读取。如果读取操作不会阻塞,则文件描述符可以读取。特别是,文件描述符在文件结束时也已准备就绪。
select()返回后,将清除所有文件描述符的readfds,准备读取的文件描述符除外。
writefds
监视该集合中的文件描述符以查看它们是否准备好进行写入。如果写操作不会阻塞,则文件描述符已准备好进行写操作。但是,即使文件描述符指示为可写,大写操作仍可能会阻塞。
select()返回后,将清除所有文件描述符的writefds,准备就绪的文件除外。
exceptfds
监视此集中的文件描述符是否有"例外条件"。有关某些特殊情况的示例,请参见poll(2)中有关POLLPRI的讨论。
select()返回之后,exceptfds将清除所有文件描述符,但发生异常情况的文件描述符除外。
nfds
此参数应设置为三个集合中任何一个的编号最高的文件描述符,再加上1。检查每个集合中指示的文件描述符,直到此限制为止(但请参阅BUGS)。
timeout
The timeout

argument is a
timeval

structure (shown below) that specifies the interval that
select()

should block waiting for a file descriptor to become ready.
The call will block until either:

*
文件描述符准备就绪;
*
呼叫被信号处理器中断;要么
*
超时到期。
请注意,超时间隔将四舍五入为系统时钟的粒度,内核调度延迟意味着阻塞间隔可能会少量溢出。
如果timeval结构的两个字段均为零,则select()立即返回。 (这对轮询很有用。)
如果将超时指定为NULL,则select()无限期地等待文件描述符准备就绪。

pselect()

pselect()系统调用使应用程序可以安全地等待,直到文件描述符准备就绪或信号被捕获为止。

除了以下三个区别之外,select()和pselect()的操作是相同的:

*
select()使用的超时时间是结构时间(以秒和微秒为单位),而pselect()使用结构时间规格(以秒和纳秒)。
*
select()可能会更新超时参数以指示还剩多少时间。 pselect()不会更改此参数。
*
select()没有sigmask参数,并且表现为以NULL sigmask调用的pselect()。

sigmask是指向信号掩码的指针(请参见sigprocmask(2));如果不是NULL,则pselect()首先将当前信号掩码替换为sigmask指向的信号掩码,然后执行"选择"功能,然后恢复原始信号掩码。 (如果sigmask为NULL,则在pselect()调用期间不会修改信号掩码。)

除了超时参数的精度差异外,以下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()允许一个人首先阻止信号,处理传入的信号,然后使用所需的sigmask调用pselect(),以避免竞争。)

The timeout

select()的超时参数是以下类型的结构:

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

pselect()的相应参数具有以下类型:

struct timespec {
    time_t      tv_sec;         /* seconds */
    long        tv_nsec;        /* nanoseconds */
};

在Linux上,select()修改超时以反映未休眠的时间;其他大多数实现则不这样做。 (POSIX.1允许任何一种行为。)当读取超时的Linux代码移植到其他操作系统时,以及代码移植到在一个循环中为多个select()重用一个struct timeval而不重新初始化它的struct timeval的代码时,这都会引起问题。 。在select()返回之后,将超时视为未定义。

版本

pselect()已添加到内核2.6.16中的Linux中。在此之前,pselect()是在glibc中模拟的(但请参见BUGS)。

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/

另外参见

accept(2),connect(2),poll(2),read(2),recv(2),restart_syscall(2),send(2),sigprocmask(2),write(2),epoll(7),时间(7)

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

名称

select,pselect,FD_CLR,FD_ISSET,FD_SET,FD_ZERO-同步I / O复用

BUGS

POSIX允许实现在文件描述符集中可以指定的文件描述符范围内定义通过常量FD_SETSIZE通告的上限。 Linux内核没有施加固定的限制,但是glibc实现使fd_set为固定大小的类型,其中FD_SETSIZE定义为1024,并且FD _ *()宏根据该限制进行操作。要监视大于1023的文件描述符,请改用poll(2)或epoll(7)。

根据POSIX,select()应该检查三个文件描述符集中的所有指定文件描述符,最大限制为nfds-1。但是,当前实现忽略这些集中的任何文件描述符,该文件描述符大于进程当前已打开的最大文件描述符数目。根据POSIX,在一组设置中指定的任何此类文件描述符都将导致错误EBADF。

从2.1版开始,glibc提供了pselect()的仿真,该仿真是使用sigprocmask(2)和select()实现的。该实现仍然很容易受到pselect()旨在防止的竞争条件的影响。 glibc的现代版本在提供内核的内核上使用(race-free)pselect()系统调用。

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

在Linux上,如果调用被信号处理程序中断(即EINTR错误返回),则select()还会修改超时。 POSIX.1不允许这样做。 Linux pselect()系统调用具有相同的行为,但是glibc包装器通过在内部将超时复制到本地变量并将该变量传递给系统调用来隐藏此行为。

SELECT - Linux手册页

Linux程序员手册 第2部分
更新日期: 2020-04-11

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.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);
}

语法

#include <sys/select.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);

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

返回值

成功后,select()和pselect()返回三个返回的描述符集中包含的文件描述符的数量(即,在readfdswritefds,exceptfds中设置的总位数)。如果超时在任何文件描述符准备就绪之前到期,则返回值为零。

如果出错,则返回-1,并设置errno表示错误;文件描述符集未修改,并且超时变得不确定。

错误说明

EBADF
其中一组给出了无效的文件描述符。 (可能是一个已经关闭的文件描述符,或者发生了错误的文件描述符。)但是,请参见错误。
EINTR
捕获到信号;参见signal(7)。
EINVAL
nfds为负或超过RLIMIT_NOFILE资源限制(请参阅getrlimit(2))。
EINVAL
超时中包含的值无效。
ENOMEM
无法为内部表分配内存。

遵循规范

select()符合POSIX.1-2001,POSIX.1-2008和4.4BSD(select()首次出现在4.2BSD中)。通常可移植到支持BSD套接字层的克隆的非BSD系统(包括System V变体)。但是请注意,SystemV变体通常会在返回之前设置超时变量,但BSD变体不会。

pselect()在POSIX.1g,POSIX.1-2001和POSIX.1-2008中定义。

日期:2019-08-20 17:58:34 来源:oir作者:oir