【Linux】进程间通信(一)---- 匿名管道
- 一.序
- 1什么是进程间通信
- 2.进程间通信的标准
- 3.为什么需要进程通信
- 二.匿名管道
- 1.原理
- 2.使用
- 3.四种情况
- 4.五个特点
一.序
1什么是进程间通信
进程间通信
通信我们大致知道是啥,就是互相传递信息
那进程间通信,就是进程间相互传递信息了
我们知道信息就是就是数据
我们都知道进程具有独立性
所以我们想要让进程中的一个,将信息直接传递给另一个进程。
虽然从理论上讲确实可以,但这样无疑是破坏了进程的独立性
所以操作系统会给在内存中开辟一个公共空间
让这两个进程来进行通信
但是我们想想
进程访问这个空间,进行通信,本质就是访问操作系统
进程对于系统来说就是用户,因为进程是用户来控制的
我们曾经多次提到:操作系统是不会信任用户的
所以操作系统从开辟公共内存到进程的相互通信,都是通过调用系统自己设计的接口来进行的
所以:
一般操作系统会有一个独立的通信模块,隶属于文件系统,叫做IPC通信模块
2.进程间通信的标准
我们知道在计算机行业中,**标准是很重要的,**就像5G的行业标准是华为制定的。
所以为了让不同计算机的不同操作系统的不同文件系统,都能进行通信,所以进程间的通信标准是很重要的。
进程间通信的定制标准:system V
和 posix
3.为什么需要进程通信
1.基本数据
2.发送命令
3.某种协同
4.通知
基于这几种需求,所以创造出了进程通信
其实以前进程间是不会进行通信的
但是人们发现进程通信还挺有必要的
所以进程间通信的产生是历史的必然
二.匿名管道
这个匿名管道就是进程间通信的一种方式,但是也有局限
现在先为大伙带来原理,再带大家来使用
1.原理
这里就得用到博主曾经在FD的图了
这个是以前在讲文件FD时用到的图
那如果我们这个时候fork一个子进程会怎么样呢
我们知道子进程fork的时候,子进程会获得与父进程相同的虚拟地址空间的副本,使子进程拥有与父进程相同的代码、数据和堆栈。
这个时候看看上面的图:
我们知道这个struct task和struct files_struct
所以他们也会被子进程进行复制
我们知道左边的部分是这样被子进程复制的
那么问题来了,右边的打开的文件需不需要被复制呢?
这个答案肯定是否定的,因为右边两个被子进程复制走
是因为他们都是父进程的一部分,但是打开的文件就不一样了
因为打开的文件和进程是平级的,并不是与进程的一部分
所以是这样的
所以,利用这个特性,我们就能来创建我们的匿名管道了
我们打开一个buffer缓冲区(实际上就是一个内存级文件)
这个时候如果fork一下呢?
就会变成这样
这个时候其实我们就能发现了,这两个进程已经能进行双向通信了
但是看看我们这个叫啥:
匿名管道,管道两个字就注定了,它只进不出
因为如果子进程和父进程都同时进行读和写的话
谁能保证父进程写完,会被父进程读走呢?
所以为了确保通信的稳定性和便于实现性
所以这里就要:
关掉父进程的读或者子进程的写
或者关掉父进程的写或者子进程的读
让它变成单向通信。
这里就是匿名管道的原理了
2.使用
这里我们已经把管道的原理给讲明白了
接下来就是使用了
之前提过:操作系统是不会信任用户的
所以操作系统从开辟公共内存到进程的相互通信,都是通过调用系统自己设计的接口来进行的
所以这里就要解释接口了:
int pipe(int pipefd[2]);
pipe是起这个作用的
接下来就带大伙来试一下匿名管道通信
#include<unistd.h>
#include<iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cerrno>
using namespace std;
void child_write(int pipefd[2])
{const char * buffer="my dad";int cnt=2;while(cnt--){write(pipefd[1],buffer,sizeof(buffer));cout<<"write success"<<endl;}}
void father_read(int pipefd[2])
{char buffer[1024];while (true){ssize_t bytesRead = read(pipefd[0], buffer, sizeof(buffer) - 1); // 留出一个字节存放字符串结束符if (bytesRead == 0)break;buffer[bytesRead] = '\0'; // 手动添加字符串结束符cout << buffer << endl;}
}
int main()
{int pipefd[2];if(pipe(pipefd)==-1){perror("pipe失败");return 0;}pid_t id=fork();if (id < 0) {perror("fork");return 1;} // 子进程else if(id == 0) {//这里就实现子进程写,父进程读把close(pipefd[0]);//子进程读child_write(pipefd);//写完,关闭通道close(pipefd[1]);} // 父进程else {close(pipefd[1]);//父进程写father_read(pipefd);}//读完,关闭通道close(pipefd[0]);//回收子进程int status;if(wait(&status)>0)cout<<"wait success\n"<<endl;}
这样就完成了通信
3.四种情况
管道的四种情况:
1.读写端正常,管道如果为空,读端就要阻塞
管道没有接受到数据,读端就会一直等待数据
2.读写端正常,管道如果满,写端就要阻塞
管道满了,读端没有读数据,数据就堵在管道了,写端无法继续写
3.写端退出,读端正常,读端读到0,不会阻塞
这是因为读端正常,写端没数据了,这个时候代表是正常读写完成。
4.读端退出,写端正常,会把写端关闭
因为操作系统不让做一些浪费性能的事情,已经没有人读取你的数据了,那你就没有继续写的必要了,系统会自己把写端关闭
4.五个特点
匿名管道的五个特点;
1.管道本质是内存级文件,寿命周期是进程
这个是肯定的,管道本来就是为了通信存在的,父子进程通信完毕,自然也就关闭了
2.管道只能单向通信
这个最前面我们也提到过
3.具有血缘关系(常见的是父子)的进行进程间通信
因为我们利用的就是父子进程fork后复制files struct的原理
其实爷孙进程也可以用,只不过常见的是用在父子而已
4.管道是面向字节流的
这个还不太好解释,之后的博客会有解释
5.父子进程会进程协同,同步与互斥的
这个也不太好解释,之后博客也会进行解释