Linux下的进程通信方式之管道

详解管道通信

概述及特点

当我们需要把一个进程的输出作为另一个进程的输入时,应该如何通讯?管道解决了这个问题。

管道是UNIX上历史最悠久的IPC方式,它在 20 世纪 70 年代早期 UNIX 的第三个版本上就出现了。在shell中我们是如何使用管道的?

1
ls | wc -l

管道的工作原理

  1. 一般的管道是半双工的,即只能在一个方向上进行数据流动。
  2. 一般的管道只能在具有共同祖先的两个进程之间使用的。
  3. 管道具有同步的属性,即写入端的进程必须等待读取端的进程将数据读走后才能继续往下执行。
  4. 管道读取是一次性的,

我们常说的管道指的是无名管道,而 FIFO(命名管道)是一种特殊的管道。

一个管道就是一个字节流

一个管道就是一个字节流。所以从管道读取数据时,可以是任何的数据大小,而不用管写入时是以多少个字节写入的。并且管道的数据传输是具有顺序的,从管道中读取出来的字节的顺序与它们被写入管道的顺序是完全一样的。在管道中无法使用 lseek()来随机地访问数据。

从管道中读取数据时

如果试图从一个没有数据的管道中读取数据,那么将会被阻塞知道有数据被写入管道中。

管道是单向的

管道时半双工通信方式,一次只可以进行读或是写。并且管道的一端用于写,一端用于读。

管道数据传输的原子保证

在单个进程写入数据的时候无需担心管道数据的同步问题,但是在多个进程向一个管道中写入数据时,Linux保证了写入不超过 PIPE_BUF 字节的操作是原子的。注意,不同的UNIX实现PIPE_BUF大小是不同的。

只有在数据被传输到管道的时候 PIPE_BUF 限制才会起作用。

如果只有一个进程进行数据写入的话,PIPE_BUF的取值就没有关系了,但是如果是有多个进程的写的话,写入的数据大于PIPE_BUF,内核可能会把该数据块划分为几个小的数据块。分为几次传输。

当写入的数据已经达到PIPE_BUF 字节时,write()会在必要的时候阻塞直到管道中的可用空间足以原子地完成操作。

如果此类阻塞的 write()被一个信号处理器中断了,那么这个调用会被解除阻塞并返回成功传输到管道中的字节数,这个字节数会少于请求写入的字节数(所谓的部分写入)。

小总结

单个进程通信时,PIPE_BUF取值没有关系,多个时又分为:传输完成前,已经传输完成后。

如果传输完成前传输的数据大小大于PIPE_BUF,那么内核将分隔这个数据块,分为多次传输,以保证操作的原子性

如果管道中的数据已经大于PIPE_BUF,此时内核会阻塞write,直到有read消耗管道中的数据。

管道的大小是有限的

管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的。一旦管道被填满之后,后续向该管道的写入操作就会被阻塞直到读者从管道中移除了一些数据为止。

在早于 2.6.11 的 Linux 内核中,管道的存储能力与系统页面的大小是一致的(如在 x86-32 上是 4096 字节),而从 Linux 2.6.11 起,管道的存储能力是65,536 字节。

管道的创建及使用

在bash中,使用|来创建一个管道,在系统编程中,提供了函数:

1
2
3
4
5
#include <unistd.h>
void pipe(int filedes[2])

//成功返回0,失败返回-1
//成功的 pipe()调用会在数组 filedes 中返回两个打开的文件描述符:一个表示管道的读取端filedes[0]),另一个表示管道的写入端(filedes[1])。

管道的创建

一般来说都是上图的右边部分,在fork()调用之后,其中一个进程应该立即关闭管道的写入端的描述符,另一个则应该关闭读取端的描述符。

关于管道读写进程的文件描述符关闭:

写进程的读文件描述符需要关闭
读进程的写文件描述符需要关闭

原因:

  1. 写进程的读文件描述符要关闭是因为,如果一个写进程去往一个没有读文件描述符被打开的管道中写数据,内核会发送一个信号SIGPIPE。在默认情况下,这个信号会杀死一个进程。但是进程可以捕捉或者忽略它,这样会导致管道上的写操作会因为EPIPE错误而失败。而这个错误对于进管道的状态是有用的。所以需要关闭写进程的读文件描述符,防止无法发送SIDPIPE信号。

  2. 读进程的写文件描述符需要被关闭是因为,如果所有的写文件描述符都被关闭了,读进程就会读到EOF表示文件的截止。如果读进程的写文件描述符没有被关闭的话,会导致无法读到EOF。所以读进程就会阻塞read,导致读被阻塞。

  3. 除开上面的这两种原因,及时关闭文件描述符,可以防止管道通信消耗文件描述符。

  4. 只有在管道的所有文件操作符都被关闭以后,内核才会回收该管道的资源。

关于匿名管道的读写端的开关所带来的影响

  • 如果一个管道的写端一直在写,而读端的引用计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只写不读再次调用write会导致管道堵塞;

  • 如果一个管道的读端一直在读,而写端的引用计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只读不写再次调用read会导致管道堵塞;

  • 而当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到件末尾样。

管道的应用

使用管道来同步进程

使用管道来执行shell命令

扩展阅读

https://www.cnblogs.com/zhuangquan/p/13141456.html

参考资料

《LINUX-UNIX系统编程》


Linux下的进程通信方式之管道
https://ysc2.github.io/ysc2.github.io/2023/11/10/Linux下的进程通信方式之管道/
作者
Ysc
发布于
2023年11月10日
许可协议