首页 > 代码库 > I/O多路转接

I/O多路转接

对于多个非阻塞I/O,怎么知道I/O何时已经处于可读或可写状态?

如果采用循环一直调用write/read,直到返回成功,这样的方式成为轮询(polling)。大多数时间I/O没有处于就绪状态,因此这样的轮询十分浪费CPU。

一种比较好的技术是使用I/O多路转接,也叫做I/O多路复用。其基本思想为:先构造一个有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已经准备好可以进行I/O了。

有3个函数poll、pselect、select可以使我们能够执行I/O多路转接。POSIX标准定义了select函数,poll是对该基本部分的XSI扩展。

 

select和pselect函数

select函数的参数告诉内核

1、我们所关心的描述符

2、对于每个描述符我们所关心的状态(是否读/写一个给定的描述符?是否关心一个描述符的异常状态?)

3、愿意等待多长时间(可以永远等待、等待一个固定时间、或不等待)

4、已经准备好描述符的状态

5、对于读、写或异常这三个状态中的每一个,哪些描述符已经准备好

当使用select的返回信息时,就可以调用相应的I/O函数(例如read或write),并且确知该函数不会阻塞。函数原型:

#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

先看最后一个参数,它指定愿意等待的时间:

struct timeval{

     long tv_sec;

     long tv_usec;

};

当tvptr=NULL时,永远等待。如果捕捉到一个信号则中断此无限等待。

当tvptr->tv_sec=0且tvptr->tv_usec=0时,完全不等待。

当tvptr->tv_sec!=0或tvptr->tv_usec!=0时,等待相应时间。当指定描述符之一准备好时,或当指定时间已经超时,返回。

中间三个参数readfds、writefds、exceptfds指向描述符集的指针,是我们关心的可读、可写、处于异常条件的各个描述符。每个描述符集存放在fd_set数据类型中。这种数据类型为每一可能的描述符保持了一位,如图:

 

第一个参数maxfdp1的意思是最大描述符加1。在三个描述符集中找到最大描述符对应的数值加1。表示内核在此范围内寻找打开的位。

POSIX.1定义了一个select的变体pselect,原型如下:

int pselect(int maxfd1, fd_set *restrict readfds, fd_set *restrict writefds,
            fd_set *restrict exceptfds, const struct timespec *restrict timeout,
            const sigset_t *restrict sigmask)

和select有以下几点区别:

1、pselect超时值用的数据结构为timespec,更为准确

2、pselect超时值声明为const,保证了在pselect函数中不会改变

3、pselect可以指定一个信号屏蔽字,在调用时,以原子操作的方式安装该信号屏蔽字,在返回时恢复以前的信号屏蔽字。

 

poll函数

poll类似select函数,原型如下:

#include<poll.h>

int poll(struct pollfd fdarray[], nfds_t nfds, int timeout)

poll没有为每个状态(可读、可写、异常)构造一个描述符集,而是构造了一个pollfd结构体数组,每个数组指定一个描述符编号以及对其关心的状态。

struct pollfd {
int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 实际发生了的事件 */
} ;

应该events事件设置成下表所表示的值,告诉内核我们关心的是什么。返回时,内核设置revents说明该描述符发生了什么。

最后一个参数说明愿意等待的时间。

I/O多路转接