进程间的通信

Source

文章目录

1.进程间通信的目的

进程之间具有独立性,这是普遍的性质,如果进程要发生通信,则代表需要进行数据的交互,即进程的数据由毫不相关,变成了有可能相关,一些独立的数据也有可能变成了共享的。
数据传输:一个进程需要将它的数据发送给另外一个进程
在这里插入图片描述
资源共享:多个进程之间共享同样的资源

通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(比如进程终止要通知父进程)
假设要对一组数据进行处理,一个进程负责先排序,另外一个进程负责进行数据的分类,当排序的进程完成之后
通知分类的进程进行分类,这就是通知事件

进程控制:有些进程希望完全能够控制另外一个进程的执行(如debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并且能够及时知道它的状态改变
比如有父子进程,父进程输入一个指令控制子进程做出相对的反应,这就是一个进程控制另外一个进程

2.进程间通信发展

管道

System V进程间通信
POSIX 进程间通信

这两种是通信标准,进程的通信必须得严格的按照标准,只有按照标准才能实现不同进程之间的交互顺利的进行

3.进程间通信分类

管道
匿名管道
命名管道

System V IPC
System V消息队列
System V共享内存
System V信号量

POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁

4.管道

管道是实现进程间通信的一种方式,进程之间通信的本质就是让不同的进程看到同一份资源

4.1管道原理

管道是Unix中最古老的进程间通信的方式
我们把从一个进程连接到另一个进程的一个数据流称为一个管道
在这里插入图片描述
我们的子进程都是通过父进程产生的,子进程的创建是以父进程为模板,即拷贝父进程的PCB、file_struct、页表等等内容,由于拷贝了file_struct,因此父子进程可以通过文件描述符看到同一份资源(同一个文件),看到同一份资源即可以完成进程之间的数据交互,实现进程之间的通信。
这种通过能看到同一份文件来实现进程通信的方式就叫做管道通信,这个文件就是我们的管道

4.1.1系统层面理解管道

在这里插入图片描述

4.1.2为什么每个进程都会默认打开三个文件

上图很好的解释了,为什么我们创建的进程没有打开0,1,2文件描述符对应的文件,系统却默认帮我们打开了,这是因为我们的进程有一个共同的祖先,而这个祖先是默认打开了0,1,2号文件描述符对应的标准输入,标准输出,标准错误文件的。
而在创建文件的时候,子进程会拷贝父进程的files_struct,因此就将对应的映射关系给拷贝过来了(一个文件可以被多个文件打开),因此我们的进程默认是打开0,1,2这三个文件的

4.1.3struct file为什么不需要拷贝

一个新的进程的创建,需要给进程创建PCB、页表、虚拟地址空间等等,因为进程是具有独立性的
而struct file是描述文件的数据结构,即这个数据结构和进程的关系是访问与被访问的关系,并不属于进程,我们的进程只是可以通过文件描述符找到对应的struct file,然后读取struct file中的数据信息,找到进程想要操作的文件。因此struct file并不会被拷贝创建一份

4.1.4文件也有自己的内存区

一个文件也是有自己的内存(缓冲)区域的,并不是直接往硬盘上写入,而是先写到内存之中,再由操作系统刷新至硬盘之中;
因此,一个进程往文件之中写入内容,这个内容会被保存至内存之中,而另外一个进程同时打开了这个文件,因此是可以看到同一份资源的

4.2匿名管道

管道只能够进行单向通信,并且需要进程之间具有亲缘关系,常常用于父子进程之间

4.2.1匿名管道的创建命令

pipe接口,如果调用从成功返回0,调用失败返回-1
接口的参数是输出型参数,对应的内容pipefd[0]对应的是文件描述符3,对文件是读的方式
pipefd[1]对应的文件描述符是4,对文件是写的方式
在这里插入图片描述

4.2.2匿名管道的实现过程

4.2.2.1注意陷阱

很多人就会开始想,为什么不直接定义一个全局变量缓冲区,父进程往里面写,子进程读,这不就实现通信了吗?
我们的进程之间是具有独立性的,独立性体面在了数据的独立性。
开始阶段父子进程看到的数据的确是一样的,如果对齐进行了修改,那么就会发生写时拷贝,此时各自进程地址空间对应的数据映射关系就就会发生改变,即一个进程数据的改变不会影响另外一个进程

4.2.2.2图解分析管道创建过程

在这里插入图片描述

4.2.2.3代码实现

在这里插入图片描述

4.2.3匿名管道代码层面的特性

4.2.3.1如果写端,不关闭文件描述符,且长时间不写入,那么读端可能(管道内有历史数据就会先读取完)长时间阻塞

在这里插入图片描述

4.2.3.2当我们在实际写入时,如果写入条件不满足,那么写入端就要被阻塞

在这里插入图片描述

4.2.3.3如果写入端关闭文件描述符,读取端就会读取到文件末尾,read返回0值

在这里插入图片描述

4.2.3.4如果读取端关闭,写入端进程被OS直接杀掉(OS系统统筹管理资源,发现有浪费进程的事件或者进程就会终止它)

在这里插入图片描述

4.2.4匿名管道的特点

1.只能用于具有亲缘关系的进程之间的通信,通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程可以使用该管道了
在这里插入图片描述
2.提供流式服务
在这里插入图片描述
3.一般而言,进程退出,管道释放,所以管道的生命周期随进程
一个进程打开一个文件,进程退出了,文件也会被关闭
我们的管道也是文件,进程退出,管道生命周期结束,所以说管道的生命周期随进程

4.一般而言,内核会对管道操作进行同步与互斥
同步:比如,写完才读,一慢都慢
互斥:读与写不同步

写端往管道写的慢,即使我们没有对读取端限速,我们读取的时候也会读的慢,说明了OS对管道的任何读写操作进行了优化,即管道中的读写操作是互斥的。
互斥是保护数据的安全,如果没有互斥,这边还没写完,那边已经读取完毕,就造成了数据的不完整
在这里插入图片描述
5.管道是半双工的,数据只能从一个方向,往另外一个方向流动,如果需要双方通信,则需要建立起两个管道

4.3命名管道

4.3.1命名管道原理

匿名管道使用是有限制的,即必须拥有血缘关系,当我们的进程之间没有关系的时候,怎么进行数据的交换呢?这时候我们可以使用命名管道来不同进程之间的数据的交互;

实现进程间的通信,首先得让它们看到同一份文件,匿名管道是亲缘关系继承拷贝属性信息,得到相同的文件描述符
命名管道是让两个进程打开同一个文件,文件是用文件路径加文件名做标识的,因此它们通过表示打开同一个文件

命名管道的本质是两个进程打开同一个文件进行操作,完成资源的共享。而我们的文件在内存上是有缓冲区的,这就为我们了的命名管道提供了依据,我们可以使用FIOF文件来做这项工作,它就被称为命名管道

命名管道是一种特殊类型的文件
在这里插入图片描述

4.3.2命名管道创建命令

在这里插入图片描述

4.3.3代码实现

服务器端(读取端):server
在这里插入图片描述
客户端(写入端):
在这里插入图片描述
在这里插入图片描述

4.3.4命令行指令创建命名管道

mkfifo即是一个调用接口,就是一个命令行指令,和我们的write、read都是一样的
在这里插入图片描述

4.3.5命名管道和匿名管道的区别

1.命名管道是mkfifo创建,open打开的,匿名管道是pipe创建并且打开的
2.匿名管道只可在具有亲缘关系的进程之间使用
3.创建和打开的方式不同,在创建、打开完成后他们的语义是相同的

5.system V共享内存

5.1共享内存原理

在这里插入图片描述
在这里插入图片描述

5.2共享内存数据结构

管道一般支持两个进程进行通信,而共享内存是一块内存区域,只需要通过用户级页表,将不同的进程的虚拟地址空间中的共享区域,和内存共享区域映射连接起来即可完成通信

共享内存在创建的时候一定是OS提供的,进程间通信的本质是让进程看到同一份资源,共享内存中的资源是由OS提供的,管道中的共享资源是文件系统提供的,本质也是OS提供的

即进程间通信的所有资源都是OS提供的,无非就是通过OS的文件部分还是OS的进程管理提供的

共享内存是一种通信的方式,不同进程之间进行通信需要OS来分配内存资源,那么内存怎么分配,内存映射链接的进程,这些都需要进行管理,因此也需要数据结构来描述共享内存

在这里插入图片描述
在这里插入图片描述

5.3共享内存操作

5.3.1共享内存的创建

5.3.1.1 ftok

ftok用来生成一个key值,用来作为共享内存的唯一标识
在这里插入图片描述

5.3.1.2 shmget

用来创建共享内存
在这里插入图片描述

5.3.1.3代码实现

在这里插入图片描述

5.3.1.4 共享内存资源生命周期随内核

在这里插入图片描述

5.3.2共享内存资源查看和删除

在这里插入图片描述
在这里插入图片描述

5.3.3共享内存大小最好为4096整数倍

在这里插入图片描述

5.3.4共享内存删除

在这里插入图片描述
在这里插入图片描述

5.3.5共享内存挂接

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

5.3.6实现进程通信

共享内存,底层不提供任何同步与互斥机制,因此使用共享内存使进程完成通信需要其他技术做支持的,在IPC之中就叫做信号量
共享内存是通过地址,让我们看到同样的资源的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3.7总结

1.内存共享,只需要发生一次拷贝,速度是进程通信之中最快的
2.内存共享,不提供同步与互斥,需要信号量的辅助
3. 内存共享之中的key是OS之中共享内存的唯一标识,shmid是用户层面的定位标识
4. 共享内存挂接时,返回的是虚拟地址空间的地址,和malloc返回的地址是一样的

6.system V 消息队列

队列先进先出,OS提供了一个消息队列,一个进程将数据封装起来变成数据块放入队列,另外一个进程去队列之中拿去数据,这个队列是OS提供的
在这里插入图片描述

7.system V信号量

7.1信号量底层操作识别

在这里插入图片描述
在这里插入图片描述

7.2信号量作用

信号量主要用于同步与互斥,本质就是一把锁

互斥:
我们要让进程之间实现通信,就得让进程看到同一份资源,然而看到同一份资源就会引入操作资源的麻烦,导致数据不一致的问题
比如,谁先写,谁先读,我写的时候你只读了一半就会发生数据的丢失
这里就需要信号量来提供互斥(锁)的概念,规定任何一方在操作的时候,另外一方不能进行操作

打个比方,小孩子吃饭,如果一人一个碗是不会发生争抢的,如果两个小孩子使用一个碗就难免发生争吵了,这时候就需要家长来提供秩序,规定不能的打架,要和睦相处,家长的管制就是一种信号量

临界资源:
我们将多个进程看到的同一份资源叫做临界资源

临界区:
访问临界资源的代码就叫做临界区

锁:
多个进程访问资源的时候,真正需要加锁的地方是临界区,因为只有临界区才会去访问临界资源
system V 里面的锁就叫做信号量
锁的表现形式是代码之中:加锁/解锁代码

二元信号量:0/1 两态,实现互斥功能
申请锁叫做P操作 释放锁叫做V操作 信号量最常见的就是PV操作
在这里插入图片描述

多元信号量:描述临界资源中,资源数目的个数,本质是计数器

8.system v标准

从上述进程通信方式之中我们可以看到,不同的方式的接口调用都是类似的。
描述通信方式的结构体也是类似的,传用的参数也是类似的
这种就有大幅度程度上的相似性,就是一种标准