select() 系统调用提供一个机制来实现同步多元I/O:
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); FD_CLR(int fd, fd_set *set); FD_ISSET(int fd, fd_set *set); FD_SET(int fd, fd_set *set); FD_ZERO(fd_set *set);
调用select() 将阻塞,直到指定的文件描述符准备好执行I/O,或者可选参数timeout指定的时间已经 过去。
监视的文件描述符分为三类set,每一种对应等待不同的事件。readfds中列出的文件描述符被监视是否有数据可供读取(如果读取操作完成则不会阻 塞)。writefds中列出的文件描述符则被监视是否写入操作完成而不阻塞。最后,exceptfds中列出的文件描述符则被监视是否发生异常,或者无 法控制的数据是否可用(这些状态仅仅应用于套接字)。
这三类set可以是NULL,这种情况下select()不监视这一类事件。
select()成功返回时,每组set都被修改以使它只包含准备好I/O的文件描述符。例如,假设有两个文件描述 符,值分别是7和9,被放在readfds中。当select()返回时,如果7仍然在set中,则这个文件描述符已经准备好被读取而不会 阻塞。如果9已经不在set中,则读取它将可能会阻塞(我说可能是因为数据可能正好在select返回后就可用,这种情况下,下一次调用select()将返回文件描述符准备好读取。
第一个参数n,等于所有set中最大的那个文件描述符的值加1。实际上是要检查的描述符数。
timeout参数是一个指向timeval结构体的指针,timeval定义如下:
#include <sys/time.h> struct timeval { long tv_sec; /* seconds */ long tv_usec; /* 10E-6 second */ };
如果这个参数不是NULL,则即使没有文件描述符准备好I/O,select()也会在经过tv_sec秒和 tv_usec微秒后返回。当select()返回时,timeout参数的状态在不同的系统中是未定义的,因此每次调用select() 之前必须重新初始化timeout和文件描述符set。实际上,当前版本的Linux会自动修改timeout参数,设置它的值为剩余时间。因此,如果 timeout被设置为5秒,然后在文件描述符准备好之前经过了3秒,则这一次调用select()返回时tv_sec将变为2。
如果timeout中的两个值都设置为0,则调用select()将立即返回,这是轮询系统找到多个描述符状态而不阻塞select函数的方法。
文件描述符set不会直接操作,一般使用几个助手宏来管理。这允许Unix系统以自己喜欢的方式来实现文件描述符set。但大多数系统都简单地实现set 为位数组。
FD_ZERO移除指定set中的所有文件描述符。每一次调用select()之前都应该先调用它。
FD_SET添加一个文件描述符到指定的set中,FD_CLR则从指定的set中移除一个文件描述符。
FD_ISSET测试一个文件描述符是否指定set的一部分。如果文件描述符在set中则返回一个非0整数,不在则返回0。FD_ISSET在调用select() 返回之后使用,测试指定的文件描述符是否准备好相关动作
因为文件描述符set是静态创建的,它们对文件描述符的最大数目强加了一个限制, 能够放进set中的最大文件描述符的值由FD_SETSIZE指定。在Linux中,这个值是1024。
select() 成功时返回准备好I/O的文件描述符数目,包括所有三个set。如果提供了timeout,返回值可 能是0;错误时返回-1。
POSIX。1也定义了一个 select 变体,称为 pselect。详情可参考其手册页。