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 会阻塞,直到管道中有数据可读了才读取数据并返回。
本文详细介绍了Linux匿名管道,包括其本质、创建方法、数据拷贝过程、数据生命周期、存储容量以及非阻塞属性的设置。通过实例解析,阐述了管道在进程间通信中的作用和特性。

2396

被折叠的 条评论
为什么被折叠?



