当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞I/O:

while ((n=read(STDIN_FILENO, buf, BUFSIZ)) > 0)
    if (write(STDOUT_FILENO, buf, n) != n)
        err_sys("write error");

这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?在这种情况下,我们不能在任一个描述符上进行阻塞读(read),否则可能会因为被阻塞在一个描述符的读操作上而导致另一个描述符即使有数据也无法处理。所以为了处理这种情况需要另一种不同的技术。

让我们观察telnet(1)命令的结构。该程序从终端(标准输入)读,将所得数据写到网络连接上,同时从网络连接读,将所得数据写到终端上(标准输出)。在网络连接的另一端,telnetd守护进程读用户键入的命令,并将所读到的送给 shell,这如同用户登录到远程机器上一样。telnetd 守护进程将执行用户键入命令而产生的输出通过 telnet 命令送回给用户,并显示在用户终端上。图14-13显示了这种工作情景。

图14-13 telnet程序概观

telnet 进程有两个输入,两个输出。我们不能对两个输入中的任一个使用阻塞 read,因为我们不知道到底哪一个输入会得到数据。

处理这种特殊问题的一种方法是,将一个进程变成两个进程(用fork),每个进程处理一条数据通路。图14-14中显示了这种安排。(System V的uucp通信包提供了cu(1)命令,其结构与此相似。)

图14-14 使用两个进程实现telnet程序

如果使用两个进程,则可使每个进程都执行阻塞read。但是这也产生了问题:操作什么时候终止?如果子进程接收到文件结束符(telnetd守护进程使网络连接断开),那么该子进程终止,然后父进程接收到 SIGCHLD 信号。但是,如果父进程终止(用户在终端上键入了文件结束符),那么父进程应通知子进程停止。为此可以使用一个信号(如SIGUSR1),但这使程序变得更加复杂。

我们可以不使用两个进程,而是用一个进程中的两个线程。虽然这避免了终止的复杂性,但却要求处理两个线程之间的同步,在复杂性方面这可能会得不偿失。

另一个方法是仍旧使用一个进程执行该程序,但使用非阻塞I/O读取数据。其基本思想是:将两个输入描述符都设置为非阻塞的,对第一个描述符发一个 read。如果该输入上有数据,则读数据并处理它。如果无数据可读,则该调用立即返回。然后对第二个描述符作同样的处理。在此之后,等待一定的时间(可能是若干秒),然后再尝试从第一个描述符读。这种形式的循环称为轮询。这种方法的不足之处是浪费CPU时间。大多数时间实际上是无数据可读,因此执行read系统调用浪费了时间。在每次循环后要等多长时间再执行下一轮循环也很难确定。虽然轮询技术在支持非阻塞I/O的所有系统上都可使用,但是在多任务系统中应当避免使用这种方法。

还有一种技术称为异步I/O(asynchronous I/O)。利用这种技术,进程告诉内核:当描述符准备好可以进行I/O时,用一个信号通知它。这种技术有两个问题。首先,尽管一些系统提供了各自的受限形式的异步I/O,但POSIX采纳了另外一套标准化接口,所以可移植性成为一个问题(以前,POSIX异步I/O是Single UNIX Specification中是可选设施,但现在,这些接口在SUSv4中是必需的)。System V 提供了 SIGPOLL 信号来支持受限形式的异步 I/O,但是仅当描述符引用STREAMS设备时,此信号才起作用。BSD有一个类似的信号SIGIO,但也有类似的限制:仅当描述符引用终端设备或网络时它才能起作用。

这种技术的第二个问题是,这种信号对每个进程而言只有1个(SIGPOLLSIGIO)。如果使该信号对两个描述符都起作用(在我们正在讨论的实例中,从两个描述符读),那么进程在接到此信号时将无法判别是哪一个描述符准备好了。尽管POSIX.1异步I/O接口允许选择哪个信号作为通知,但能用的信号数量仍远小于潜在的打开文件描述符的数量。为了确定是哪一个描述符准备好了,仍需将这两个描述符都设置为非阻塞的,并顺序尝试执行I/O。我们将在14.5节讨论异步I/O。

一种比较好的技术是使用I/O多路转接(I/O multiplex-ing)。为了使用这种技术,先构造一张我们感兴趣的描述符(通常都不止一个)的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。pollpselectselect这3个函数使我们能够执行I/O多路转接。在从这些函数返回时,进程会被告知哪些描述符已准备好可以进行I/O。

POSIX指定,为了在程序中使用select,必须包括<sys/se-lect.h>。但较老的系统还要求包括<sys/types.h>、<sys/time.h>和<unistd.h>。查看select手册页可以弄清楚你的系统都支持什么。

I/O多路转接在4.2BSD中是用select函数提供的。虽然该函数主要用于终端I/O和网络I/O,但它对其他描述符同样是起作用的。SVR3在增加STREAMS机制时增加了poll函数。但在SVR4之前,poll只对STREAMS设备起作用。SVR4支持对任意描述符起作用的poll。

results matching ""

    No results matching ""