引言:
北京时间:2023/4/11/21:17,今天的文章更新啦!但是还是没有上热榜,所以我们需要继续更文啦!我相信下一篇博客肯定是可以上热榜的,加油!并且今天晚上因为有一节体育课,所以导致现在才开始码字,体育课上教我们打羽毛球,虽然我自从高考到现在已经快一年没打了,但是还是有点实力,但是实力有待恢复,运动其实是非常快乐的,哎!可惜,时间需要用来码字,连打球的时间都挤不出来了,所以让我们趁热打铁,来学习一下有关进程间通信之管道的知识吧!
深入进程间通信
为什么需要有进程间通信,从以前有关进程的知识,我们可以发现,我们以前学的都是单独一个进程有关的知识,无论是进程终止、进程创建,还是进程等待等知识,所以单独一个进程就像是一个个体,并且明白,个体和个体之间肯定是有一定的联系,所以进程和进程之间也应该存在一定的联系,这个联系的目的,就好比个体和个体之间,是为了促进某些方面的发展,所以进程和进程之间的联系,目的也是为了促进某些方面,例如,节省资源、提高效率等!所以进程间通信的目的就是为单独一个一个的进程之间的联系提供环境,使进程之间可以执行我们想要的一些操作,如:数据传输(一个进程将对应的数据传送给另一个进程)、资源共享(一个进程将资源共享给多个进程使用)、通知事件(一个进程通过系统调用接口参数,来告诉另一个进程有关的事件,如,子进程终止时,通过输出型参数,通知父进程)、进程控制(一个进程为了实时了解另一个进程的状态,此时就需要对该进程进行控制),但是注意: 进程间通信提供的联系环境,这个联系不仅仅涉及进程之间的,本质上都是操作系统来完成,具体如下述所说:
因为进程具有独立性,所以如果想要让两个进程之间能够看到同一份资源,此时要明白,进程和进程之间肯定完成不了这个操作,就需要操作系统来起中间作用,从而让一个进程和另一个进程可以看到同一份资源,最后实现进程间通信的基础条件,两个进程共享同一份资源(文件资源),所以进程间通信的本质不是让进程和进程之间通信,本质只是搭建一个进程和进程之间通信的环境而已,如下图所示:
从通信理解标准,再依据标准搞定通信
理解了上述有关进程间通信的知识之后,此时我们先摒弃进程,具体来谈谈通信二字,什么是通信,通信二字应该是上世纪非常重要的一个概念,它的提出在当时可能是跨时代的,简简单单从现在就能看出,人类已经离不开通信了,并且在这条道路上已经走得越来越远,从5G通信就可以看出,当今通信技术已经非常的成熟,通信方面的效率性、稳定性、安全性在5G通信上达到新的高度;为什么要有通信这种问题,我们就不谈了,我们重点谈谈为什么通信能够发展的如此迅速,从2G到3G到4G到如今的5G,日新月异,高速发展的背后是因为什么呢?此时就不得不引出跟通信紧紧挂钩的两个字,标准,什么是标准,想必大家都知道,不就是标准吗?哈哈哈!所以什么是标准,你脑袋直接混乱,标准不就是标准吗?所以按照日常生过来理解, 标准就是一个规定,一个规定,大家都必须遵循的准则和依据, 所以明白了这点,我们就知道了,通信能够发展如此迅速的原因,就是因为标准,大家按照标准执行,严格遵守标准,所以明白一个道理,任何事情想要成功,成气候,首先一定要有一个标准,所以通信能够如此成功的原因,就是因为它有一套属于自己的标准:通信标准,感兴趣的同学参考该链接:通信标准详解
了解了上述知识之后,此时我们就明白,任何事情都是存在一个标准的,无论是国家,还是社会,还是世界,只要有人存在,就必然有标准,这就是人类能够不断进步的第一原则,通信不例外,进程间通信更不例外,并且知道,进程间通信的标准主要是两种,一种是System进程间通信标准(本地通信), 一种是POSIX进程间通信标准(网络通信) ,感兴趣的同学参考该链接:进程间通信标准详解,并且明白,进程间通信的种类有多种,在不同的场景或者是环境之下,就有可能使用不同种类的进程间通信,但无论是那个种类的进程间通信,由于有进程间通信标准,所以当在不同的环境或者不同的场景下使用不同种类的进程间通信,进程间通信的实现都大致相同,这就是标准的意义好作用
如何理解管道
明白了上述知识之后,我们就来看看进程间通信最简单的一种,进程间通信之管道的知识
首先要明白,在Linux系统下,| 表示的就是 管道 ,并且当我们使用了管道之后,管道左右对应的指令(函数接口),最终都会变成进程,所以得出结论,你使用一个管道,就会同时生成两个进程,你使用两个管道,就会同时生成三个进程,类推,如下图所示:
抽象一个管道,两个进程之间的数据传输,如下图:
所以如上图可以看出,操作系统在进程间通信的时候,会帮我们创建一个管道,按照Linux系统一切皆文件来看,此时管道本质也就是一个文件,等同于操作系统在进行进程间通信的时候,会帮我们维护一个文件,并且凭借这个文件来实现让两个不同的进程共享一份"资源"的效果 ,这样操作系统就完成了进程间通信环境的搭建,从而让进程之间可以进行通信,那么具体如何通信呢?
有了之前和上述知识,理解如何通信这个问题就非常的简单,没什么多余的知识点和要求,本质上就是让一个进程(who进程)以写入的方式去打开管道文件,将数据拷贝到管道文件中,然后让另一个进程(wc -l)以读的方式打开管道文件,最终从管道文件中读取到数据,就这么简单多了的没有,唯一要注意的是,理解进程的默认输出和默认输入,如下:
注意:进程默认打印数据,是向标准输出打印,进程读取数据,默认是从标准输入读取,所以此时如果想要实现,让一个进程(who)将数据拷贝到管道文件,而不是标注输出文件,此时就需要将该进程(who)的标准输出重定向到管道文件当中,另一个进程(wc -l)将标准输入重定向到管道文件,只有这样,操作系统才默认,进程(who)是向管道文件中拷贝数据而不是标准输出,另一个进程(wc -l)是向管道文件读取数据,而不是标准输入
进一步理解标准输出和标准输入,如上述的更改对应进程的标准输出和标准输入到管道文件,本质上都是根据原理,为了让管道文件成为传送数据那个进程(who)的标准输出,表示的就是将被传送的数据不拷贝到显示器上,而是拷贝到管道文件中,而另一个进程(wc -l),此时则不从标准输入(键盘)中获取数据,而是从管道文件中获取(重定向了)
上述这个知识点,困扰了我很久,原因可能是因为进程和文件描述符表之间的关系没玩明白,当然上述知识点需要贯穿理解的知识是有的,所以越往后学,需要掌握和运用的知识就越多,那么知识和知识之间的理解成本就会越高,所以基础一定要牢!并且上述这个知识点,可以说是理解管道最为重要的一点,搞定了这个,别的知识都只是顺水推舟摆了!具体看下图:
如上图所示,此时由于具有血缘关系的进程之间它们的文件描述符表都是继承于父进程,所以导致它们指向的文件是同一文件,并且由于操作系统维护了一个匿名管道文件,所以此时两个进程它们就共享一个管道文件,从而实现了让两个进程看到同一份资源,共享同一份资源的效果,最后由于每个进程对该共享资源都具有读写的方式,所以此时两个进程之间就可以通过控制读写,进而控制进程之间的通信,例如,此时想要让子进程进行写入,父进程(也可能是兄弟进程)进行读取,此时要做的就是将子进程的读端关闭,父进程的写端关闭,具体是因为父进程和子进程对共享资源都具有读写的能力,但是由于管道只能进行单向传递(因为文件只有一个缓冲区),所以父进程和子进程不能对共享资源同时进行读写,只能你读我写或者是你写我读,只有满足这样的情况下,进程和进程之间才能成功完成通信(也就是该博客最开头的内容,进行资源共享,数据传递,通知事件,进程控制)
并且从上述的血缘进程之间共享同一个个文件描述符表,可以解释一个现象,这个现象就是当时我们学习进程创建时,看到的,为什么创建子进程之后,printf或者cout,都是向标准输出(显示器)打印数据,原因就是因为,血缘进程之间共享文件描述符表(继承)
进程间通信代码实现
通过上述知识,有关进程间为什么要通信,如何通信等原理,我们都搞定了,并且具体的图我们也见过了,此时就让我们来完成学习进程间通信的最后一个过程,自己实现一个进程间通信的代码,如下所示:
想要自己实现一个进程间通信的代码,如上图所示,原理已经非常的明显了,具体就是分为如下几个步骤
1. 创建管道
2. 创建子进程
3. 关闭不需要的fd
4. 开始通信
所以此时就让我们按照上述步骤来自己实现一下进程间通信的代码吧!
创建管道
此时想要创建管道,如上述知识所说一般,管道本质上就是一个文件,并且这个文件是一个内存级文件,是有内核创建,然后操作系统进行管理的一个文件,所以明白,这个文件我们肯定是不可能创建出来的,想要创建出这个内存级文件,就必须要调用系统调用接口,让操作系统通过内核帮我们创建,此时这个系统调用接口:pipe
在Linux系统下使用说明如下:
如上图,pipe的基本使用说明,我们可以看出如下信息:
pipe函数用于创建一个管道,以实现进程间的通信,pipe函数的定义如下: 头文件#include<unistd.h> 函数接口调用int pipe(int pipefd[2]); pipe函数定义中的pipefd参数是一个大小为2的一个数组类型的指针, 该函数成功时返回0,并将一对打开的文件描述符值填入pipefd参数指向的数组,失败时返回 -1并设置错误码(errno)
代码如下:
所以此时通过上述的代码,此时我们就构建出了,如下图所示的管道(本质就是:在一个进程中,打开两个文件,一个以读的方式打开,一个以写的方式打开)
要注意的是,此时pipe接口参数是一个输出型参数,目的就是为了获取到被打开文件的文件描述符,以便于我们后序构建单向通信时,将进程之间不需要的文件描述符关闭,并且注意:pipe接口的参数是一个数组指针,由于数组天生就是以地址的形式出现,所以此时pipe接口参数是一个地址,也就是一个输出型参数,改变了pipe接口中该数组的值,该接口外部,该数组也会跟着改变,此时又因为pipe接口调用结束之后,会将两个打开文件的文件描述符拷贝给该数组,所以最终该接口调用成功之后,此时该数组的0下标和1下标表示的就是被打开文件的文件描述符,但此时因为我们要构建单向通信(管道要求),所以此时当创建完子进程之后(fork),就需要将进程不需要的fd关闭,又因为两个进程的文件描述符表是完全相同的,所以我们就可默认此时该数组0下标是以读方式打开的文件描述符,1下标是写方式打开的文件描述符,然后假如,此时是子进程向父进程传送数据,那么此时就将子进程的0下标,也就是以读方式打开的文件关闭,再将父进程的1下标关闭,也就是以写方式打开的文件关闭(反正本质就是将两个进程以读写方式打开的文件关闭一个,但必须是不同的那一个),这样一个单向通信的信道我们就构建出来了,具体如下图和代码所示:
此时根据上述代码,我们就可以得到如下图所示进程间通信的环境搭建了,此时根据下图原理,上图代码,此时进程间就可以进行数据传输等行为了
如下是我们接下来会学习的知识,这里由于时间关系,下篇博客搞定
System V IPC(本地通信标准)
明白了上述知识之后,此时我们就来认识一下System V进程间通信标注,其中包括了进程间通信的消息队列、共享内存、信号量等标准的相关知识!
消息队列
具体先看该链接:1.消息队列知识详解 2.消息队列相关知识理解 3.消息队列互补知识理解
该三个链接文章,足以让我们搞懂什么是消息队列了
共享内存
信号量
POSIX IPC(网络通信标准)
消息队列、共享内存、信号量、互斥量、条件变量、读写锁
北京时间:2023/4/13/7:49,早八人!润