select 函数

提供 select 函数的用意在于解决一个进程同时需要处理多个描述符的读, 这时候不能在任何一个描述符上阻塞读, 否则可能因为在一个描述符上的读操作被阻塞了, 而其它的描述符上的数据无法处理. 开始讨论时提供了两个不那么好的方法, 一种是轮询(poll)每个描述符, 每隔一段时间遍历所有的描述符, 发起 read 操作, 如果该输入有数据则立即处理, 如果没有数据就立马返回并继续下一个操作符. 这个方法的问题与所有的轮询的问题都是一样的, 浪费大量的 CPU 以及很难确定等待的时间间隔. 另外一种是异步 I/O (asynchronous I/O) , 当描述符准备好 I/O 时由内核通过一个信号通知进程, 这种方式的缺点是一个信号无法确定是哪个描述符准备好了 I/O , 为了了解仍然需要顺序尝试执行 I/O , 而使用多个信号, 信号的数量又难以满足每个描述符一个.

最终得到的办法便是 I/O 多路转接 (I/O multiplexing). 使用这种方法首先构造一个感兴趣的描述符集合, 然后调用 select 函数, 直到这些描述符中的一个已准备好进行 I/O 时, 该函数才返回. 描述符集包含的感兴趣的条件可以有三种: 可读、可写或者处于异常条件. 每个描述符集存储在一个 fd_set 数据类型中, 它是一个位矢量, 为每个可能的描述符保持一位. 任何描述符都是非负整数, 其中标准输入的描述符为 0, 标准输出的描述符为 1, 标准错误的描述符为 3. 当放入 fd_set 中时, 标准输入将在索引为 0 的第一个比特, 其它依次类推. 如果有一个描述符的值为 10 , 那么它将在索引为 9 的比特的位置上. 对于一个 fd_set 数据可以直接赋值给同类型的另一个变量. 以下四个函数用于操作 fd_set 数据 :

/* 检查描述符 fd 是否在描述符集 fdset 中 */
int FD_ISSET(int fd, fd_set *fdset);
/* 将描述符 fd 从描述符集 fdset 中清除 */
int FD_CLR(int fd, fd_set *fdset);
/* 将描述符 fd 添加到描述符集 fdset 中 */
int FD_SET(int fd, fd_set *fdset);
/* 将描述符集 fdset 的所有位置为 0 */
int FD_ZERO(fd_set *fdset);

在声明一个描述符集之后, 必须调用 FD_ZERO 将这个描述符集置为 0 , 因为刚声明的描述符集值是未定义的. 然后在其中设置我们感兴趣的各个描述符的位. 惯用法如下:

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

当设置好描述符集之后, 调用 select 函数执行 I/O 多路转接.

int select(int maxfdp1, fs_set *restrict readfds,
           fd_set *restrict writefds, fd_set *restrict exceptfds,
           struct timeval *restrict tvptr);

readfds, writefds 和 exceptfds 分别是指向描述符集的指针, 这个三个描述符集说明了我们关心的可读、可写和处于异常条件的描述符集合, 如果对任意条件不关心可以对相应的描述符集提供一个空指针. maxfdp1 则是三个集合中最大的描述符的值加 1, 提供这个值的原因在于限制检查的描述符的范围, 从而不必将 CPU 浪费在几百个没有使用的位内搜索. 最后一个参数 tvptr 的含义是在函数返回前的等待时间, 当 tvptr == NULL 时将永远等待直到描述符集中有一个已经准备好了或者被信号中断. 当 tvptr 有值时表示等待相应的时间, tvptr->tv_sev 为等待秒数, tvptr->tv_usec 为等待微秒数, 此时仅当描述符集中一个已经准备好了或者超时或者被信号中断才会返回. 特别的当两个字段都为 0 表示不等待立即返回. 函数返回准备就绪的描述符数目, 并修改三个描述符集只包含准备好条件的描述符. 特别的如果返回了 0 , 那么所有描述符集都会置为 0 . 如果返回 -1 表示出错, 并且所有描述符集保持不变.

对于普通的文件, 这三个条件都将返回准备好. 另外一点是, 一个描述符阻塞与否并不影响 select 是否阻塞. 在 Unix 中, 如果一个描述符碰到了文件尾端, select 依然认为改描述符是可读的, 但是调用 read 函数将返回 0 .

Leave a Reply

Your email address will not be published. Required fields are marked *