管道是linux的一种通信方式,一种两个进程间进行单向通信的机制,它提供了简单的流控制机制,系统提供了pipe生成一个管道并返回两个描述符,一 个用来读管道,一个用来写管道,因此它们可以共享访问文件,这样每个管道就可以有很多个读进程和写进程,然而实际上进程不知道它正在读或写的是一个管道, 它有以下局限性:
由于管道传递数据的单向性,决定其使用的局限性:数据的单向流动;有亲缘关系的进程间的通信;没有名字;缓冲区大小受限制(传送的是无格式的字节流)
管道就是一个存在于内存的特殊文件,进程就是通过读写该文件进行通信的(内存中的某个页面作为数据缓冲区)
如果要建立两个进程的数据通路,首先父进程应调用pipe创建管道,接着调用fork,由于子进程自动继承父进程的数据段,便可实现父子进程同时拥有管 道 的操作权,如下图(a),但为了维护管道,比如想要一个父进程到子进程的数据通道时,在父进程中关闭读出端,子进程中关闭管道的写入端,图(b)。
管道的创建
Linux下创建管道可以通过函数pipe来完成。该函数如果调用成功返回0,并且数组中将包含两个新的文件描述符:如果有错误发生,返回-1。该函数原型如下:
#include <unistd.h>
int pipe(int fd[2]);
管道两端可分别用描述符fd[0]以及fd[1]来描述。需要注意的是,管道两端的任务是固定的,一端只能用于读,由描述符fd[0]表示,称其为管道读 端;另一端只能用于写,由描述符fd[1]来表示,称其为管道写端。如果试图从管道写端读数据,或者向管道读端写数据都将导致出错。
管道是一种文件,因此对文件操作的I/O函数都可以用于管道,如read(),write()等。
注意:管道是一旦创建成功,就可以作为一般的文件来使用,对一般文件进行操作的I/O函数也适用于管道,如:
管道的一般用法是,进程在使用fork函数创建子进程前先创建一个管道,该管道用于在父子进程间通信,然后创建子进程,之后你进程关闭管道的读端,子进程 关闭管道的写端。父进程负责向管道写数据而子进程负责读数据。当然父进程可以关闭管道的写端而子进程关闭管道的读端。这样管道就可以用于父子进程间的通 信,也可用于兄弟进程间的通信。下面介绍进程是如何通过管道来读写数据的。
管道关闭
管道关闭只需将这两个文件描述符关闭即可,可以使用普通的close函数进行关闭。
从管道中读数据
如果某进程要读取管道中的数据,那么该进程应当关闭fd1,同时向管道写数据的进程应当关闭fd0。因为管道只能用于具有亲缘关系的进程间的通信,在各进程进行通信时,它们共享文件描述符。在使用前,应及时地关闭不需要的管道的另一端,以避免意外错误的发生。
进程在管道的读端读数据时,如果管道的写端不存在,则读进程认为已经读到了数据的末尾,该函数返回读出的字节数为0;管道的写端不存在,则读取的字节数大 于PIPE_BUF,则返回管道中现有的所有数据;如果请求的字节数不大于PIPE_BUF,则返回管道中现有的所有数据(此时,管道中数据量小于请求的 数据量),或者返回请求的字节数(此时,管道中数据量大于等于请求的数据量)。
注意:PIPE_BUF在include/linux/limits.h中定义,不同的内核版本可能会有所不同。
向管道中写数据
如果某进程希望向管道中写入数据,那么该进程应该关闭fd0文件描述符,同时管道另一端的进程关闭fd1。向管道中写入数据时,Linux不保证写入的原 子性(原子性是指操作在任何时候都不能被任何原因所打断,操作要么不做要么就一定完成)。管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读 进程不读走管道缓冲区中的数据,那么写操作将一直被阻塞等待。
在写管道时,如果要求写的字节数小于等于PIPE_BUF,则多个进程对同一管道的写操作不会交错进行。但是,如果有多个进程同时写一个管道,而且某些进程要求写的字节数超过PIPE_BUF所能容纳时,则多个写操作的数据可能会交错。
注意:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号。应用程序可以处理也可以忽略该信号,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错,错误码为EPIPE。