简单聊聊: Linux匿名管道

相信很多在linux平台工作的童鞋, 都很熟悉管道符 '|', 通过它, 我们能够很灵活的将几种不同的命令协同起来完成一件任务。就好像下面的命令:


  1. echo 123 | awk '{print $0+123}'       # 输出246 

不过这次咱们不来说这些用法, 而是来探讨一些更加有意思的, 那就是 管道两边的数据流"实时性" 和 管道使用的小提示。

其实我们在利用管道的时候, 可能会不经意的去想, 我前一个命令的输出, 是全部处理完再通过管道传给第二个命令, 还是一边处理一边输出呢? 可能在大家是试验中或者工作经验中, 应该是左边的命令全部处理完再一次性交给右边的命令进行处理, 不光是大家, 我在最初接触管道时, 也曾有这么一个误会, 因为我们通过现象看到的就是这样。

但其实只要有简单了解过管道这工具, 应该都不难得出解释:

  • 管道是两边是同时进行, 也就是说, 左边的命令输出到管道, 管道的右边将马上进行处理。

管道的定义

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会堵塞,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

管道工作流程图

通过上面的解释可以看到, 假设 COMMAND1 | COMMAND2, 那么COMMAND1的标准输出, 将会被绑定到管道的写端, 而COMMAND2的标准输入将会绑定到管道的读端, 所以当COMMAND1一有输出, 将会马上通过管道传给COMMAND2, 我们先来做个实验验证下:


  1. # 1.py 
  2. import time 
  3. import sys 
  4. while 1: 
  5.     print '1111' 
  6.     time.sleep(3) 
  7.     print '2222' 
  8.     time.sleep(3)  
  9. [root@iZ23pynfq19Z ~]# python 1 | cat  

在上面的命令, 我们可以猜测下输出结果: 究竟是 睡眠6秒之后, 输出"1111222", 还是输出 "1111" 睡眠3秒, 再输出 "2222", 然后再睡眠3秒, 再输出"1111" 呢? 答案就是: 都不是! what! 这不可能, 大家可以尝试下, 我们会看到终端没反应了, 为什么呢? 这就要涉及到文件IO的缓冲方式了,关于文件IO, 可以参考我的另一篇文章: 浅谈文件描述符1和2, 在最下面的地方提到文件IO的三种缓冲方式:

  • 全缓冲: 直到缓冲区被填满,才调用系统I/O函数, (一般是针对文件)
  • 行缓冲: 遇到换行符就输出(标准输出)
  • 无缓冲: 没有缓冲区,数据会立即读入或者输出到外存文件和设备上(标准错误

因为标准输出被改写, 所以将会采取全缓冲的方式, 也就是说, 直到缓冲区被填满, 或者手动显示调用flush刷入,才能看到输出.那我们可以将代码改写成下面两种方式吧


  1. # 方式1: 填满缓冲区, 我这边大小是4096字节, 你们也可以试下这个值, 估计都一样 
  2. import time 
  3. import sys 
  4. while 1: 
  5.     print '1111' * 4096 
  6.     time.sleep(3) 
  7.     print '2222' * 4096 
  8.     time.sleep(3)  
  9. # 方式2: 手动刷入写队列 
  10. import time 
  11. import sys 
  12. while 1: 
  13.     print '1111' 
  14.     sys.stdout.flush()    // 因为是标准输出, 所以直接通过sys的接口去flush 
  15.     time.sleep(3) 
  16.     print '2222'  
  17.     sys.stdout.flush() 
  18.     time.sleep(3) 

输出结果:


  1. # 第一种方式: 
  2. [root@iZ23pynfq19Z ~]# python 1 | cat  
  3. 1111.....(超多1, 刷屏了..) 
  4. 睡眠3秒.. 
  5. 2222.....(超多2, 刷屏了..) 
  6.  
  7. # 第二种方式: 
  8. [root@iZ23pynfq19Z ~]# python 1 | cat  
  9. 1111 
  10. 睡眠3秒.. 
  11. 2222 
  12. 睡眠3秒.. 
  13. 1111 
  14. .... 

在这里我们已经能够得出结果, 如果像我们以前所想的那样, 要等到COMMAND1全部执行完才一次性输出给COMMAND2, 那么结果应该是无限堵塞..因为我的程序一直没有执行完..这样应该是不符合老前辈们设计初衷的, 因为这样可能会导致管道越来越大..然而管道也是有大小的~ 具体可以去看posix标准, 所以我们得出结论是: 只要COMMAND1的输出写入管道的写端(不管是缓冲区满还是手动flush), COMMAND2都将立刻得到数据并且马上处理.

那么 管道两边的数据流"实时性" 讨论到就先暂告一段落, 接下来将在这个基础上继续讨论: 管道使用的小提示.

在开始讨论前, 我想先引入一个专业术语, 也是我们偶尔会遇到的, 那就是: SIGPIPE

或者是一个更加具体的描述: broken pipe (管道破裂)

上面的专业术语都是跟管道读写规则息息相关的, 那咱们来看下 管道的读写规则吧:

当没有数据可读时

  • O_NONBLOCK (未设置):read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK ( 设置 ) :read调用返回-1,errno值为EAGAIN。

当管道满的时候

  • O_NONBLOCK (未设置): write调用阻塞,直到有进程读走数据
  • O_NONBLOCK ( 设置 ):调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

在上面我们可以看到, 如果我们收到SIGPIPE信号, 那么一般情况就是读端被关闭, 但是写端却依旧尝试写入

咱们来重现下 SIGPIPE


  1. #!/usr/bin/python  
  2. import time  
  3. import sys  
  4. while 1:  
  5. time.sleep(10) # 手速不够快的童鞋可以将睡眠时间设置长点  
  6. print '1111'  
  7. sys.stdout.flush() 

这次执行命令需要考验手速了, 因为我们要赶在py醒过来之前, 将读端进程杀掉


  1. python 1 | cat  
  2. ------------------------  
  3. # 另一个终端  
  4. [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'cat|python'  
  5. root 10775 4074 0 00:05 pts/2 00:00:00 python 1  
  6. root 10776 4074 0 00:05 pts/2 00:00:00 cat # 读端进程  
  7. root 10833 32581 0 00:06 pts/0 00:00:00 grep -P cat|python  
  8. [root@iZ23pynfq19Z ~]# kill 10776 

输出结果


  1. [root@iZ23pynfq19Z ~]# python 1 | cat  
  2. Traceback (most recent call last):  
  3. File "1", line 6, in  
  4. sys.stdout.flush()  
  5. IOError: [Errno 32] Broken pipe  
  6. Terminated 

从上图我们可以验证两个点:

  1. 当我们杀掉读端时, 写端会收到SIGPIPE而默认退出, 管道结束
  2. 当我们杀掉读端时, 写端的程序并不会马上收到SIGPIPE, 相反的, 只有真正写入管道写端时才会触发这个错误

如果写入一个 读端已经关闭的管道, 将会收到一个 SIGPIPE, 那读一个写端已经关闭的管道又会这样呢?


  1. import time 
  2. import sys  
  3. # 这次我们不需要死循环, 因为我们想要写端快点关闭退出 
  4. time.sleep(5)    
  5. print '1111' 
  6. sys.stdout.flush()  
  7. # 因为我们想要 读端 等到足够长的时间, 让写端关闭, 所以我们需要利用awk先睡眠10秒 
  8. [root@iZ23pynfq19Z ~]# python 1.py | awk '{system("sleep 10");print 123}'  
  9.  ------------------------ 
  10. [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'awk|python' 
  11. root     11717  4074  0 00:20 pts/2    00:00:00 python 1.py 
  12. root     11718  4074  0 00:20 pts/2    00:00:00 awk {system("sleep 10");print 123} 
  13. root     11721 32581  0 00:20 pts/0    00:00:00 grep -P awk|python  
  14. # 5秒过后 
  15. [root@iZ23pynfq19Z ~]# ps -fe | grep -P 'awk|python' 
  16. root     11685  4074  0 00:20 pts/2    00:00:00 awk {system("sleep 10");print 123} 
  17. root     11698 32581  0 00:20 pts/0    00:00:00 grep -P awk|python  
  18. # 10秒过后 
  19. [root@iZ23pynfq19Z ~]# python 1 | awk '{system("sleep 10");print 123}'  
  20. 123 

在上面也已经证明了上文提到的读写规则: 如果所有管道写端对应的文件描述符被关闭,将产生EOF结束标志,read返回0, 程序退出

总结

通过上面的理论和实验, 我们知道在使用管道时, 两边命令的数据传输过程, 以及对管道读写规则有了初步的认识, 希望我们以后在工作时, 再接触管道时, 能够更加有把握的去利用这一强大的工具。

本文作者:佚名

来源:51CTO

时间: 2024-12-21 00:23:05

简单聊聊: Linux匿名管道的相关文章

Linux进程间通信——使用匿名管道

在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式--匿名管道,通过它进程间可以交换更多有用的数据.   一.什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号"|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入.   举个例子,在shell中输入

Linux进程间通信学习:如何使用匿名管道

在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式--匿名管道,通过它进程间可以交换更多有用的数据. 一.什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号"|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入. 举个例子,在shell中输入命令:l

IPC——匿名管道

 Linux进程间通信--使用匿名管道 在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式--匿名管道,通过它进程间可以交换更多有用的数据.   一.什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号"|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的

linux shell 管道命令(pipe)使用及与shell重定向区别_linux shell

看了前面一节:linux shell数据重定向(输入重定向与输出重定向)详细分析 估计还有一些朋友是头晕晕的,好复杂的重定向了.这次我们看下管道命令了.shell管道,可以说用法就简单多了. 管道命令操作符是:"|",它仅能处理经由前面一个指令传出的正确输出信息,也就是 standard output 的信息,对于 stdandard error 信息没有直接处理能力.然后,传递给下一个命令,作为标准的输入 standard input. 管道命令使用说明: 先看下下面图: comma

匿名管道 readfile-匿名管道阻塞,ReadFile处就不动了

问题描述 匿名管道阻塞,ReadFile处就不动了 #include #include main() { HANDLE read=NULL,write=NULL; SECURITY_ATTRIBUTES ss; STARTUPINFO sa={0}; PROCESS_INFORMATION pp={0}; //定义结构体SECURITY_ATTRIBUTES变量 char text[]="匿名管道程序测试!"; DWORD writetext; ss.nLength=sizeof(ss

进程通信系列-匿名管道

匿名管道只能在本机由父进程至子进程,优点在于子进程方便重定向,常用于应用程序内部 注意判断此进程是父类还是子类,代码长度一般 匿名管道类 #include "stdafx.h" #include "niming.h" #include <iostream> using namespace std; niming::niming(void) { } niming::~niming(void) { } int niming::build() { SECURI

在VMware上制作一个简单的Linux

大体思路 boot root initrd.gz grub vmlinuz-2.6.18-308.el5 bin sbin lib etc proc sys dev boot   有以上内容我们就可以运行一个非常简单的Linux,只需要往里面添加各种配置文件,就可以启动我们所需要的各种服务.在制作之前,我们先做一些准备工作. 1.在VMware上添加一块新的IDE磁盘 2.将这块盘分区,/dev/hdb1 /dev/hdb2,之后格式化为ext3的文件系统 3.挂载/dev/hdb1到/mnt/

我是学客-【进程间通信】匿名管道问题,困扰已久,求各位帮忙指点一二

问题描述 [进程间通信]匿名管道问题,困扰已久,求各位帮忙指点一二 代码如下,这样是没有问题的,子进程收到hello child之后会返回信息.只是不知为什么 区域A 中的 问题代码 必须要在创建进程之前 关闭写句柄? 如果放到 CreateProcess之后 ,ReadFile就会一直卡在那,子进程估计没有返回,但这是为什么? 这个程序目前遇到的问题是区域A的信息成功写进去了,随后的句柄被关闭了,等子进程创建完毕后,再用区域B的WriteFile肯定是没办法写进去了,而区域C的ReadFile

代码-匿名管道子进程创建失败

问题描述 匿名管道子进程创建失败 我是跟着孙鑫VC教程学的,跟着他的步骤敲得代码,但是子进程总是创建失败, if(!CreateProcess(("..ChildDebugChild1.exe"),NULL,NULL,NULL, TRUE,0,NULL,NULL,&sui,&pi)) { CloseHandle(hRead); CloseHandle(hWrite); hRead=NULL; hWrite=NULL; MessageBox("创建子进程失败!&