无名管道作为Linux进程间通讯,我们这里把理论和实际结合起来说明。

1.什么是管道

管道,英文位pipe,在学习linux系统编程一个重要概念.它的发明人是道格拉斯.麦克罗伊,这位也是UNIX上早期shell的发明人。他在发明了shell之后,发现系统操作执行命令的时候,经常有需求要将一个程序的输出交给另一个程序进行处理,这种操作可以使用输入输出重定向加文件搞定,比如:

输入以下命令行:

yates@yates-virtual-machine:~/test/code/FOLDER$ ls  -l /etc/ > etc.txt
yates@yates-virtual-machine:~/test/code/FOLDER$ wc -l etc.txt
255 etc.txt

后面发明管道,在shell中,可以使用 “|”连接个命令,shell会把前后两个命令输入输出用一个管道相连,以便得到进程间通讯的目的。

yates@yates-virtual-machine:~/test/code/FOLDER$ ls  -l /etc/ | wc -l
255

以上两种方法,你认为分开两个进程,前面进程以写入方式打开文件,后面以读的方式打开。管道内容缓存在内存中。

无名管道:

它的特点只能在父子进程中使用,父进程产生子进程前打开一个管道文件,然后fork后,子进程拷贝父进程的管道文件描述符,以达到共用一个管道通信的目的。除了父子进程外,别的都不知道该管道文件符,确保数据传输安全性。

创建匿名管道:

匿名管道创建非常简单,用pipe函数创建出两个文件描述符,再用write,read分别对管道进行读写数据。请看以下demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFSIZE 4096
#define STRING "1231313"int main(void)
{int fd[2];char buf[BUFSIZ];if (pipe(fd) == -1){perror("pipe error");exit(1);}int ret;int count = 0;ret = write(fd[1], STRING, strlen(STRING));if (ret < 0){perror("write error ");exit(1);}ret = read(fd[0],buf,sizeof(buf));if(ret < 0){perror("read error");exit(1);}printf("read buffer =%s\r\n",buf);exit(0);
}

以上代码执行结果是

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
read buffer =1231313

以上demo列举最简单匿名管道创建和读写操作。

注意:fd[1]用来写操作,fd[0]用来读操作。

如果在两个进程之间,应该怎么通过管道进行通讯呢?

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main()
{int pipefd[2];pid_t pid;char buf[BUFSIZ];if (pipe(pipefd) == -1){perror("pipe error\r\n");exit(1);}int ret = 0;pid = fork();if (pid == -1){perror("fork error \r\n");exit(1);}else if (pid == 0){printf("this is a child,pid=%d\r\n", getpid());write(pipefd[1], STRING, strlen(STRING));}else{printf("this is a father , pid=%d\r\n", getpid());ret = read(pipefd[0], buf, sizeof(STRING));if(ret<0){printf("read data error %d\r\n",ret);exit(1);}printf("%s\r\n", buf);}exit(0);
}

以上demo运行结果是:

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
this is a father , pid=10043
this is a child,pid=10044
1231313

阻塞和非阻塞

管道可以分为阻塞和非阻塞,通过fcntl函数设置

当没有数据可读时

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

当管道满的时候

  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

当默认创建文件描述符时,默认是打开阻塞模式,请看一下demo。

情况一:默认打开阻塞模式,只有读操作

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"int main(void)
{int fd[2];char buf[BUFSIZ];if (pipe(fd) == -1){perror("pipe error");exit(1);}int ret;int count = 0;printf("ready to read pipe data\r\n");ret = read(fd[0],buf,sizeof(buf));if (ret < 0){perror("read error ");exit(1);}printf("buffer:%s\r\n",buf);
}

运行以上demo结果是:

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
ready to read pipe data

程序一直阻塞在read函数,因为此时read函数默认为阻塞模式。

情况二:非阻塞read

通过设置fcntl函数设置read模式为非阻塞模式。

int flag = fcntl(fd[0],F_GETFL); //读取默认模式
 ret = fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);//使能非阻塞模式

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main(void)
{int fd[2];char buf[BUFSIZ];if (pipe(fd) == -1){perror("pipe error");exit(1);}int ret;int count = 0;int flag = fcntl(fd[0],F_GETFL);//读取默认配置ret = fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);//使能非阻塞模式printf("ready to read pipe data\r\n");ret = read(fd[0],buf,sizeof(buf));if (ret < 0){printf("read error :%d\r\n",ret);exit(1);}printf("buffer:%s\r\n",buf);
}

执行以上程序结果是:

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
ready to read pipe data
read error :-1

结论:当没有write函数情况下,raad函数立即返回为-1.

情景三:如果先关闭写文件描述符,在非阻塞模式下只读取数据

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main(void)
{int fd[2];char buf[BUFSIZ];if (pipe(fd) == -1){perror("pipe error");exit(1);}int ret;int count = 0;int flag = fcntl(fd[0],F_GETFL);ret = fcntl(fd[0],F_SETFL,flag | O_NONBLOCK);printf("ready to read pipe data\r\n");close(fd[1]);ret = read(fd[0],buf,sizeof(buf));if (ret < 0){printf("read error :%d\r\n",ret);exit(1);}printf("ret=%d,buffer:%s\r\n",ret,buf);
}

执行以上demo结果是

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
ready to read pipe data
ret=0,buffer:

当关闭写文件描述符时,read函数立马返回0.

会有小伙伴问,如果关闭读文件描述符fd[0]能,这里不上代码,read函数结果返回-1.

结论:在非阻塞模式下,当关闭写文件描述符时,read函数立马返回0。如果关闭读文件描述符fd[0]能,read结果返回-1。

情景四:如果在阻塞模式下,只写入数据。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main(void)
{int fd[2];if (pipe(fd) == -1){perror("pipe error");exit(1);}int ret;int count = 0;ret = write(fd[1], STRING, strlen(STRING));if (ret < 0){perror("write error ");exit(1);}printf("end\r\n");
}

运行结果是:

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
end

说明第一次写入write成功,但是如果循环写入呢?

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
write count=0
write count=1
write count=2
write count=3
write count=4
write count=5
write count=6
write count=7
write count=8
write count=9
write count=10
...
write count=9352
write count=9353
write count=9354
write count=9355
write count=9356
write count=9357
write count=9358
write count=9359

结果去到9359次停止,为什么呢?

这里需要说到管道容量。

管道实际上就是内核控制的一个内存缓冲区,有容量上线,我们这里把最大缓冲大小命名为PIPESIZE,注意,PIPESIZE最小为一页,如果电脑磁盘格式为4k的话,那PIPESIZE最小为4096,但是在64位ubuntu系统上,通过查看/proc/sys/fs/pipe-max-size ,PIPESIZE设置为1048576(2^20)。

管道容量大小可以设置的,通过fcntl 设置F_SETPIPE_SZ设置容量大小,注意最低为1page(一般系统为4096)。

int ret = fcntl(fd[1],F_SETPIPE_SZ,BUFSIZE);//该函数设置管道大小。

请看以下demo

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main(void){int fd[2];if(pipe(fd)==-1){perror("pipe error");exit(1);}int ret ;int count =0;ret = fcntl(fd[1],F_SETPIPE_SZ,BUFSIZE);while(1){ret = write(fd[1],STRING,strlen(STRING));if(ret < 0 ){perror("write error ");exit(1);}printf("write count=%d\r\n",count);count ++;}
}

以上demo执行结果为

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
write count=0
write count=1
write count=2
write count=3
write count=4
write count=5
write count=6
write count=7
write count=8
write count=9
write count=10
write count=11
write count=12
write count=13
write count=14
write count=15
write count=16
write count=17
write count=18
write count=19
write count=20
write count=21
write count=22
write count=23
write count=24
write count=25
...
write count=567
write count=568
write count=569
write count=570
write count=571
write count=572
write count=573
write count=574
write count=575
write count=576
write count=577
write count=578
write count=579
write count=580
write count=581
write count=582
write count=583
write count=584

比上个demo代码write执行次数少了很多。

结论:在阻塞模式下,当管道容量未达到上上限时,write函数可以成功写进入,当达到容量上限是,write函数会一直阻塞。

场景五:在非阻塞模式下一直写入

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#include <fcntl.h>
#define BUFSIZE 4096
#define STRING "1231313"
int main(void){int fd[2];if(pipe(fd)==-1){perror("pipe error");exit(1);}int ret ;int count =0;int flag = fcntl(fd[1],F_GETFL);fcntl(fd[1],F_SETFL,flag|O_NONBLOCK);ret = fcntl(fd[1],F_SETPIPE_SZ,BUFSIZE);while(1){ret = write(fd[1],STRING,strlen(STRING));if(ret < 0 ){perror("write error ");exit(1);}printf("write count=%d\r\n",count);count ++;}
}

运行结果为

yates@yates-virtual-machine:~/test/code/FOLDER$ ./pipe
write count=0
write count=1
write count=2
write count=3
write count=4
write count=5
write count=6
write count=7
write count=8
write count=9
write count=10...
write count=560
write count=561
write count=562
write count=563
write count=564
write count=565
write count=566
write count=567
write count=568
write count=569
write count=570
write count=571
write count=572
write count=573
write count=574
write count=575
write count=576
write count=577
write count=578
write count=579
write count=580
write count=581
write count=582
write count=583
write count=584
write error : Resource temporarily unavailable

结论,当PIPESIZE满的时候,write函数返回为-1.

按照以上场景,总结出以下结论:

O_NONBLOCK关闭,n <= PIPESIZE

n个字节的写入操作是原子操作,write系统调用可能会因为管道容量(PIPESIZE)没有足够的空间存放n字节长度而阻塞。

O_NONBLOCK打开,n <= PIPESIZE

如果有足够的空间存放n字节长度,write调用会立即返回成功,并且对数据进行写操作。空间不够则立即报错返回,并且errno被设置为EAGAIN。

O_NONBLOCK关闭,n > PIPESIZE

对n字节的写入操作不保证是原子的,就是说这次写入操作的数据可能会跟其他进程写这个管道的数据进行交叉。当管道容量长度低于要写的数据长度的时候write操作会被阻塞。

O_NONBLOCK打开,n > PIPESIZE

如果管道空间已满。write调用报错返回并且errno被设置为EAGAIN。如果没满,则可能会写入从1到n个字节长度,这取决于当前管道的剩余空间长度,并且这些数据可能跟别的进程的数据有交叉。

Linux系统编程- 无名管道(匿名管道)相关推荐

  1. 【Linux系统编程学习】匿名管道pipe与有名管道fifo

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 0. 关于进程通信 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到 ...

  2. Linux系统编程:pipe匿名管道的使用,实现linux命令下管道命令

    pipe函数介绍 函数原型int pipe(int pipefd[2]) 来创建匿名管道; 传出2个fd 文件描述符,pipefd[0]表示匿名管道的读端,pipefd[1]表示匿名管道的写端.有这个 ...

  3. Linux系统编程:fifo有名管道的使用

    fifo介绍 我们可以利用管道进行进程间通信,已经有匿名管道 为啥还要fifo 有名管道呢?有名管道是对匿名管道的一个补充,匿名管道是用在有血缘关系的进程间通信.fifo有名管道呢,可以用在任何进程间 ...

  4. 【Linux系统编程】进程间通信--有名管道

    命名管道的概述 无名管道,由于没有名字,只能用于亲缘关系的进程间通信(更多详情,请看<无名管道>).为了克服这个缺点,提出了命名管道(FIFO),也叫有名管道.FIFO 文件. 命名管道( ...

  5. Linux系统编程——进程间通信:命名管道(FIFO)

    命名管道的概述 无名管道,由于没有名字,只能用于亲缘关系的进程间通信(更多详情,请看<无名管道>).为了克服这个缺点,提出了命名管道(FIFO),也叫有名管道.FIFO 文件. 命名管道( ...

  6. linux程序设计百度网盘,linux系统编程视频 百度网盘下载

    本帖最后由 雇佣兵333 于 2015-5-19 16:15 编辑 c教程目录: Linux开发快速入门培训 gcc快速入门 Makefile快速入门 GDB快速入门 Linux系统编程之文件篇 01 ...

  7. 【Linux系统编程】进程间通信之无名管道

    00. 目录 文章目录 00. 目录 01. 管道概述 02. 管道创建函数 03. 管道的特性 04. 管道设置非阻塞 05. 附录 01. 管道概述 管道也叫无名管道,它是是 UNIX 系统 IP ...

  8. linux有名管道数据异常,Linux系统编程—有名管道

    ▋****1. 管道的概念 管道,又名「无名管理」,或「匿名管道」,管道是一种非常基本,也是使用非常频繁的IPC方式. 1.1 管道本质 管道的本质也是一种文件,不过是伪文件,实际上是一块内核缓冲区, ...

  9. linux系统编程之管道(三):命名管道FIFO和mkfifo函数

    进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的.如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通 ...

最新文章

  1. hdu4768 非常规的二分
  2. 取input 输入_tensorRT动态输入(python)
  3. 从零入门 Serverless | 教你 7 步快速构建 GitLab 持续集成环境
  4. codeforces741C Arpa’s overnight party and Mehrdad’s silent entering(二分图)
  5. Dusktree System (1)
  6. 试题 历届试题 买不到的数目(dp/数学)
  7. ftp无法与服务器建立连接_建立与Oracle数据库服务器连接的两种连接模式
  8. StudentManager-java+mysql学生管理系统
  9. 申请Let's Encrypt永久免费SSL证书
  10. CPU,内存, 硬盘,指令之间的关系
  11. 使用Nacos配置中心云端化本地application.properties
  12. pass the URL parameters from the webseite page to Flash
  13. 【多媒体封装格式详解】---MKV【1】
  14. 单片机c语言常用的语句有几条,单片机C语言教程-基础语句
  15. ubuntu20.05安装vmware workstation 16,踩坑:GLib does not have GSettings support.
  16. 微信小程序锦鲤砍价 搭建教程完整版
  17. 【HBuilder】前端IDE神器
  18. 在哪下拼多多上传助手?拼多多软件方法介绍
  19. (015) 自动加载
  20. nodejs操作Excel表格

热门文章

  1. windows常用操作命令
  2. Axure RP 如何实现导航栏切换页面——母版
  3. 联发科服务器芯片,基于MTK7623N芯片的香蕉派 BPI-R2 ,智能通信物联网网关开发平台...
  4. 在线聊天 java_java写在线聊天
  5. 计算机文档排版软件,正式文档排版格式计算机软件及应用IT计算机专业资料-正式文档排.pdf...
  6. 喇叭天线的增益计算matlab源代码,(18—26.5)GHz喇叭天线近场增益的计算-易迪拓培训.PDF...
  7. CentOS 5.3启动后报错——ata1.00: error
  8. p8z java快速命名工具
  9. sketchup 图片转模型_不用CAD描图迅速将图片转换成su模型
  10. FZUOJ 2150 Fire Game