【面试】 之 详解 进程间通信 !!

在这里插入图片描述

为什么要进程间通信

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。

但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC:Inter Processes Communication )。

进程间通信的目的

  • 数据传输:一个进程需要把它的数据传输给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另外一个进程或一组进程发送消息,通知他们发生了某种事情(例如进程终止要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如DEBUG进程),此时控制进程希望能够拦截另一个进程所有陷入和异常,并能够及时知道他们的状态改变。

管道

$ ps auxf | grep mysql

< | > 竖线就是管道,功能是将前一个命令的输出作为后一个命令的输入。可以看出,管道的数据传输是单向的,如果想互相通信,需要创建两个管道。

上述是匿名管道,使用完就要销毁

还有一个类型是命名管道,也被叫做FIFO,因为数据是先进先出的传输方式。

使用命名管道需要用mkfifo创建,并命名。

mkfifo mypipe

因为linux下一切皆文件,所以可以使用ls -l查看:
在这里插入图片描述
类型文件是P,即管道。

向管道中写入数据:

echo "hello" > mypipe

发现命令停止不动了,需要读取管道里面的数据,命令才可以退出。

cat < mypipe

从上面的例子可以看出,管道的通知机制类似于缓存,就像一个进程把数据放在某个缓存区域,然后等着另外一个进程去拿,并且是管道是单向传输的。

可以看到,管道这种通信方式效率较低,不适合频繁地交换数据。当然,他的优点是简单,同时也很容易得知管道里的数据被另一个进程读取了

管道创建的原理

匿名管道的创建,需要通过下面这个系统调用:

int pipe(int fd[2])

表示创建了一个匿名管道并返回了两个文件描述符。fd[0]表示管道的读取端描述符,fd[1]表示管道的写入端描述符。
匿名管道是特殊文件,不存在于文件系统,只存在于内存
在这里插入图片描述
其实,所谓的管道,就是内核里面的一串缓存。从管道的一端写入数据,一端读取数据,都是缓存在内核中的。传输的数据无格式的流且大小受限

管道跨进程实现:

fork创建子进程,创建的子进程会复制父进程的文件描述符,两个进程可以通过各自的fd写入和读取同一个管道文件实现跨进程通信。

为了避免父进程于子进程同时读与写造成混乱,做法是:

  • 父进程关闭读取端
  • 子进程关闭写入端

在这里插入图片描述
如果需要双向通信,则需要建立两个管道。

Shell中,执行A | B,其两个进程都是shell创建出来的子进程,A与B不存在父子关系,他俩的父进程都是Shell。
在这里插入图片描述
通过上述描述,可以看到,对于匿名管道,他的通信范围是存在父子关系的进程因为管道没有实体,也没有管道文件,只能通过fork来复制父进程的文件描述符,未达到通信的目的

对于命名管道,它可以在不相关的进程间通信。因为命名管道,提前创建了一个类型为管道的文件,在进程里只要使用这个设备文件,就可以互相通信。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

优缺点

这种通信方式有什么缺点呢?显然,这种通信方式效率低下,你看,a 进程给 b 进程传输数据,只能等待 b 进程取了数据之后 a 进程才能返回。

所以管道不适合频繁通信的进程。当然,他也有它的优点,例如比较简单,能够保证我们的数据已经真的被其他进程拿走了。我们平时用 Linux 的时候,也算是经常用。

消息队列

那我们能不能把进程的数据放在某个内存之后就马上让进程返回呢?无需等待其他进程来取就返回呢?

消息队列就解决了管道必须等待进程来取数据的问题。

A进程给B进程发送消息,A进程只要把消息放到对应的消息队列中就可以正常返回,B进程需要时再去读取就可以。相反也是如此。。

工作原理

消息队列是保存在内核的消息链表,发送数据时,会分成独立的消息体(数据块),消息的发送方与接受方要对发送的数据类型进行定义。如果进程从消息体中读取了数据,内核就会把这个消息体删除。

消息队列的生命周期是随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在。

优缺点

  • 消息队列不适合传输比较大的数据,因为再内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。
  • 消息队列通信中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态。读数据的时候,会从内核态拷贝数据到用户态。

共享内存

共享内存很好的解决了消息队列拷贝数据所消耗的时间。

系统会给每个进程分配一个独立的虚拟空间,不同的虚拟内存映射到不同的物理内存中,所以即便A与B的虚拟地址是一样,但是他们访问的是不同的物理地址,对于数据的增删改查互不影响。

共享内存的机制,是两个进程各拿出一块虚拟空间,映射到相同的物理内存中。这样一个进程写入东西的时候,另一个进程马上就看到了,不需要拷贝来拷贝去,提高了进程间通信的速度。

在这里插入图片描述

优缺点

  • 解决了消息队列数据拷贝的时间开销
  • 带了新的问题,如果两个进程同时修改同一个共享内存,很有可能就会发生冲突。
  • 例如,两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了

信号量

为了防止多进程竞争共享资源,造成数据错乱,所以需要保护机制使共享资源,在任意时刻只能被一个进程访问。信号量解决了这一问题

信号量其实是一个整型计数器,只要用于实现进程间的同步与互斥,而不是用于缓存进程间数据通信

工作机制

信号量表示资源的数量,控制信号量的方式有两种原子操作:

  • P操作,将信号量 - 1, 如果信号量 < 0,表示资源已经被占用,进程需阻塞等待; 如果 >= 0,表示还有资源可以使用,进程可以正常执行。
  • V操作, 将信号量 + 1, 如果信号量 <= 0,表示当前有进程在阻塞等待,需要将其唤醒;相加后 > 0 ,表示没有阻塞的进程。

P操作是在进入共享资源之前,V操作是离开共享资源,两个必须成对出现。

  1. 两个进程互斥访问共享内存,初始化信号量为1

在这里插入图片描述

  • 进程A在访问共享内存之前,先执行P操作,将初始信号量 - 1,信号量变为0,表示有资源可用,进程A就可以访问共享内存。
  • 若此时,B进程也想访问共享内存,执行P操作后,发现信号量变为 - 1,表示资源被占用,则阻塞等待。
  • 进程A访问完共享内存,执行V操作,信号量变为0,则表示当前有进程在阻塞等待,就会唤醒进程B,B进程就可以访问共享内存。
  • 进程B访问完共享内存执行V操作,此时信号量恢复为1.

可以发现,信号量为1就表示互斥信号量,可以保证共享内存任意时刻只有一个进程在访问,很好的保护了共享内存。

  1. 信号量实现多进程同步的方式,初始化信号量为0。

在多进程里,每个进程不一定顺序执行。他们基本是各自独立的、不可预知的速度向前推进,但又希望他们之间相互合作,实现一个共同的任务。

例如,进程A是负责生产数据,进程B负责读取数据,这两个进程是相互合作、相互以来的,进程A必须先生产数据,B才能读取数据。

在这里插入图片描述

  • 如果进程B比进程A先执行了,信号量就会变为 -1,表示进程A没有生产数据,B就阻塞等待。
  • 等A进程生产完数据,执行V操作,信号量变为0,表示进程B在阻塞状态。
  • 唤醒B进程,A进程已经生产完数据。B进程可以正常读取数据。

信号初始化为0,就表示同步信号量,可以保证A在B前执行。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页