进程间通信之管道篇

🏆一、进程间通信目的

1.1什么是通信

进程是具有独立性的,而我们要实现进程间通信的目标,是需要开辟空间和创造方法的。

通信目的:

1、数据传输:一个进程需要将它的数据发送给另一个进程

2、资源共享:多个进程之间共享相同的资源。

3、通知事件:一个进程需要向另一个或一组进程发送消息(例如进程终止要通知父进程)

4、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

那么管道就是十分传统的一种解决进程间通信的方式。

在学习进程间通信之前,我们就曾见识过管道的使用。

 

进程通信的原因:多进程协同完成某项任务

目前主要的进程间通信的方式有三种:POSIX(让通信过程可以跨主机) ,System V(聚焦在本地通信,主要有共享内存,消息队列,信号量),以及管道(依托于文件系统完成的一套技术方案)

🏆二、匿名管道

2.1匿名管道的由来

说了这么多,那么管道到底是什么?管道的底层原理是什么?我们如何创建和通过管道来实现进程间通信呢?

匿名管道是基于父子进程而衍生的一种解决方案

 父进程打开一个文件后,通过fork()创建子进程,子进程继承父进程文件描述符表,这样两个进程就指向了同一个文件。

父进程通过打开的这个文件的缓冲区写入,子进程再从缓冲区读取,这种过程就是一个进程向文件当中写数据,另一个进程从文件当中读数据。这样进程间就实现了数据传输,而这个过程就是进程间通信

这里还有一个问题,这个文件是否需要向磁盘写数据和读取数据呢?

答案是不需要!因为我们这个文件是供父子进程之间进行通信使用,而非存储到磁盘。而IO的过程是非常影响效率的。正因为它有这一特性也就意味着它和一般的文件是不同的。没错,它是由OS提供的内核级文件--->匿名管道。

管道文件和普通文件的区别:

2.2基于匿名管道再理解进程间通信

首先必须得有两个进程都能看到的部分,因为两个进程都能从公共部分写入或读取才能实现数据传输。又因为进程具有独立性,所以我们进程间要实现通信,必须由第三方提供缓冲区供进程间进行数据传输。如果由A进程提供缓冲区会因为进程的独立性导致A进程独有,B进程与之同理。所以第三方必然是操作系统(OS).

1、OS需要直接或间接给通信双方的进程提供内存空间。

2、要通信的进程必须看到一份公共的资源。

这一原理贯穿了所有的进程间通信实现方案:

所谓的不同的通信种类本质就是让进程间看到同一份资源,这份资源是OS哪一模块提供的,就是什么通信模式

2.3匿名管道的使用

从上图我们看到,父进程先创建管道文件,父进程有管道文件的读写端,然后fork创建子进程,这样由于子进程继承父进程的文件描述符表,所以子进程也有管道的读写端。然后父进程关闭读端/写端,子进程关闭写端/读端。通过管道就能实现单向进程通信

那么为什么父进程不关闭读端或写端后再fork()创建子进程呢?

因为父子进程关闭的读写端不一样,如果父进程关闭读端,那么子进程继承的也是关闭读端;而父进程需要读端是开启的(否则无法实现单向通信)。所以是创建完子进程后再关闭读端/写端。

 

OS提供的管道的接口,调用成功返回0.调用失败返回-1.

参数pipefd[2]是输出型参数,调用pipe接口的时候,OS以读和写的方式打开文件,并将读和写对应的文件描述符下标填到pipefd数组中。通过数组,把读端和写端对应的下标全部返回。就以读和写的方式打开了pipe文件。

演示:

 那么输出型参数返回的数组中fds[0]fds[1]谁是读取,谁是写入呢?

2.4演示使用匿名管道

 

我们如何验证我们的管道成功通信了呢?因为我们的子进程给父进程发的消息有自己的pid,而我们在父进程打印接收到的消息时,打印了父进程的pid。所以我们只需监控查看进程pid,与我们打印出的内容进行对照即可验证是否完成管道通信。

 

经验证,确实完成了进程间通信。

匿名管道的实际通信过程:

2.5匿名管道的几种情境下的特点

①写端慢,读端快

这句话是什么意思呢?这是一种场景,让写端输入慢(写端会sleep),而读端不设限制。比如写端(子进程)在sleep期间,父进程在做什么呢?

父进程在等待读取,首先read方法是一种阻塞式调用当写端没有写入信息时,读端在等待读取,而非执行循环

我们可以验证一下:将子进程(写端)代码改为在写入数据后,休眠50s

再来看父进程(读端)会有何表现:

 

我们可以看到,在读取过一次后,由于写端没有输入而是在sleep。读端就阻塞在read,等待读取。(如果是执行循环就应该打印"bbbbbbbbb"而非阻塞)

这里我们发现,如果管道中没有了数据,读端再读,默认会直接阻塞当前正在读取的进程!

② 写端快,读端慢

与上一种情境相反,我们让子进程循环写入,而父进程读取端sleep。

 发现会一瞬间写满pipe文件。写满后就不会再写了,此时写端就会被阻塞。

当写端快,读端慢的时候会写满管道文件,然后写端阻塞等待读端读取。

③写端先于读端关闭

如果我们写一条信息,子进程就关闭写端fd。作为读端(父进程),如果写端已经关闭,而读端已经全部读取管道文件中的数据,那么读端就会读到文件结尾。

 子进程(写端)关闭,父进程读到返回值0,读端读到写端关闭结果。

④读端先于写端关闭

如果读端关闭,那么写端写的数据将不会被接收,也就是说写端写的数据将没有任何意义。

那么此时OS会自动终止写端,会给写端进程发送信号,终止写端。

 

 我们看到子进程pid是32134,收到的终止信号是13号。那么13信号是什么意义呢?

此时我们可以总结匿名管道的四种情况和五大特征

四种情况:

1、写端慢,读端快。读端不会循环而是阻塞在read方法,等待写端写入数据读取。

2、读端慢,写端快。写端写满管道文件,就不再写入数据,等待读端读取数据。

3、写端先于读端关闭。读端会先把管道中的数据全部读取后,读取到写端退出返回的结果,从而关闭读端,不再读取。 

4、读端先于写端关闭。读端关闭,写端写入的数据不被接收,就没有了意义。OS会自动终止写端,会给写端进程发送信号,终止写端。

五大特征:

1、只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

2、管道是面向字节流的。

3、一般而言,进程退出,管道释放,所以管道的生命周期随进程。

4、内核会对管道操作进行同步和互斥。通俗点讲就是任何一个时刻,只允许一个进程向另一个进程发送消息。(涉及到加锁,等到多线程部分再说)

5、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。

🏆三、匿名管道的应用

在了解了匿名管道后,我们可以做一个小游戏。原理很简单:父进程通过管道给子进程发送一个命令操作码,子进程根据命令操作码执行对应的任务。为了提高效率我们要创建多个子进程,并且随机给每个子进程发送命令操作码,避免某个子进程执行过多任务。

上面即是原理图。

 

 上图就是我们简单实现的一个程序。

我们运行查看一下:

我们看到代码成功执行:父进程随机挑选一个任务,将其任务码(这里转换成指针数组的下标)发送给随机一个子进程,子进程接收到任务后,执行对应的操作!

而我们实现的代码,复用性很强,耦合度低,想执行其他任务,只需改动实现函数即可。

但是我们写的代码有一个很深的bug

父进程打开的文件,是会被子进程共享的!

 

 

如何解决这个问题呢?除了我们之前的暴力关闭所有的写端。我们还可以保存fork() 继承下来的没必要的写端到一个vector容器中,每次对子进程操作时,关闭继承下来的写端:

 🏆四、命名管道

4.1命名管道

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道命名管道是一种特殊类型的文件。

mkfifo是创建管道文件的命令。

这里以p开头的文件叫做管道文件它的作用在于使两个不相关的进程一个可以向它写入,一个向它读取,直接完成进程间通信

 

我们看到确实写入了数据,但是为什么管道文件的大小是0呢?这是因为它不把数据刷新到磁盘上,所以看到的是0.

我们已经有一份公共的资源以供读取,匿名管道是通过继承的方式让父子进程看到同一个文件, 那么命名管道是如何做到让不同的进程看到同一份资源呢?

只需让不同的进程打开指定路径(路径+文件名)的同一个文件就可以了

4.2命名管道的使用

上面是指令级别的使用,我们编写代码时,操作系统提供有专门的接口。

 

我们运行一下上述代码:

 

成功创建管道文件。

那么我们不想要这个管道文件时,可以通过unlink接口。

 

 这段代码是由client通过命名管道named_pipeserver写数据。

但是我们还需要注意几个细节:

1、当server运行打开命名管道,它会等待client同样打开相同管道文件后再向后运行。

 

 2、为什么server端会多出一个换行符呢?

这是因为我们的回车键也被读取了!

我们需要手动去除:

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

·匿名管道由pipe函数创建并打开。

·命名管道由mkfifo函数创建,打开用open

·FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在他们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

·命名管道使用的前提是双方必须都打开它(一个以读的形式,一个以写的形式),否则就会阻塞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值