文章目录
前言:这连个是多路IO模型中最常用的。简单的说,这个模型是为了用1个线程监控尽可能多的io请求。
select
警告:
select ()只能监视文件描述符编号小于FD _ SETSIZE (1024)ーー对于许多现代应用,这是一个不合理的低限值ーー这种局限性以后也不会改变。所有现代应用程序都应该使用 poll (2)或 epoll (7) ,以摆脱这种限制。
Select ()允许程序监视多个文件描述符。如果文件可以执行相应的 I/O 操作(例如read ,或者无阻塞write),则将等待一个或多个文件描述符变为“就绪”。
Fd _ set
数据结构,用于表示一组文件描述符。根据 POSIX 的资料,FD _ set 结构中文件描述符最大数量是宏FD _ SETSIZE 的值。
文件描述符集
Select ()的主要参数是文件描述符(三个“集合”使用类型 fd _ set声明) ,这些集合允许调用者等待指定的三类事件。如果不想监视对应事件,就将fd_set描述符指定为NULL。
注意: 在返回时,如果该文件可被操作,则对应文件描述符在sets中的位置都会被置位,用这种方法表明那些文件是“ready”的。因此,如果在循环中使用 select () ,则集合必须在每次调用之前重新初始化。文件描述符集的内容可以使用以下宏:
FD _ ZERO ():
此宏清除sets(删除所有文件描述符)。它应该作为初始化文件描述符集。
FD _ SET ()
此宏添加要设置的文件描述符 fd,如果添加集合中已经存在的文件描述符,属于不可操作,但是不会产生错误。
FD _ CLR ()
此宏从 set 中删除文件描述符 fd。移除集合中不存在的文件描述符是不可操作的,但是不会产生错误。
FD _ ISSET ()
Select ()根据文件是否“ready”修改sets中的位 ;select ()之后,调用FD _ ISSET ()宏来测试文件描述符是否处于置位状态。FD _ ISSET ()返回非零,表示文件描述符 fd还在set 中,文件“ready”了;返回0,则fd不在set中了,表明这轮查询中该文件不可操作。
参数
1,readfds
监视此集合中的文件描述符,以确定它们是否准备好进行读取。如果读操作不会阻塞,文件描述符就准备好进行读取。在文件结束时,文件描述符也是“ready”的。 select()返回后,readfds将清除所有文件描述符,除了处于“ready”状态的文件。
2,writefds
这个集合中的文件描述符被监视,看它们是否准备好写入。一个文件描述符如果写操作不会阻塞,就表示准备好写入。但是,即使一个文件描述符指示为可写,写巨量信息的操作仍然可能阻塞。select()返回后,writefds将被清除所有文件描述符,除了那些准备好写入的。
3,exceptfds
这个集合中的文件描述符被监视,看它们是否有“异常条件”。一些异常条件的例子,参见poll(2)中对POLLPRI的讨论。select()返回后,exceptfds将被清除所有文件描述符,除了那些发生了异常条件的。
4,timeout timeout
该参数是一个timeval结构(如下所示),它指定了select()应该阻塞等待一个文件描述符变为就绪的时间间隔。调用将阻塞,直到以下情况之一发生:
• 一个文件描述符变为就绪;
• 调用被信号处理程序中断;或者
• 超时到期。
注意,超时间隔将被向上取整到系统时钟粒度,而内核调度延迟意味着阻塞间隔可能会超出一小段时间。
如果timeval结构的两个字段都是零,那么select()立即返回。(这对于轮询很有用。)
如果timeout被指定为NULL,select()无限期地阻塞等待一个文件描述符变为就绪。
timeout
select()的超时参数是以下类型的结构:
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
pselect()的相应参数是一个timespec(3)结构。
在Linux上,select()修改超时以反映没有睡眠的时间;大多数其他实现不这样做。(POSIX.1允许两种行为。)这在Linux代码读取超时被移植到其他操作系统时,以及在代码被移植到Linux时,重用一个struct timeval来进行多个select()循环而不重新初始化它时,都会引起问题。在select()返回后,将超时视为未定义。
ERROR 错误码
EBADF 在一个集合中给出了一个无效的文件描述符。 (也许是一个已经关闭的文件描述符,
或者是一个发生了错误的文件描述符。)
EINTR 捕获到一个信号;参见signal(7)。
EINVAL nfds为负或超过了RLIMIT_NOFILE资源限制(参见getrlimit(2))。
EINVAL timeout中包含的值无效。
ENOMEM 无法为内部表分配内存。
return value 返回值
成功时,select()和pselect()返回三个描述符集合中包含的文件描述符的数量(即,readfds、writefds、exceptfds中设置的位的总数)。如果在任何文件描述符变为就绪之前超时到期,返回值可能为零。
出错时,返回-1,并设置errno以指示错误;文件描述符集合不被修改,超时变为未定义。
NOTES 注意
fd_set是一个固定大小的缓冲区。执行FD_CLR()或FD_SET()时,如果fd的值为负或等于或大于FD_SETSIZE,将导致未定义的行为。此外,POSIX要求fd是一个有效的文件描述符。
select()和pselect()的操作不受O_NONBLOCK标志的影响。
模拟unsleep
在usleep(3)出现之前,一些代码使用了一个调用select(),所有三个集合都为空,nfds为零,而timeout设置为非NULL,作为一种相当可移植的方式来以秒 / 微秒精度睡眠。[就是说:用select实现sleep,避免调用或无法调用sleep库]
select() 和 poll()通知之间的对应关系
在Linux内核源代码中,我们找到了以下定义,它们显示了select()的可读、可写和异常条件通知与poll(2)和epoll(7)提供的事件通知之间的对应关系:
#define POLLIN_SET (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
EPOLLHUP | EPOLLERR)
/* 准备好读取 */
#define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT |
EPOLLERR)
/* 准备好写入 */
#define POLLEX_SET (EPOLLPRI)
/* 异常条件 */
多线程中的应用
如果select()正在监视的文件描述符在另一个线程中被关闭,结果是未指定的。在一些UNIX系统上,select()解除阻塞并返回,指示文件描述符已经准备好(随后的I/O操作可能会失败并返回错误,除非在select()返回和I/O操作执行之间有另一个进程重新打开了文件描述符)。在Linux(和一些其他系统)上,在另一个线程中关闭文件描述符对select()没有影响。总之,在这种情况下依赖于特定行为的任何应用程序都必须被认为是有缺陷的。
C语言库和内核的区别
Linux内核允许任意大小的文件描述符集合,根据nfds的值确定要检查的集合的长度。然而,在glibc实现中,fd_set类型是固定大小的。另请参阅BUGS。
本页描述的pselect()接口是由glibc实现的。底层的Linux系统调用名为pselect6()。这个系统调用的行为与glibc包装函数有些不同。
Linux pselect6()系统调用修改了它的超时参数。然而,glibc包装函数通过使用一个本地变量作为传递给系统调用的超时参数来隐藏这种行为。因此,glibc pselect()函数不会修改它的超时参数;这是POSIX.1-2001所要求的行为。
pselect6()系统调用的最后一个参数不是一个sigset_t *指针,而是一个如下形式的结构:
struct {
const kernel_sigset_t *ss; /* 指向信号集的指针 */
size_t ss_len; /* 指向'ss'的对象的大小(以字节为单位) */
};
这允许系统调用获取信号集及其大小的指针,同时考虑到大多数架构支持最多6个系统调用参数的事实。参见sigprocmask(2)对内核和libc信号集概念之间差异的讨论。
BUGS
POSIX允许实现定义一个上限,通过常量FD_SETSIZE来宣告,这个上限是可以在文件描述符集合中指定的文件描述符的范围。Linux内核没有固定的限制,但是glibc实现使fd_set成为一个固定大小的类型,FD_SETSIZE定义为1024,FD_*()宏根据这个限制来操作。要监视大于1023的文件描述符,请使用poll(2)或epoll(7)代替。
将fd_set参数作为值-结果参数的实现是一个设计错误,这个错误在poll(2)和epoll(7)中被避免了。[也就是说:将监视结果用fd_set带出来,并用FD_ISSET() 检测是一个错误的设计]
根据POSIX,select()应该检查三个文件描述符集合中指定的所有文件描述符,直到nfds-1的限制。然而,当前的实现忽略了任何在这些集合中大于进程当前打开的最大文件描述符号的文件描述符。根据POSIX,任何这样的文件描述符如果在其中一个集合中被指定,应该导致EBADF错误。
从glibc 2.1开始,glibc提供了一个使用sigprocmask(2)和select()实现的pselect()的模拟。这个实现仍然容易受到pselect()设计用来防止的竞争条件的影响。现代版本的glibc在内核提供pselect()系统调用的情况下使用(无竞争)pselect()系统调用。
在Linux上,select()可能会报告一个套接字文件描述符为“准备好读取”,而随后的读取却阻塞。这可能发生在数据已经到达但经过检查发现校验和错误并被丢弃的情况下。可能还有其他一些情况导致文件描述符被错误地报告为就绪。因此,在不应该阻塞的套接字上使用O_NONBLOCK可能会更安全。
在Linux上,如果select()调用被信号处理程序中断(即EINTR错误返回),select()也会修改超时参数。POSIX.1不允许这种行为。Linux pselect()系统调用也有相同的行为,但是glibc包装函数通过内部将超时复制到一个本地变量并将该变量传递给系统调用来隐藏这种行为。
pselect
pselect()系统调用允许应用程序安全地等待,直到一个文件描述符变为就绪或者捕获到一个信号。 select()和pselect()的操作是相同的,除了以下三个区别:
• select()使用的超时是一个struct timeval(有秒和微秒),而pselect()使用的是一个struct timespec(有秒和纳秒)。
• select()可能会更新超时参数,以指示还剩多少时间。pselect()不会改变这个参数。
• select()没有sigmask参数,其行为与用NULL sigmask调用pselect()相同。
sigmask是一个指向信号掩码的指针(参见sigprocmask(2));如果它不是NULL,那么pselect()首先用sigmask指向的信号掩码替换当前的信号掩码,然后执行“select”函数,然后恢复原始的信号掩码。(如果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(),避免了竞争。)
select 例子
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
int
main(void)
{
int retval;
fd_set rfds;
struct timeval tv;
/* 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);
}