1 问题及办法
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞 I / O:
while ( (n=read(STDIN_FILENO, buf, BUFSIZ) ) > 0)
if (write (STDOUT_FILENO, buf, n) != n)
err_sys (write err”or) ;
这种形式的阻塞 I / O到处可见。但是如果必须读两个描述符又将如何呢?如果仍旧使用阻塞 I / O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。
方法 1:设置两个进程,每个进程处理一条数据通路,但是也产生了这两个进程间相互配合问题。
方法 2:使用一个进程执行该程序,但调用非阻塞 I / O读取数据,然后使用轮询技术交叉 read,这种方法的不足之处是浪费 C P U时间。
方法 3:使用异步 IO技术,基本思想是进程告诉内核,当一个描述符已准备好可以进行 I / O时,用一个信号通知它。这种技术有两个问题,一是并非所有系统都支持这种机制, S V R 4为此技术提供 S I G P O L L信号,但是仅当描述符引用流设备时,此信号才能工作;二是这种信号对每个进程而言只有 1个。如果使该信号对两个描述符都起作用,那么在接到此信号时进程无法判别是哪一个描述符已准备好可以进行 I / O。为了确定是哪一个描述符已准备好,仍需将这两个描述符都设置为非阻塞的,并顺序试执行 I / O。
方法 4:是使用 I / O多路转接( I/O multiplexing),其基本思想是:先构造一张有关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行 I / O时才返回。在返回时,它告诉进程哪一个描述符已准备好可以进行 I / O。
s e l e c t和 p o l l的可中断性:在 S V R 4之下,如果指定了 S A R E S TA RT,那么 s e l e c t和 p o l l也是自动再起动的。
2 select 函数
① 概述
select函数使我们在 SVR4和 4.3+BSD之下可以执行 I/O多路转接,传向 select的参数告诉内核:
(1) 我们所关心的描述符。
(2) 对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的描述符?是否关心一个描述符的异常条件?)。
(3) 希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)。
从 s e l e c t返回时,内核告诉我们:
(1) 已准备好的描述符的数量。
(2) 哪一个描述符已准备好读、写或异常条件。
图片1
② 等待时间
struct timeval{
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
} ;
• t v p t r= =NULL
永远等待。如果捕捉到一个信号则中断此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则 s e l e c t 返回- 1 , e r r n o 设置为 E I N T R 。
• t v p t r- >t v s e c= =0 && t v p t r- >t v u s e c= =0
完全不等待。测试所有指定的描述符并立即返回。这是得到多个描述符的状态而不阻塞 s e l e c t 函数的轮询方法。
• t v p t r- >t v s e c ! =0 | | t v p t r- >t v u s e c! =0
等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。如果在超时还没有一个描述符准备好,则返回值是 0 ,(如果系统不提供微秒分辨率,则 t v p t r- >t v u s e c 值取整到最近的支持值。)与第一种情况一样,这种等待可被捕捉到的信号中断。
③ fd_set 数据类型
中间三个参数 re a d f d s 、 w r i t e f d s 和 e x c e p t f d s 是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或处于异常条件的各个描述符。每个描述符集存放在一个 f d s e t 数据类型中。
对 f g s e t 数据类型可以进行的处理是: ( a ) 分配一个这种类型的变量, ( b ) 将这种类型的一个变量赋与同类型的另一个变量, ( c ) 对于这种类型的变量使用图中的四个宏。
FD_ZERO : clear all bits in fdset ;
FD_SET : turn on bit for fd in fdset ;
FD_CLR : turn off bit for fd in fdset ;
FD_ISSET : test bit for fd in fdset 。
s e l e c t 中间三个参数中的任意一个(或全部)可以是空指针,这表示对相应条件并不关心。如果所有三个指针都是空指针,则 s e l e c t 提供了较 s l e e p 更精确的计时器。
④ 要检查的描述符数
s e l e c t 第一个参数 m a x f d p1 的意思是“最大 f d 加 1 ( max fd plus 1 )”。在三个描述符集中找出最高描述符编号值,然后加 1 ,这就是第一个参数值。也可将第一个参数设置为 F D S E T S I Z E ,这是一个 < s y s / t y p e s . h > 中的常数,它说明了最大的描述符数(经常是 2 5 6 或 1 0 2 4 )。但是对大多数应用程序而言,此值太大了。确实,大多数应用程序只应用 3 ~ 1 0 个描述符。如果将第三个参
数设置为最高描述符编号值加 1 ,内核就只需在此范围内寻找打开的位,而不必在数百位的大范围内搜索。
⑤ 返回值
s e l e c t 有三个可能的返回值。
(1) 返回值- 1 表示出错。这是可能发生的,例如在所指定的描述符都没有准备好时捕捉到一个信号。
(2) 返回值 0 表示没有描述符准备好。若指定的描述符都没有准备好,而且指定的时间已经超过,则发生这种情况。
(3) 返回一个正值说明了已经准备好的描述符数,在这种情况下,三个描述符集中仍旧打开的位是对应于已准备好的描述符位。
⑥ 理解“准备好”的含义
对于“准备好”的意思要作一些更具体的说明:
(1) 若对读集( re a d f d s )中的一个描述符的 r e a d 不会阻塞,则此描述符是准备好的。
(2) 若对写集( w r i t e f d s )中的一个描述符的 w r i t e 不会阻塞,则此描述符是准备好的。
(3) 若对异常条件集( e x c e p t f d s )中的一个描述符有一个未决异常条件,则此描述符是准备好的。现在,异常条件包括: ( a ) 在网络连接上到达指定波特率外的数据,或者 ( b ) 在处于数据包方式的伪终端上发生了某些条件。
应当理解一个描述符阻塞与否并不影响 s e l e c t 是否阻塞。也就是说,如果希望读一个非阻塞描述符,并且以超时值为 5 秒调用 s e l e c t ,则 s e l e c t 最多阻塞 5 秒。相类似,如果指定一个无限的超时值,则 s e l e c t 阻塞到对该描述符数据准备好,或捕捉到一个信号。
如果在一个描述符上碰到了文件结束,则 s e l e c t 认为该描述符是可读的。然后调用 r e a d ,它返回 0 ,这是 U N I X 指示到达文件结尾处的方法。
3 poll 函数
① 概述
struct pollfd {
i n t f d ; /* file descriptor to check, or < 0 to ignore */
s h o r t e v e n t s ; /* events of interest on fd */
s h o r t r e v e n t s ; /* events that occurred on fd */
} ;
Nfds 说明 ufds 中的元素数。
② events
将 e v e n t s 成员设置为表中所示值的一个或几个。通过这些值告诉内核我们对该描述符关心的是什么。返回时,内核设置 r e v e n t s 成员,以说明对该描述符发生了什么事件。(注意, p o l l 没有更改 e v e n t s 成员,这与 s e l e c t 不同, s e l e c t 修改其参数以指示哪一个描述符已准备好了。)
头四行测试可读性,接着三行测试可写性,最后三行则是异常条件。最后三行是由内核在返回时设置的。即使在 e v e n t s 字段中没有指定这三个值,如果相应条件发生,则在 r e v e n t s 中也返回它们。
当一个描述符被挂断后( P O L L H U P ),就不能再写向该描述符。但是仍可能从该描述符读取到数据。
应当理解文件结束与挂断之间的区别。如果正在终端输入数据,并键入文件结束字符, P O L L I N 被打开,于是就可读文件结束指示( r e a d 返回 0 )。 P O L L H U P 在 r e v e n t s 中没有打开。如果读调制解调器,并且电话线已挂断,则在 r e v e n t s 中将接到 P O L L H U P 。
与 s e l e c t 一样,不论一个描述符是否阻塞,并不影响 p o l l 是否阻塞。
③ 等待时间
• timeout == INFTIM 永远等待。常数 INFTIM 定义在 <stropts.h>, 其值通常是- 1 。当所指定的描述符中的一个已准备好,或捕捉到一个信号则返回。如果捕捉到一个信号,则 p o l l 返回- 1 , errno 设置为 EINTR 。
• t i m e o u t == 0 不等待。测试所有描述符并立即返回。这是得到很多个描述符的状态而不阻塞 p o l l 函数的轮询方法。
• timeout > 0 等待 t i m e o u t 毫秒。当指定的描述符之一已准备好,或指定的时间值已超过时立即返回。如果已超时但是还没有一个描述符准备好,则返回值是 0 。(如果系统不提供毫秒分辨率,则 t i m e o u t 值取整到最近的支持值。)