【APUE】I/O复用

1. 概念

当从一个fd读,写到另一个fd时,可以在下列形式的循环中使用阻塞I/0。

但是如果必须从两个fd中读,如果仍然使用阻塞式I/O,那么程序就会长时间阻塞在一个描述符上。这在网络编程中需要多个socket中获取数据的情况尤为常见。
解决方法一般有如下几种:
a).使用多进程/线程模型,每个进程/线程阻塞式等待一个fd。但是需要之间的多个信号通信机制,增加了程序的复杂性。
b).使用非阻塞式I/O(open with O_NOBLOCK),不断轮询(polling)多个描述符。但浪费CPU时间,并且多次执行read的系统调用。每次polling一遍后应该sleep若干时间,但这个时间很难确定。
c).使用信号驱动I/O模型。首先用sigaction设置SIGIO的信号处理程序,这样内核在数据ready的时候就发送一个SIGIO给进程,进程用信号处理程序接收并处理,完成时成功返回。
d).使用异步I/O(asynchronous I/O)。基本思想是进程告诉内核,当一个fd已经ready的时候,用一个signal通知它。需要注意的是,并非所有的UNIX系统都支持。(System V为这种机制提供了SIGPOLL信号,但是仅当fd是STEAMS设备的时才可用。另外这个信号对每个进程而言只有一个,如果该信号对两个fd都起作用则无法判断哪一个已经ready。为了确定,则将多个fd都设为非阻塞的,以此read来判断)。Linux支持异步I/O但是不默认支持STREAMS机制。与信号驱动I/O相比,信号驱动是通知发起时通知进程,然后将数据从内核读到进程空间。而异步I/O是完成全部过程才通知进程。
e).使用I/O复用(I/O multiplexing)。先构造一张有关fd的列表,然后调用一个函数。直到fd中一个已经准备进行I/O时,这个函数才返回。多路转接是这种问题实现的最好方式。具体函数介绍如下。

2.select和pselect函数

select函数使我们可以执行I/0多路转接,传向select的参数告诉内核:
(1).关心的fd
(2).对于每个fd关心的状态。(读,写或者异常)
从select返回,内核告诉我们:
(1).已经准备号的fd数量。
(2).对于读,写或者异常这三个状态中的每一个,哪些描述符已经准备好。

该函数提供了一种在单个进程中监视多个文件描述符的方法。可以对三种类型的描述符集进行监视:可读(第2个参数:readfds)、可写(第3个参 数:writefds)、处于异常状态(第4个参数:exceptfds)的描述符。从第2个参数起,参数都可以为空(NULL),当文件描述符集为空时,表示不监视其描述符的状态;nfds 是三个文件描述符号中最大的描述符+1。这样就会在一定的范围内搜索需要检测的描述符,否则,将会在所有可选的fd_set中搜索。
最后一个描述符为愿意等待的时间,

timeval *timeout有三种情况
a). timeout == NULL 表示永远阻塞,直到fd准备好。
b). timeout->tv_sec == 0 && timeout->tv_usec == 0 表示完全不等待,测试所有的fd并立即返回。这样得到多个fd的状态而不阻塞select函数的polling方法。
c). timeout->tv_sec != 0 && timeout->tv_usec != 0 等待指定的秒数和毫秒数。当指定的fd之一已经ready时,或者指定时间到达时立即返回。如果是超时时返回则返回0。

Reference:
APUE Chapter 14
UNP Chapter 6

Posted in C|C++, Linux.