头脑导图
学习内容
进程间通讯的一些知识点:是什么、为什么和怎么办??之后就是理解管道中的匿名管道的一些知识点:会创建匿名管道、匿名管道的四种环境……最后,就是进程池的代码编写,也是最难的一部门。
学习目标
过上面的学习目标,我们可以列出要学习的内容:
- 进程间通讯是什么
- 进程间通讯为什么
- 进程间通讯怎么办
- 匿名管道是什么
- 匿名管道如何进行创建
- 匿名管道的四种环境
- 管道的五大特征
- 进程池的编写
一、进程间通讯
1.1 进程间通讯的概念
每个进程各自有不同的用户地点空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要互换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通讯(IPC,InterProcess Communication)
一些易错的知识点:
- 进程之间具有独立性,拥有自己的虚拟地点空间,因此无法通过各自的虚拟地点进行通讯(A的地点经过B的页表映射不一定映射在什么位置)
- 进程间的通讯除了内核中的缓冲区之外另有文件以及网络通讯的方式可以实现
1.2 进程间通讯的作用
- 数据传输:一个进程需要将它的数据发送给另一个进程。
- 通知变乱:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种变乱(如进程终止时要通知父进程)。
- 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
- 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
1.3 进程间通讯的本质(如何进行通讯)
进程间通讯的本质是:让不同的进程看到同一份资源。进程是具有独立性的,我们每一个进程不能看到其他进程的代码和数据,进程 = 内核数据结构 + 代码 + 数据。因此,我们需要借助第三方资源实现进程间的通讯。
进程间的通讯本钱较高,我们需要先将某一个进程需要通讯,在OS操作系统中创建一个共享资源,OS操作系统必须提供许多的系统调用,由于OS操作系统创建的共享资源的不同,函数调用的不同,进程间通讯会有不同的类型。让不同的进程看到同一份(操作系统)资源(内存)。
总结:
- 进程间通讯,本钱大概会稍微高一点
- 进程间通讯的前提是:让不同的进程,(操作系统)看到同一份资源(内存)
1.4 进程间为什么要进行通讯??
进程之间是需要进行某种协同的,而协同的前提条件是通讯。通讯时的数据是有类别的:通知就绪的数据,控制相干信息的数据,单纯地通报给我的数据……
事实上:进程是具有独立性的,在进程间通讯的本质中说到:进程间通讯的本质是什么?以及进程 = 内核数据结构 + 代码 + 数据。
协同的意思:
协同是指和谐的两个大概两个以上的不同资源的个体协同同等地完成某个任务的过程或本领。
二、管道
2.1 什么是管道
一说到管道,我们大概想到的是下水道的管道,但是这里讲的管道是计算机中的管道,在详细一点,是Linux操作系统中的管道,那么管道到底是什么呢??
管道技能还是比较重要的,我们每天都可以用到。在计算机中,由于进程之间是相互独立的,信息无法进行交互。而计算机中的管道,就是一种解决进程间信息交互的本领。
2.2 匿名管道
为什么叫匿名管道呢??由于不需要文件路径和文件名。
2.2.1 解释一种现象,为什么父子进程向同一个显示器终端打印数据??
进程会默认打开三种标准输入和输出,0,1,2 三个文件描述符代表的是标准输入端,标准输出端和标准错误流。我们可以通过bash来进行理解,bash如果打开了3端口,那么bash其所有的子进程都会默认打开3端口。
2.2.2 匿名管道的工作原理
匿名管道仅用于具有血缘关系的进程之间的通讯,只允许单向通讯。
为什么管道要单向通讯??? 由于简朴。
进程间通讯的本质是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通讯的原理是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或读取操作,进而实现父子进程间的通讯。
理解一种现象:为什么父子进程会向同一个显示器打印数据???
进程默认会打开三个标准输入输出流:0,1,2。 bash打开,bash的子进程默认也就打开了,由于标准输入输出流都是bash的子进程,所以会默认打开。
为什么我们主动关闭子进程0,1,2文件描述符,不会影响父进程继续使用显示器文件??
由于struct file 是通过内存级的引用计数,在主动关闭子进程0,1,2文件描述符,会使struct file 的引用计数减一。当引用计数为0时,才会开释struct file。
注意:
- 这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。父子进程对于文件系统是浅拷贝。
- 管道虽然用的是文件的方案,但操作系同一定不会把进程进行通讯的数据革新到磁盘当中,由于如许做有IO参加会降低效率,而且也没有须要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是逐一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。
2.2.3 pipe函数
- #include <unisted.h>
- int pipe(int pipefd[2])
复制代码 pipe()函数的参数部门是一个巨细为2的数组类型的指针,该函数成功时返回0,并将一对打开的文件描述符值填入pipefd参数指向的数组。该函数失败时返回-1。
通过pipe函数创建的这两个文件描述符 fd[0] 和 fd[1] 分别构成管道的两头,往 fd[1] 写入的数据可以从 fd[0] 读出。并且 fd[1] 一端只能进行写操作,fd[0] 一端只能进行读操作,不能反过来使用。要实现双向数据传输,可以使用两个管道。
2.2.3.1 pipe函数的参数部门
pipe函数的参数是输出型参数,数组pipefd[2]表现的是对于一个管道的读端和写端的文件描述符。
数组元素 | 数组元素中代表的寄义 | pipefd[0] | 管道读端的文件描述符 | pipefd[1] | 管道写端的文件描述符 | 这里有一个小扩展:
2.2.3.2 pipe函数的返回部门
当pipe函数成功调用时返回 0,pipe函数调用失败后返回 -1。
2.2.4 匿名管道的使用步调
2.2.4.1 创建一个管道
一般都是结合fork()函数进行使用,由于匿名管道需要在创建子进程之前创建,由于只有如许才气复制到管道的操作句柄,与具有亲缘关系的进程实现访问同一个管道通讯。所以我们需要先在创建子进程之前,利用pipe()函数创建一个匿名管道。
2.2.4.2 创建一个子进程
利用fork()函数创建一个子进程,将子进程和父进程对应的读端大概写端关闭。由于匿名管道只支持半双工通讯,因此,匿名管道只能有一个读端和一个写端。将对应的读端或写端的文件描述符进行关闭。
2.2.4.3 子进程和父进程各自创建读端大概是写端
创建一个读端和一个写端的函数进行通讯即可。
下面来进行详细地先容过程:
在创建子进程之前将匿名管道创建完成。
创建子进程,将父子进程看到同一份文件缓冲区,进而进行通讯。
父进程关闭fd[0],子进程关闭fd[1]。
创建读端的相干操作。
利用read()函数来进行读出操作,注意read()函数的用法。
read()函数用于从文件描述符(通常是套接字、文件等)读取数据。
- #include <unisted.h>
- ssize_t read(int fd, void *buf, size_t count);
复制代码 read()函数的fd参数:
read()函数的buf参数:
read()函数的count参数:
read()函数的返回值为:
- 如果成功,返回读取字符的长度(大概为0,表现读到文件的末端)
- 如果时报,则返回-1,并设置errno表现读取失败的原因
创建写端的相干操作。
利用write()函数来进行写入操作,注意write()函数的用法。
wrtie()函数用于将数据写入文件描述符(通常是套接字、文件等)。
- #include <unisted.h>
- ssize_t write(size_t fd, void* buf, int count);
复制代码 write()函数的fd参数:
write()函数的buf参数:
write()函数的count参数:
write()函数的返回值:
- 如果成功,返回读取字符的长度(大概为0,表现读到文件的末端)
- 如果时报,则返回-1,并设置errno表现读取失败的原因
父子既然要关闭不需要的id,为什么刚开始要打开呢??可以不关闭吗??
为了使子进程继承下去。也可以不关闭不需要的id。文件描述符数组中的文件描述符是有限的,而且还会导致文件描述符泄漏,但是还是建议关闭,万一误写!!!!!
2.3 管道的四种环境
- 环境一:管道内部是空的 && write fd 未关闭,读取条件不具备,读进程会被壅闭,等待读取条件具备进行写入数据
- 环境二:管道被写满 && read fd 不读且没有关闭,写进程被壅闭,等待写条件具备进行读取数据
- 环境三:管道一直在读,写端关闭,读端会一直读到末端
- 环境四:读端直接关闭,写端一直在写,写端进程会被操作系统直接使用13号信号进行关闭,相当于程序出现异常
其中前面两种环境就能够很好的分析,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调和谐的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中另有空间,若是条件不满意,则相应的进程就会被挂起,直到条件满意后才会被再次唤醒。
第三种环境也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且今后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。
第四种环境也不难理解,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没故意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。
2.4 管道的五种特征
- 匿名管道:只能用于具有血缘关系的进程之间,可以共享一个文件
- 管道内部:自带进程之间同步的机制,多执行流执行代码的时间,具有显着的顺序性
- 文件的生命周期是随进程的
- 管道文件在通讯的时间,是面向字节流的,写入的次数与读入的次数不是逐一匹配的
- 管道中的通讯模式,是一种特殊的半双工模式
字节流,我们来做一个实行:子进程不断地向管道中输入数据,父进程不断地向管道吸收数据,我们可以看出,写入的次数与读入的次数不是逐一匹配的。
- // 子进程进行写入
- void subprocessWrite(int fd)
- {
- std::string message = "father, I am child";
- while (true)
- {
- // 不停地在往管道中打印数据
- std::cerr << "+++++++++++++++++++++++++++++" << std::endl;
- std::string info = message + getOtherMessage() + std::to_string(getpid());
- std::cerr << info << std::endl;
- write(fd, info.c_str(), info.size());
- sleep(1);
- }
- }
- // 父进程进行读取
- void fatherprocessread(int fd)
- {
- char inbuffer[size]; //
- while (true)
- {
- sleep(5); // 等待读取
- ssize_t n = read(fd, inbuffer, sizeof inbuffer);
- if(n > 0)
- {
- inbuffer[n] = '\0';
- std::cout << "get message:" << inbuffer << std::endl;
- sleep(1);
- }
- else if(n == 0)
- {
- std::cout << "client quit, father get return val:" << n << "father quit tool!" << std::endl;
- break;
- }
- else if(n < 0)
- {
- std::cout << "read error" << std::endl;
- break;
- }
- //break;
- }
- }
复制代码
2.5 管道的巨细
管道是一个文件,既然是一个文件,必然会有容量的巨细,那么管道的巨细是多少呢??当管道容量变满后,会进行壅闭。那么如何计算出管道的巨细呢?
2.5.1 使用man手册
根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面巨细相同,从Linux 2.6.11今后,管道的最大容量是65536字节。
2.5.2 使用ulimit下令
我们可以使用 ulimit -a 下令来检察当前资源限制的设定。
根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096512×8=4096 字节。
2.5.3 自行测试
我们可以将写端的文件描述符打开,一直进行写入操作,知道管道满为止。由图,我们可以知道我的管道的容量巨细是65536。
代码如下:
2.6 有关匿名管道的一些知识点
下令行中的 “|” ,就是本日学习的匿名管道:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |