匿名管道常常用来实现父子进程的通信。通过pipe函数创建两个文件描述符,分别指向管道的读端和写端。
从内核对于共享文件的实现来理解管道
- 描述符表。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
- 文件表。所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置、引用计数(即当前指向该表项的描述符表项数), 以及一个指向 v-node 表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。直到它的引用计数为零,内核删除这个文件表表项。
- v-node表。所有的进程共享这张表。每个表项包含 stat 结构中的大多数信息,包括 st_mode 和 st_size 成员
多个文件描述符可以通过不同的文件表表项引用同一个文件,比如对同一个文件open了多次就会产生以下结果
父子进程对于文件共享的实现就是fork后,父子进程具有相同的文件描述符表,因而共享相同的文件位置。只有父子进程都关闭了相应文件描述符,内核才会删除对应的文件表表项。
因此管道用于父子进程通信的情形就像下面这张图(管道由父进程创建,使用循环队列)
管道的读写特点(默认采用阻塞)
- 写端全部关闭时(引用计数为0),读取完管道剩余数据后,再次read会返回0。
- 写端未全部关闭(引用计数大于0),但管道中已经没有剩余数据时,再次read会阻塞,直到有数据可读。
- 读端全部关闭时(引用计数为0),写数据会收到SIGPIPE信号,默认会导致进程异常终止。
- 读端未全部关闭(引用计数大于0),管道写满后,再次write会阻塞,直到管道由剩余空间。
单个管道不用于双向通信,一个管道只用于单向的字节流原因:
如果一个进程可以对管道进行读写,可能会导致写入的字节又被自己读出,这显然是违背意图的。
在使用管道时,写进程关闭读端,读进程关闭写端的原因:
- 减少管道占用资源
- 如果读进程没有关闭写的文件描述符,那么即使写进程已经关闭了写入的描述符,由于仍有一个写入的描述符开着,读进程执行read的时候会保持阻塞。如果写进程没有关闭读的文件描述符,那么即使读进程已经已经关闭了读的描述符,写进程仍然能够往管道里写入,导致写满管道然后阻塞。正常情况,也就是当没有读的文件描述符存在时,写入管道操作会让当前进程收到SIGPIPE信号。
那该怎么实现双向通信?
使用两个匿名管道,分别用于单向的字节流。