在某些特性上又不同于文件,例如,当数据读出后,则管道中就没有数据了,但文件没有这个特性。
匿名半双工管道在系统中是没有实名的,并不可以在文件系统中以任何方式看到该管道。它只是进程的一种资源,会随着进程的结束而被系统清除。管道通信是在UNIX系统中应用比较频繁的一种方式,例如使用grep查找。
上述命令中使用的是半双工管道,即grep命令的输入是ls命令的输出。管道从数据流动方向上又分全双工管道以及半双工管道,当然全双工管道现在某些系统还不支持,其在具体的实现过程中也只是在文件打开的方式上有一点区别(在操作规则上也有一些不同,全双工管道要相比半双工复杂的多)。
匿名管道没有名字,对于管道中使用的文件描述符没有路径名,也就是不存在任何意义上的文件,它们只是在内存中跟某一个索引节点相关联的两个文件描述符。匿名半双工管道的主要特性如下:
● 数据只能在一个方向上移动。
● 只能在具有公共祖先的进程间通信,即或是父子关系进程间、或是在兄弟关系进程间通信。
尽管有如此限制,半双工管道还是最常用的通信方式。Linux环境下使用pipe函数创建一个匿名半双工管道,其函数原型如下:
参数int fd[2]为一个长度为2的文件描述符数组,fd[0]是读出端,fd[1]是写入端,函数的返回值为0表示成功,–1表示失败。当函数成功返回,则自动维护了一个从fd[1]到fd[0]的数据通道。
下面实例演示了如何使用pipe函数创建管道以及关闭管道。程序中先使用函数pipe建立管道,并使用管道传输数据,在程序的结束部分,释放掉管道占用的文件资源(两个文件描述符),具体实现如下。
当对管道进行读写操作时,使用read和write函数对管道进行操作。当对一个读端已经关闭的管道进行写操作时,会产生信号SIGPIPE,说明管道读端已经关闭,并且write操作返回为–1,errno的值为EPIPE,对于SIGPIPE信号可以进行捕捉处理。如果写入进程不能捕捉或者干脆忽略SIGPIPE信号,则写入进程会中断。
%注意:在进行读写管道时,对一个管道进行读操作后,read函数返回为0,有两种意义,一种是管道中无数据并且写入端已经关闭。另一种是管道中无数据,写入端依然存活。这两种情况要根据需要分别处理。
从程序实例14.1中可以发现,单独一个进程操作管道是没有任何意义的,管道的应用一般体现在父子进程或者兄弟进程的通信。
如果要建立一个父进程到子进程的数据通道,可以先调用pipe函数紧接着调用 fork函数,由于子进程自动继承父进程的数据段,则父子进程同时拥有管道的操作权,此时管道的方向取决于用户怎么维护该管道,管道示意图如图14-3所示。
图14-3 管道示意图
当用户想要一个父进程到子进程的数据通道时,可以在父进程中关闭管道的读出端,相应的在子进程中关闭管道的输出端,如图14-3中图B所示。相反的,当维护子进程到父进程的数据通道时,在父进程中关闭输出,子进程中关闭读入即可。总之,使用pipe 及fork组合,可以构造出所有的父进程与子进程,或子进程到兄弟进程的管道。
下面实例演示了使用pipe以及fork组合实现父子进程通信。程序中先使用pipe函数建立管道,使用fork函数创建子进程。在父子进程中维护管道的数据方向,并在父进程中向子进程发送消息,在子进程中接收消息并输出到标准输出。
程序中使用pipe函数加fork组合,实现父进程到子进程的通信。程序在父进程段中关闭了管道的读出端,并相应地在子进程中关闭了管道的输入端,从而实现数据从父进程流向子进程。
管道在兄弟进程间应用时,应该先在父进程中建立管道,然后调用fork函数创建子进程,在父子进程中维护管道的数据方向。
%注意:这里的问题是维护管道的顺序,当父进程创建了管道,只有子进程已经继承了管道后,父进程才可以执行关闭管道的操作。如果在fork之前已经关闭管道,子进程将不能继承到可以使用的管道的。
下面实例演示了管道在兄弟进程间通信。下例中在父进程中创建管道,并使用fork函数创建2个子进程。在第1个子进程中发送消息到第2个子进程,第2个子进程中读出消息并处理。在父进程中,由于并不使用管道通信,所以什么都不做,直接关闭了管道的两端并退出。