在所有POSIX兼容的平台上,select函数使我们可以执行I/O多路转接。传给select的参数告诉内核:

  • 我们所关心的描述符;
  • 对于每个描述符我们所关心的条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件);
  • 愿意等待多长时间(可以永远等待、等待一个固定的时间或者根本不等待)。从select返回时,内核告诉我们:
    • 已准备好的描述符的总数量;
    • 对于读、写或异常这3个条件中的每一个,哪些描述符已准备好。

使用这种返回信息,就可调用相应的I/O 函数(一般是readwrite),并且确知该函数不会阻塞。

#include <sys/select.h>

int select(int maxfdp1, fd_set *restrict readfds,
                fd_set *restrict writefds, fd_set *restrict exceptfds,
                struct timeval *restrict tvptr);
                //返回值:准备就绪的描述符数目;若超时,返回0;若出错,返回−1

先来说明最后一个参数,它指定愿意等待的时间长度,单位为秒和微秒(回忆 4.20 节)。有以下3种情况。

tvptr == NULL

永远等待。如果捕捉到一个信号则中断此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1errno设置为EINTR

tvptr->tv_sec == 0 && tvptr->tv_usec == 0

根本不等待。测试所有指定的描述符并立即返回。这是轮询系统找到多个描述符状态而不阻塞select函数的方法。

tvptr->tv_sec != 0 || tvptr->tv_usec != 0

等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。如果在超时到期时还没有一个描述符准备好,则返回值是 0。(如果系统不提供微秒级的精度,则tvptr->tv_usec值取整到最近的支持值。)与第一种情况一样,这种等待可被捕捉到的信号中断。

POSIX.1允许实现修改timeval结构中的值,所以在select返回后,你不能指望该结构仍旧保持调用select之前它所包含的值。FreeBSD 8.0、Mac OS X 10.6.8和Solaris 10都保持该结构中的值不变。但是,若在超时时间尚未到期时,select就返回,那么Linux 3.2.0将用剩余时间值更新该结构。

中间3个参数readfdswritefdsexceptfds是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。每个描述符集存储在一个fd_set数据类型中。这个数据类型是由实现选择的,它可以为每一个可能的描述符保持一位。我们可以认为它只是一个很大的字节数组,如图14-15所示。

图14-15 对select指定读、写和异常条件描述符

对于fd_set数据类型,唯一可以进行的处理是:分配一个这种类型的变量,将这种类型的一个变量值赋给同类型的另一个变量,或对这种类型的变量使用下列4个函数中的一个。

#include <sys/select.h>

int FD_ISSET(int fd, fd_set *fdset);
//返回值:若fd在描述符集中,返回非0值;否则,返回0
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);

这些接口可实现为宏或函数。调用FD_ZERO将一个fd_set变量的所有位设置为0。要开启描述符集中的一位,可以调用FD_SET。调用 FD_CLR可以清除一位。最后,可以调用FD_ISSET测试描述符集中的一个指定位是否已打开。

在声明了一个描述符集之后,必须用FD_ZERO将这个描述符集置为0,然后在其中设置我们关心的各个描述符的位。具体操作如下所示:

fd_set rset;
int  fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);
FD_SET(STDIN_FILENO, &rset);

select返回时,可以用FD_ISSET测试该集中的一个给定位是否仍处于打开状态:

if (FD_ISSET(fd, &rset)) {
    ...
}

select的中间3个参数(指向描述符集的指针)中的任意一个(或全部)可以是空指针,这表示对相应条件并不关心。如果所有3个指针都是NULL,则select提供了比sleep更精确的定时器。(回忆10.19节,sleep等待整数秒,而select的等待时间则可以小于1秒,其实际精度取决于系统时钟。)习题14.5给出了这样一个函数。

select第一个参数maxfdp1的意思是“最大文件描述符编号值加1”。考虑所有3个描述符集,在3个描述符集中找出最大描述符编号值,然后加1,这就是第一个参数值。也可将第一个参数设置为FD_SETSIZE,这是<sys/select.h>中的一个常量,它指定最大描述符数(经常是1024),但是对大多数应用程序而言,此值太大了。确实,大多数应用程序只使用3~10个描述符(某些应用程序需要更多的描述符,但这种UNIX程序并不典型)。通过指定我们所关注的最大描述符,内核就只需在此范围内寻找打开的位,而不必在3个描述符集中的数百个没有使用的位内搜索。

例如,图14-16所示的两个描述符集的情况就好像是执行了下述操作:

fd_set readset, writeset;
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_SET(0, &readset);
FD_SET(3, &readset);
FD_SET(1, &writeset);
FD_SET(2, &writeset);
select(4, &readset, &writeset, NULL, NULL);

因为描述符编号从0开始,所以要在最大描述符编号值上加1。第一个参数实际上是要检查的描述符数(从描述符0开始)。

select有3个可能的返回值。

  1. 返回值-1表示出错。这是可能发生的,例如,在所指定的描述符一个都没准备好时捕捉到一个信号。在此种情况下,一个描述符集都不修改。
  2. 返回值0表示没有描述符准备好。若指定的描述符一个都没准备好,指定的时间就过了,那么就会发生这种情况。此时,所有描述符集都会置0。
  3. 一个正返回值说明了已经准备好的描述符数。该值是3个描述符集中已准备好的描述符数之和,所以如果同一描述符已准备好读和写,那么在返回值中会对其计两次数。在这种情况下,3个描述符集中仍旧打开的位对应于已准备好的描述符。

对于“准备好”的含义要作一些更具体的说明。

  • 若对读集readfds中的一个描述符进行的read操作不会阻塞,则认为此描述符是准备好的。
  • 若对写集writefds中的一个描述符进行的write操作不会阻塞,则认为此描述符是准备好的。
  • 若对异常条件集exceptfds中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。现在,异常条件包括:在网络连接上到达带外的数据,或者在处于数据包模式的伪终端上发生了某些条件。(Stevens[1990]的15.10节中描述了后一种条件。)
  • 对于读、写和异常条件,普通文件的文件描述符总是返回准备好。

一个描述符阻塞与否并不影响select是否阻塞,理解这一点很重要。也就是说,如果希望读一个非阻塞描述符,并且以超时值为5秒调用select,则select最多阻塞5 s。相类似,如果指定一个无限的超时值,则在该描述符数据准备好,或捕捉到一个信号之前,select会一直阻塞。

如果在一个描述符上碰到了文件尾端,则select会认为该描述符是可读的。然后调用read,它返回0,这是UNIX系统指示到达文件尾端的方法。(很多人错误地认为,当到达文件尾端时,select会指示一个异常条件。)

POSIX.1也定义了一个select的变体,称为pselect

#include <sys/select.h>
int pselect(int maxfdp1, fd_set *restrict readfds,
    fd_set *restrict writefds, fd_set *restrict exceptfds,
    const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);
    //返回值:准备就绪的描述符数目;若超时,返回0;若出错,返回−1

除下列几点外,pselectselect相同。

  • select的超时值用timeval结构指定,但pselect使用timespec结构(回忆4.2节中timespec结构的定义)。timespec结构以秒和纳秒表示超时值,而非秒和微秒。如果平台支持这样的时间精度,那么timespec就能提供更精准的超时时间。
  • pselect的超时值被声明为const,这保证了调用pselect不会改变此值。
  • pselect 可使用可选信号屏蔽字。若 sigmaskNULL,那么在与信号有关的方面, pselect 的运行状况和 select 相同。否则,sigmask 指向一信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。

results matching ""

    No results matching ""