进程间通信-管道

1. 进程间通信介绍

1.1 进程间通信目的

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

1.2 进程间通信分类

在这里插入图片描述
我们知道:进程是具有独立性的。如果我们想让进程之间交互数据,我们需要进程通信。而通信之前,我们最关键的是让不同的进程看到同一份资源。其实这里我们要学习的不是如何通信。而是如何看到同一份资源。由于资源的不同,决定了不同种类的通信方式

2. 管道

2.1 什么是管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
在这里插入图片描述

2.2 站在文件描述符角度-深度理解管道

在这里插入图片描述
这是我们之前说的:一个进程打开文件的过程。现在我们想fork一下,那么它的子进程会拷贝哪些呢?答案是:子进程会拷贝父进程部分的PCB和struct file_struct,而不会拷贝struct file。而拷贝下来的子进程和父进程指向同一文件。
在这里插入图片描述
管道就是让写入的数据不在刷新到磁盘中,而是写入缓冲区中。所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

那么管道具有什么特点呢
在进程间通信中,管道是用来传输数据的,并且是单向的

2.2.1 具体通信的过程

在这里插入图片描述
这里首先父进程是用两个文件描述符来打开管道的读和写。然后fork出子进程后,子进程也会指向同一个资源。如果父进程传数据给子进程,那么父进程会关闭读接口,子进程关闭写接口。反之,父进程会关闭写接口,子进程关闭读接口

现在有如下几个问题:
为什么父进程要分别打开读和写?
为了让子进程继承,那么子进程就不需要打开了

为什么父子要关闭对应的读或写?
因为管道是单向通信的

2.3 匿名管道

那么我们如何一次创建两个文件描述符来进行读和写呢
系统提供了我们一个接口:
在这里插入图片描述

2.4 代码实现

1. 创建管道:
在这里插入图片描述
2. 创建子进程:
在这里插入图片描述
3. 实现单向的:
现在我们需要子进程来进行只读,父进程只需要写。那么我们需要关闭子进程的写和父进程的读。
现在有一个问题是:数组里那个下标放的是读,那个放的是写
规定:下标0是读端,下标1是写端
在这里插入图片描述
验证一下是否通信
在这里插入图片描述
我们在父进程里写入一些变化的信息,然后通过管道让子进程去读。
在这里插入图片描述
这里有一个问题:子进程怎么知道父进程结束了
原因是:管道里有一个引用计数,可以知道有多少文件指向自己。当父进程写完时就会关闭文件描述符,管道里的引用计数就会减1

大家有没有想过这样的一个问题:父进程还没写完,子进程读完了?
答案是:这种情况在管道里不会发生。我们看下面的例子:
在这里插入图片描述
运行情况如下:
在这里插入图片描述
我们可以发现,子进程并没有去执行第二种情况。而是一直等待父进程。

结论:当父进程没有写入数据的时候,子进程在等。当父进程写入数据后,子进程才能read到数据。子进程打印读取数据要以父进程为主。父进程和子进程读写的时候,是有一定顺序性的

在父子进程向显示器写入时,就没有这样顺序。因为它们缺乏访问控制。管道内部,是自带访问控制机制。

管道内部,没有数据,read就必须阻塞等待(等管道有数据)。
管道内部,如果数据被写满,writer就必须阻塞等待(等待管道中有空间)

那么在命令行中的 | 这个管道是什么呢
其实就是匿名管道
在这里插入图片描述
在这里插入图片描述
我们可以看出管道两边的命令的ppid是一样的,也就是说它们的父亲是一样的。它们的关系是兄弟。

它是过程如下
在这里插入图片描述
这是一个创建子进程的管道,当我们再创建一个进程时。
在这里插入图片描述
再创建一个子进程后,父进程的读写端都关闭,然后让这两个兄弟进程通信。

3. 进程控制

现在,我们想控制进程做事情,我们这样写:
在这里插入图片描述
这个和上面是一样的,创建管道和创建子进程。
在这里插入图片描述
这里我们写一个函数集合,为了让子进程去执行这些方法。
在这里插入图片描述
这里写了一个子进程去执行任务的代码。

那么这个(void)s是什么意思呢
原因:assert断言,是编译有效,在debug模式下存在,release 模式,断言就没有了,一旦断言没有了,s变量就是只被定义了,没有被使用。release模式中,可能会有warning。
在这里插入图片描述
这是父进程指派任务的过程,父进程给子进程派送10次。

运行结果如下:
在这里插入图片描述

那么我们需要控制一批子进程呢
在这里插入图片描述
现在父进程要给三个子进程安排不同的任务,我们有什么解决办法呢?
在这里插入图片描述
有几个进程,我们创建几个管道。如果我们想让进程1做某些任务,我们就往1管道里面写,如果我们想让进程2做某些任务,我们就往2管道里面写,依次类推。

代码实现:
在这里插入图片描述
我们这里添加了一个pair类型的结构和进程的个数。

那么第一步:我们需要创建processNum个进程。
在这里插入图片描述
第二步:父进程给哪个进程指派任务。
在这里插入图片描述
这里父进程做的事,让子进程和对应管道写端的结构体放进这个数组里。这样就能知道哪个进程对应哪个管道。
在这里插入图片描述
当所有的子进程创建成功后,我们就开始让父进程来派发任务。

派发任务的代码我们如何写呢
在这里插入图片描述

第三步:子进程去执行任务。
在这里插入图片描述
那么我们就要完成这个work函数:
在这里插入图片描述
子进程在对应的blockFd去读。

第四步:回收资源。
在这里插入图片描述
运行结果如下
在这里插入图片描述

4. 管道读写规则

当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。

如果所有管道写端对应的文件描述符被关闭,则read返回0。
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性

5. 管道特点

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信。通常,一个管道由一个进程创建,然后该进程调用fork,此后父,子进程之间就可应用该管道。
  2. 管道只能单向通信(内核实现决定的)。管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
    在这里插入图片描述
  3. 管道自带同步机制(pipe满,write等,pipe空,read等),也就是自带访问控制。
  4. 一般而言,进程退出,管道释放,所以管道的生命周期随进程。
  5. 管道是面向字节流的,先写的字符,一定是先被读取,没有格式边界,需要用户来定义区分内容的边界(比如:上面写的sizeof(uint32_t))。

6. 命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用命名管道

6.1 创建一个命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
在这里插入图片描述
在这里插入图片描述
命名管道也可以从程序里创建,相关函数有:
在这里插入图片描述
我们知道:进程间通信的本质是不同的进程看到同一份资源。匿名管道是通过子进程继承父进程的文件描述符表。命名管道是通过一个fifo文件,文件有路径,所有具有唯一性。所以通过路径我们可以找到同一份资源

6.2 代码实现

在这里插入图片描述
我们在这里创建两个文件,一个是客户端文件,一个是服务端文件。

首先,我们要让这两个进程看到同一份资源:
在这里插入图片描述
我们在这里创建了一个隐藏文件。在这个头文件里。然后我们让clientFifo文件和serverFifo文件都包含这个头文件,这样这两个进程都可以看到这个路径了。
在这里插入图片描述
这是makefile里面的代码,因为我们要形成两个可执行程序。

现在我们就需要创建管道文件,只需要一个进程创建管道文件,另外一个来使用这个就可以了。那么我们就在服务端创建这个管道文件:
在这里插入图片描述
我们先看一下运行结果:
在这里插入图片描述
从运行结果我们可以看到:创建了一个.fifo的文件。

现在我们想让clientFifo进程去写入,让serverFifo进程去读取:
在这里插入图片描述
首先,我们以写的方式打开文件,让管道的引用计数+1。

下面我们就要让它完成写入功能:
在这里插入图片描述
写完后进行关闭:
在这里插入图片描述
serverFifo也是类似的道理,我们以读的方式打开文件:
在这里插入图片描述
那么下面就是serverFifo读取的过程:
在这里插入图片描述
读取完之后,我们需要关闭:
在这里插入图片描述

运行结果:
在这里插入图片描述
在这里插入图片描述
从结果我们可以看到:在clientFifo里面写入,在serverFifo就能读取。
在这里插入图片描述
当clientFifo进程按Ctrl+d退出时,serverFifo也退出了,并且.fifo也自动删除。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学代码的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值