Linux——匿名管道的详解

本文详细介绍了Linux匿名管道,包括其本质、创建方法、数据拷贝过程、数据生命周期、存储容量以及非阻塞属性的设置。通过实例解析,阐述了管道在进程间通信中的作用和特性。

1.进程间通信的概念

什么是进程间通信?

进程间通信就是在不同进程之间传播或交换信息,其思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。由于这种方式具有单向传递数据的特点,所以这个作为传递消息的共享文件就叫做“管道”。

为什么要进程间通信?

每一个进程都拥有自己独立的虚拟地址空间,造成了进程的独立性,进程通信的出现为了各个进程之间更好交换数据和控制。

2.匿名管道

2.1匿名管道的本质

-管道的本质就是在内核中开辟的一段空间,这段空间也可以称之为缓冲区,并且这段空间是没有标识符

2.2匿名管道的创建方法及其数据拷贝过程

int pipe(int fd[2])

  • fd【2】作为一个数组,包含fd【0】和fd【1】,记录着两个文件描述符。其中的值是pipe函数赋予的,fd【0】作为读端,fd【1】作为写端。(1写进,0读出)
  • 返回值:成功返回0,失败返回错误代码

在这里插入图片描述
查看fd【0】和fd【1】的值:


  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5     int fd[2];
  6     int ret =pipe(fd);
  7     if(ret<0)
  8     {
  9         perror("pipe");
 10         return -1;
 11     }
 12     printf("fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);
 13
 18     return 0;
 19 }

运行结果:根据文件描述符的知识不难理解,打开一个文件时候默认会有0,1,2三个文件描述符,根据最小位未占用法,得出fd【0】=3,fd【4】=4
在这里插入图片描述

在这里插入图片描述

让我们来写个小例子来用用匿名管道:

  1 #include<fcntl.h>
  2 #include<string.h>
  3 #include<stdio.h>
  4 #include<unistd.h>
  5 int main()
  6 {
  7     int fd[2];
  8     int ret =pipe(fd);
  9     if(ret<0)
 10     {
 11         perror("pipe");
 12         return -1;
 13     }
 14     printf("fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);
 15     write(fd[1],"linux",5);
 16     char buf[1024]={0};
 17     read(fd[0],buf,5);
 18      printf("%s\n",buf);
 19     return 0;
 20 }

数据的拷贝过程——管道需要在内核和用户空间进行四次的数据拷贝:由用户空间的buf中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buf。
输出:linux
在这里插入图片描述

2.3管道中数据的生命周期

从2.2中我们知道数据从fd【1】中写入并从fd【0】读出,那么管道中被读出的数据“linux”还在管道中吗?
方法很简答,调用两次read函数,若第二次无法读取“linux”,则数据被拷走了,不在管道中。

  1 #include<fcntl.h>
  2 #include<string.h>
  3 #include<stdio.h>
  4 #include<unistd.h>
  5 int main()
  6 {
  7     int fd[2];
  8     int ret =pipe(fd);
  9     if(ret<0)
 10     {
 11         perror("pipe");
 12         return -1;
 13     }
 14     printf("fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);
 15     write(fd[1],"linux",5);
 16     char buf[1024]={0};
 17     read(fd[0],buf,5);
 18     printf("第一次read:%s\n",buf);
 19     read(fd[0],buf,5);
 20      printf("第二次read:%s\n",buf);
 21     while(1)
 22     {
 23         sleep(1);
 24     }
 25 
 26     return 0;
 27 }

在这里插入图片描述
只有第一次read函数调用了,因此“linux”是被拷走了!

让我们调用pstack看看函数阻塞在哪了:
在这里插入图片描述
在堆栈中,函数先进后出,因此这个read函数是第二个函数,也就说明程序阻塞在第二个read函数了。

2.4管道的存储数据的容量

pipe_size最大存储64k大小的数据
pipe_buf 大小为4k

pipe_buf保证了写入操作的原子性
举个例子:进程A想将“linux”写入管道,假设其超过4k,进程B想将“python”写入管道,也超过4k,当进程A刚写入“lin”时间片结束,操作被打断,进程B开始执行。此时进程A并未完整将“linux”写入管道中去。而pipe_buf就是为了防止这样的情况出现。

2.5将管道写端和读端分别设置为非阻塞属性

1.将写端描述符设置为非阻塞属性属性

  • 1.1读端文件都关闭,调用write的进程会收到管道破裂信号SIGPIPE,系统会将调用write的进程退出
  • 1.2读端文件不关闭,不读,写端一只写,直到将管道写满,write函数将报错资源不可用,返回-1,

2.将读端描述符设置为非阻塞属性属性

  • 1.1写端文件都关闭,读端将管道内内容读完后返回0,表示没读到内容
  • 1.2写端文件不关闭,不写,读端一只读,read函数将报错资源不可用,返回-1

3.将读端描述符同时设置为阻塞属性属性

  • 3.1先运行写进程(管道内容写满时被阻塞),再运行读进程,一切正常。
  • 3.2先运行读进程(管道内容为空被阻塞),再运行写进程,一切正常

4.将读端描述符同时设置为非阻塞属性属性

  • 1和2中报错问题各取一半

结合其他的理解看看:

1.如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次 read 会返回0,就像读到文件末尾一样。

2.如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次 read 会阻塞,直到管道中有数据可读了才读取数据并返回。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值