1.什么是进程通信?
答:两个或多个进程实现数据层面的交互;但是因为进程的独立性,导致进程通信的成本较高;
2.为什么要通信?
答:多进程之间由协同的需求,所以通信;以下是通信要实现的目的:
2.1.基本数据:
2.2.发送命令:一个进程控制另一个进程
2.3.多进程之间实现协同
2.4.通知
2.6.通信是有成本的:因为是要打破进程的独立性
3.怎么通信?
3.1进程间通信的本质:必须让不同的进程看到同一份“资源”
3.2“资源”——特定形式的内存空间;
3.3这个资源谁提供?答:一般是操作系统提供;
3.4为什么不是我们两个进程中的一个提供呢?答:进程具有独立性;假设是由一个进程提供,这个资源应该属于谁?他不是共享资源,还是进程独有,因为破环了进程的独立性;OS是第三方空间,谁用谁申请
3.5进程访问这个空间,进行通信,本质就是访问操作系统;进程代表的就是用户;“资源”从创建、使用、释放——必须调用系统接口;
3.6操作系统提供资源,保障了进程通信,而且底层设计、接口设计、都要由操作系统独立设计;一般操作系统,会有一个独立的通信模块——隶属于文件系统——IPC通信模块(进程间通信的意思)
3.7进程间通信是由标准的;两套标准——system V 和 posix
3.8基于文件级别的通信方式——管道(此时的文件,只是代称,我可以往内存中写啊)
思想是正确的,但是效率并不高;管道是Linux(unix)最古老的进程通信方式;
3.9管道原理(自我理解):基础IO部分,缓冲区里的数据要写入磁盘文件,现在不写入磁盘,而是直接与子进程交互,把数据给子进程;并且通过引用计数原理(智能指针)控制着文件的打开和关闭,进行读写;(只能单向通信所以命名为管道)
3.10强烈建议如果单向通信,一定要关闭不必要的文件描述符,防止写错;
3.11 如果我要进行双向通信呢——答:建两个管道;
3.12 以上讲的是父子进程,如果没有任何关系,可以用以上讲的原理进行通信吗?答:不能,做不到;
3.13兄弟之间也可以用管道,进行通信;爷爷和孙子也可以进行通信——使用管道通信,必须得是具有血缘关系;常用于父子之间
3.14 以上所说的文件,是在缓冲区中进行的,并不是在真实的文件中交互的,所以这个管道叫做匿名管道!
4.接口
4.1open、close是磁盘级的接口,不能用他,而是专用的接口pipe(系统接口)
int pipe(int pipefd[2]);
//pipdfd[2] 输出型参数
//pipefd[0] 只读下标
//pipefd[1] 只写下标
4.2代码实现管道
// 实现系统间通信
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdlib> //stdlib.h C++风格的头文件写法
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
#defind N 2
#define NUM 1024
void Writer(int wfd)
{
string s = "hello,i am child!";
pid_t self = getpid();
int number = 0;
char buffer[NUM];
while (true)
{
//构建字符串
buffer[0] = 0; // 字符串清空;只是为了提醒阅读代码的人,我把这个数组当作字符串了
snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number);
//cout<<buffer<<endl;
//发送给父进程
write(wfd,buffer,strlen(buffer));
sleep(1);
}
}
void Reader(int rfd)
{
char buffer[NUM];
while(true)
{
buffer[0]=0;
ssize_t n=read(rfd,buffer,sizeof(buffer));
if(n>0)
{
buffer[n]=0; //0=='\0'
cout<<"father get a message["<<getpid()<<"]#"<<buffer<<endl;
}
}
}
int main()
{
int pipefd[N] = {0};
int n = pipe(pipefd); // 0下标是读;1下标是写
if (n < 0)
{
perror("pipe");
return 1;
}
// cout<<"pipefd[0]:"<<pipefd[0]<<",pipefd[1]:"<<pipefd[1]<<endl;
// 子进程写入,父进程读
pid_t id = fork();
if (id < 0)
return 2;
if (id == 0)
{
// 子进程
close(pipefd[0]);
Writer(pipefd[1]);
close(pipefd[1]);
exit(0);
}
close(pipefd[1]);
Reader(pipefd[1]);
pid_t rid = waitpid(id, nullptr, 0);
if (rid < 0)
return 3;
close(pipefd[0]);
return 0;
}
4.3根据以上代码可以得到管道的本质:将上层缓冲区的数据拷贝到系统缓冲区,再将数据拷贝给上层缓冲区;
4.4进程间通信的本质:需要让不同的进程,看到同一份资源——但是是多执行流共享的,难免会出现访问冲突的问题;
5.管道的特征:五点
5.1具有血缘关系的进程进行进程间通信;
5.2管道只能单向通信;
5.3父子进程是会进程协同的,同步与互斥的——保护管道文件的数据安全;
5.4管道是面向字节流的;
5.5管道是基于文件的,文件的生命周期是跟随进程的(父子进程退出,管道会被操作系统回收)
6.管道的4种情况:
6.0管道是有大小的,Linux版本2.6.11之前是4KB,之后是64KB;(为了保障传输数据的整体性,有序性,定义了一个pipe_buf 大小是4KB,当写入系统缓冲区的数据小于4KB时,必须要等子进程传输完数据,父进程才能读——这个工作就是读取的原子性问题)
6.1读写端正常,管道如果为空,读端就要阻塞;
6.2读写端正常,管道如果被写满,写端就要阻塞
6.3读端正常,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞;一直在读;
6.4读端关闭,写端正常,操作系统就要杀掉这个正在写入的进程;如何干掉?答:操作系统通过信号(是几号信号? 答:13号)干掉(操作系统是不会做低效,浪费等类似的工作的,如果做了,那就是操作系统的bug)
ulimit -a //查看系统对一些非常重要的资源的限制
7.管道的应用场景
7.1这个管道和我们以前学的知识,哪些是有关系的?
Linux shell 指令 :cat test.txt | head -10 |tail -5 这个语句的实现,就是shell命令+进程替换+管道实现的;
7.2使用管道实现一个简易版本的进程池;(请看下一节)