文章目录
受排版的限制,上面的思维导图显示的不是很完整。下面是思维导图源文件的获取方式:
思维导图获取
提取码: diid
问题:进程间通信是什么?
进程间通信就是 进程和进程 之间进行通信。
问题:进程间通信的目的?(为什么要有进程间通信)
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(如进程终止时会去通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦住另一个进程的所有陷入和异常,并能够及时知道它的状态改变
问题:进程间通信的分类?
管道:匿名管道pipe、命名管道
System V IPC:System V 消息队列、System V 共享内存、System V 信号量
POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
问题:进程间通信的本质是什么?
进程间通信的本质就是让不同的进程,看到同一份资源。
问题:怎么做到进程间通信?
ps: 由于进程运行时具有独立性,因此进程间通信时,一般要借助第三方(OS)资源 #OS要提供一段内存区域,该区域能被双方进程看到
通信的本质就是“数据的拷贝”
匿名管道pipe
匿名管道的使用
intpipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]代表read读端;fd[1]代表write写端;
返回值:
成功返回0;失败返回错误代码
演示
//父进程通过管道给子进程发消息#include<iostream>#include<unistd.h>#include<sys/wait.h>#include<sys/types.h>#include<cstring>usingnamespace std;intmain(){int fd[2];pipe(fd);//创建匿名管道
pid_t id =fork();if(id ==0){//子进程close(fd[1]);//关闭写端 char buffer[128];
ssize_t num =read(fd[0], buffer,sizeof(buffer));
buffer[num]='\0';//从文件中读取的字符串并不是以'\0'为结尾的
cout << buffer;}else{//父进程 close(fd[0]);//关闭读端 constchar* buffer ="this is father porcess!\n";write(fd[1], buffer,strlen(buffer));wait(NULL);}return0;}
匿名管道的原理
父进程先创建一个匿名管道,然后再创建子进程。在子进程创建的时候,子进程会拷贝父进程的task_struct和files_struct结构体,而他们的files_struct结构体中的文件描述符指向了同一个文件,这样子进程就能看到父进程所创建的匿名管道了。
Tip:
管道虽然使用的是文件的方案,其实OS并不会把数据刷新到磁盘当中 (匿名和命名都是),因为刷新到磁盘的话,会降低效率(有IO参与,效率肯定会下降)。*//刷新到磁盘也没有意义,2个进程只是借助文件实现通信,所以并不需要刷新到磁盘当中*
父子进程通过匿名管道通信的过程
首先,父进程先使用pipe函数创建一个管道,一个进程在创建管道的时候,默认是同时打开读端fd[0]和写端fd[1]的。
父进程调用fork函数,子进程在被创建时会拷贝父进程的task_struct和files_struct结构体,而在files_struct中有2个文件描述符已经指向了一个匿名管道,所以子进程也能看到这个管道。
为了实现父子间通过管道进行通信,必须让其中1个关闭读端,另一个关闭写端。
管道的特点
- 管道只能进行单向通信
- 管道是半双工通信 (数据在同一时刻只能向1个方向流动,想要进行双向通信需要建立2个管道)
- 管道的内部提供了互斥与同步的机制
- 管道的生命周期跟随进程,一般情况下,进程退出,管道就会被释放
- 匿名管道适合具有血缘关系的进程进行通信 (父子进程、兄弟进程)
- 管道提供流式服务
这里大概解释一下管道特点中的个别几点:
问题:什么是半双工通信?什么是全双工通信?
全双工:在同一时刻,发送数据 和 接收数据 能同时进行 #同一时刻能双向
半双工:在同一时刻,只能进行发送 或 接收 #同一时刻只能单向
问题:什么是同步与互斥机制?
这个概念我将会放在多线程博客当中去讲解,这里就大概的介绍一下。互斥就是同一时刻只能有一个进程/线程正在使用临界资源,同步就是在确保了临界资源安全的前提下,以特定的顺序让进程/线程去访问临界资源。
管道的读写规则
- 正常进行写和读时,当write写完,关闭该进程的写端fd[1]。此时,读端read函数的返回值为0
- 写端还没写完,读端就关闭了,此时写端进程会被OS杀掉 (收到SIGPIPE信号)
- 不write,一直read,由于写端接收不到数据,就会出于阻塞状态
- 不read,一直write,当管道满了之后,写端就会进入阻塞状态
总结:
读管道:
1. 管道中有数据,read返回实际读到的字节数
2. 管道中无数据:
(1) 管道写端全部被关闭,read返回0
(2) 写端没有全部被关闭,在read处阻塞等待
写管道:
1. 管道读端全部被关闭,进程异常终止 (OS发送SIGPIPE信号给写端)
2. 读端没有全部被关闭:
(1) 管道未满时,write继续写入数据
(2) 管道写满了,write阻塞
管道的大小
命名管道
由于匿名管道只能作用于具有共同祖先的进程间通信,然而大部分的进程实际上都是不相关的,所以我们引入了命名管道,命名管道可以让完全不相干的2个进程进行通信。命名管道虽然是打开一个目录下的文件,但是实际上也是在内存当中进行操作,并不会刷新到磁盘当中。
命名管道的使用
命令行中创建:
mkfifo filename
程序中创建:
intmkfifo(constchar* filename,mode_t mode);//包含于<sys/types.h> <sys/stat.h>
参数:
filename:创建出来的命名管道的名称
mode:命名管道权限的设置
演示
//实现client端给server端发消息//server.cpp#include<iostream>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>usingnamespace std;#defineFILE_NAME"myfifo"intmain(){mkfifo(FILE_NAME,0666);//mkfifo的第一参数是文件名,它会给你创建个以这个为名字的命名管道 int fd =open(FILE_NAME, O_RDONLY);char buffer[128];while(1){
ssize_t num =read(fd, buffer,sizeof(buffer));
buffer[s]=0;//下面的代码是server端接收client端的字符串的消息if(num <0){
cout <<"read error!"<< endl;break;}elseif(num ==0){
cout <<"End of File!"<< endl;break;}else{
cout << buffer << endl;}}return0;}//client.cpp#include<iostream>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>usingnamespace std;#defineFILE_NAME"myfifo"intmain(){int fd =open(FILE_NAME, O_WRONLY);constchar* msg ="hello server, this is client";int cnt =5;while(cnt--){write(fd, msg,strlen(msg));sleep(1);}sleep(100);return0;}
匿名管道 VS 命名管道
匿名管道与命名管道的唯一区别就在于 ----- 管道的打开方式不同
匿名管道由pipe函数创建,并在创建时打开。
命名管道由mkfifo函数创建,打开需要使用open
管道的应用
- 可以实现进程之间互相传递消息(字符串)
- 可以让一个进程帮助另一个进程执行指令 (另一个进程接收消息后,使用execl函数执行指令)
- 可以让另一个进程帮助进行计算
- 可以实现“文件的拷贝” //文件的上传、下载,实际上都是文件的拷贝
版权归原作者 wuqiongjin 所有, 如有侵权,请联系我们删除。