进程间通信-管道

news/2025/3/25 21:34:58/

文章目录

  • 1. 进程间通信介绍
    • 1.1 进程间通信目的
    • 1.2 进程间通信分类
  • 2. 管道
    • 2.1 什么是管道
    • 2.2 站在文件描述符角度-深度理解管道
      • 2.2.1 具体通信的过程
    • 2.3 匿名管道
    • 2.4 代码实现
  • 3. 进程控制
  • 4. 管道读写规则
  • 5. 管道特点
  • 6. 命名管道
    • 6.1 创建一个命名管道
    • 6.2 代码实现

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也自动删除。


http://www.ppmy.cn/news/72543.html

相关文章

python疑难进阶手册(1)-文件操作与随机数(1)

目录 文件操作文件编码文件读写一次一密加解文件随机数随机数概述随机数编程循环生成随机数文件操作 文件编码 通常情况下,文件是以 text mode 打开,就是说以字符串形式进行文件读写,这些字符串是以特定的 encoding 编码的。如果没有指定 encoding ,默认与平台有关的,,…

webGIS 的几种技术路径以及区别对比

引言: 作为前端程序员,WebGIS为我们提供了许多令人兴奋的机会,其中JavaScript地图库是实现WebGIS的重要工具之一。本文将介绍几个常用的JavaScript地图库,并对它们进行比较,以帮助我们在实际项目中做出明智的选择。 Le…

AlmaLinux 8.8 发布 - RHEL 下游免费发行版(CentOS 稳定版的替代品)

AlmaLinux 8.8 发布 - RHEL 下游免费发行版(CentOS 稳定版的替代品) AlmaLinux OS 是一个开源、社区驱动的项目,旨在提供 CentOS 稳定版的替代品。 请访问原文链接:https://sysin.org/blog/almalinux-8/,查看最新版。…

pubspec.yaml 第三方依赖版本控制

以下是一些常见的版本控制方式: 精确版本号:您可以指定特定的版本号,例如 dependency_name: 1.2.3。这将确保只有指定的版本被安装和使用。 范围约束:您可以使用比较运算符来指定版本范围,例如 dependency_name: ^1.2…

OPCUA 聚合服务器和历史数据服务器

前言 开放自动化是一个热门话题,自动化XML(AutomationML),基于信息模型的通信协议(OPC UA)和工业4.0 管理壳(ASS) 可谓是开放自动化的三套件。三者相互交叉,相互引用&…

UniApp之使用manifest.json应用配置的详细教学

manifest.json 文件是 UniApp 开发中用来配置应用信息的重要文件。通过修改 manifest.json 文件,开发者可以配置应用的名称、图标、启动页面、权限等信息。本文将为您提供详细的教学,介绍如何使用 manifest.json 文件进行应用配置,并提供示例…

2023年汽车软件行业趋势分析:安全性是汽车软件开发的重大挑战2023年汽车软件开发

伴随电动化、自动驾驶和混合动力车辆的发展,汽车行业正正在经历重大变革,面临着新的市场需求和挑战。阅读本文,您将了解到《2023年汽车软件开发现状》报告中强调的值得注意的汽车软件开发趋势,还可在文末获取完整报告。 汽车软件…

springboot+java养老院儿童福利院管理系统

安家儿童福利院管理系统包括儿童管理、申请领养管理、捐赠管理、楼栋管理、宿舍管理、分配信息管理、宿舍物品管理、报修管理、维修工管理、报修状态管理、留言管理、系统管理。通过对系统的实现得出安家儿童福利院管理系统具有安全稳定、操作简单、功能强大等特点,…