【Linux】进程间通信之匿名管道

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、进程间通信介绍
      • 1.1 是什么
      • 1.2 为什么(目的)
      • 1.3 怎么做到的(浅聊)
      • 1.4 进程间通信的实现方式
  • 二、什么是管道
  • 三、匿名管道
      • 3.1 匿名管道的工作原理
      • 3.2 站在文件描述符角度-深度理解管道
      • 3.3 pipe系统调用接口
      • 3.4 匿名管道的特征
      • 3.5 匿名管道的四种情况
  • 四、简单模拟进程池(匿名管道的应用场景)
  • 五、相关代码

一、进程间通信介绍

1.1 是什么

进程间通信IPCInter-Process Communication)就是两个或多个进程实现数据层面的交互

1.2 为什么(目的)

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

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

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

1.3 怎么做到的(浅聊)

  • 进程间通信的本质:让不同的进程能共享同一份“资源”。这个“资源”可以理解为特定形式的内存空间。当一个进程将数据写入共享内存后,另一个进程就可以从同一份内存空间中读取这些数据,从而实现了进程之间的数据传递和共享。

  • 这个资源(内存)一般由操作系统提供。那为什么不是两个进程中的其中一个提供这个“资源”呢?可以假想一下,假设这个资源真的由其中一个进程提供,而进程是具有独立性的,各自拥有自己的虚拟地址空间和资源,那么它就会暴露自己的内部状态和数据给其他进程,如果提供资源的进程出现故障或意外终止,其他进程可能会受到影响,因为它们依赖于这个进程提供的资源。

  • 相比之下,如果资源由操作系统提供,而进程相当于用户,如果想使用资源,那么操作系统必定会提供统一接口

  • 而我们知道,因为系统中不止一对进程在进行通信,可能会存在多个,那么操作系统就要提供多份“资源”,因此操作系统就必须对“资源”进行管理,这又得搬出管理的六字真言:先描述,再组织

1.4 进程间通信的实现方式

请添加图片描述

二、什么是管道

Linux中,管道可以被视为一种特殊类型的文件,它是基于文件级别的通信方式。它使得一个进程的输出可以直接成为另一个进程的输入,从而实现了进程之间的数据传输和协作。在Linux中,你可以使用管道符号 |将一个进程的输出发送到另一个进程的输入。

  • 比方说你想要统计一个文件中包含的单词数量。

请添加图片描述

其中,当cat命令和wc命令运行起来后就是两个进程,cat进程通过标准输出将数据传输到管道当中,wc进程再通过标准输入从管道当中读取数据,至此便完成了两个进程间通信。

请添加图片描述

Linux中,有两种类型的管道,一种是匿名管道(Anonymous Pipe),另一种是命名管道(Named Pipe)。因此,这篇博客重点是要解释匿名的工作原理等方面。(命名管道在下一篇博客讲解)

三、匿名管道

3.1 匿名管道的工作原理

  • Linux中,管道可以被视为一种特殊类型的文件,它是基于文件级别的通信方式。为什么这样说呢?管道的使用方式类似于文件的读写,它是通过文件描述符进行访问,管道的读取端和写入端分别对应着文件的读取和写入。在管道中写入的数据会被暂存在内核的缓冲区中,然后读取进程从缓冲区中读取,因为它在文件系统中没有具体的文件路径或名字,因此被称为匿名管道。它其实是操作系统在内存中创建的一段缓冲区。(后面会介绍)

  • Linux中,使用管道符号 | 本质上就是创建了一个匿名管道,它常用于父子进程(或者有血缘关系)之间的通信。

不知道大家有没有好奇:进程通信时每次都要向文件中读取数据,而文件是存储在磁盘上的,这不会导致效率非常低吗?如果效率非常低的话,Linux为什么还要开发出管道符号|呢?因此,我们必须要知道匿名管道的工作原理。

进程间通信的本质:让不同的进程看到同一份资源。因此,使用匿名管道实现父子进程间通信的原理就是:让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。

在这里插入图片描述

学习到现在,我们都清楚(复习):

  • 当一个程序运行起来(加载到内存),就是一个进程,操作系统会管理该进程并且其创建task_struct结构体对象。

  • 当进程打开文件时,操作系统会在内核中创建数据结构来描述这个已打开的文件对象struct file。它包含了inode(文件的所有属性)、指向文件操作函数表的指针(文件操作方法)、内核缓冲区等等。

  • 而进程可以打开多个文件。所以,操作系统还需要对打开的文件进行管理,所以进程task_struct对象里有一个指针struct files_struct* files,这个指针指向一个结构体files_struct,而这个结构体包含一个指针数组struct file* fd_array[],这个数组我们可以称之为文件描述符表。数组中的每个元素都是指向当前进程所打开文件的指针(地址)!默认情况下,当一个进程启动时,操作系统会打开三个标准流(文件):stdin(键盘文件)、stdout(显示器文件)、stderr(显示器文件)。

回归正题:

此时fork创建子进程,操作系统会以父进程为模板,为子进程创建新的进程控制块task_structfiles_struct等。但并不会复制父进程打开的文件对象 struct file,因为文件操作通常是在用户空间的,与进程无关;而子进程会拷贝父进程的文件描述符表 files_struct,这是因为文件描述符表是进程的一部分,它记录了进程打开的文件描述符和相应的文件对象。因此,子进程的 struct file* fd_array[] 中的元素会指向父进程打开的文件对象,即父子进程(不同的进程)可以看到同一份资源(打开的文件),这不就是通信的本质吗?!

又因为文件对象struct file是由操作系统管理的,如果操作系统识别到是普通文件,就通过内核缓冲区刷新策略往磁盘上读写;而如果识别到时管道文件,操作系统不会将数据刷新到磁盘,而是直接在内核缓冲区中进行数据传输。因此,这种操作方式使得管道的数据传输更加高效。

注意:匿名管道在相关进程的生命周期内有效,但是并不是一旦相关进程结束就会自动关闭并释放资源。而是会依靠文件对象的引用计数机制来管理资源的释放。只有当所有相关进程都关闭了对管道的引用时,文件对象的引用计数变为零,才会触发操作系统对管道资源的释放。

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

如果父进程以只读方式打开管道,并且在创建子进程时,子进程也会以只读方式打开管道。那么进程间该如何通信呢?

显然以上这种方式是无法进行通信的。如果要实现通信,一种常见的方法(步骤)是:

  1. 父进程在打开管道的时候,以读和写的方式打开

请添加图片描述

  1. 接下来父进程fork创建子进程,子进程会拷贝父进程的文件描述符表,所以子进程也会以读写的方式打开管道文件

请添加图片描述

  1. 接下来根据实际需求,可以通过关闭相应的管道端口来实现单向通信。比方说,你想让父进程向管道写入数据,而子进程从管道读取数据。那么你就需要关闭父进程的读取端,关闭子进程的写入端。

请添加图片描述

接下来可能有人会想:为什么管道两端不设计成既可读,也可写呢?

  1. 竞态条件:如果管道的两端都可以读写,那么可能会出现竞态条件。两个进程同时尝试在管道中读取和写入数据,可能导致数据的不一致性和混乱。

  2. 死锁:读写管道的两端同时进行读写操作时,可能会导致死锁。例如,一个进程在等待从管道中读取数据,而另一个进程在等待向管道中写入数据,这种情况下可能会导致两个进程相互等待,最终造成死锁。

  3. 数据丢失:如果管道的两端同时读写,可能会导致数据丢失。例如,一个进程正在向管道中写入数据,同时另一个进程正在从管道中读取数据,这种情况下可能会导致部分数据被丢失。

  4. 混乱的通信:同时读写管道可能会导致通信的混乱和不可预测性。由于数据的读写是同时进行的,可能会导致数据的顺序混乱或部分数据丢失,使通信变得不可靠。

因此,为了避免这些问题,一般建议使用管道时一端只负责写入数据,另一端只负责读取数据。这样可以确保通信的可靠性和一致性。

3.3 pipe系统调用接口

Linux中,除了使用管道符号|创建和使用匿名管道之外,也可以在程序中使用 pipe 系统调用进行创建和使用。

【函数原型】

#include <unistd.h>
int pipe(pipefd);

其中:

  • pipefd:做输出型参数,传一个整型数组,用于存放管道文件的两个文件描述符。pipefd[0] 是管道的读取端,pipefd[1] 是管道的写入端。

  • 返回值

    • 成功时,返回值为0,并且在 pipefd 数组中存放管道的两个文件描述符。

    • 失败时,返回值为-1,错误码保存在 errno 中。

在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,我们设置父进程只能向管道读取数据,而子进程从管道写入数据,代码如下(配代码注释)

在这里插入图片描述

【程序结果】

在这里插入图片描述

3.4 匿名管道的特征

  1. 匿名管道常用于具有血缘关系的进程。

  2. 管道只能单向通信。

  3. 父子进程是会协同的,同步与互斥的,是为了保护管道文件的数据安全(信号量及多线程再谈)。意思就是说:在管道通信过程中,如果父进程负责从管道读取数据,而子进程负责向管道写入数据,要确保进程在通信时以正确的顺序发送和接收消息,避免出现数据竞争和不确定行为。

  4. 管道的生命周期随进程。因为管道在操作系统中被看作是一种文件,但是它其实是是通过内存缓冲区来传输数据。当所有打开管道的进程(读取端和写入端)都关闭了管道,管道所占用的资源就会被操作系统释放。

  5. 管道是面向字节流的。(网络再谈)

3.5 匿名管道的四种情况

  1. 读写端正常,管道如果为空,读端就会被阻塞,等待写端的数据。

比方说,写端只向管道写三次数据,那么对应的读端就会接收三次数据,后续读端就会被阻塞,等待写端数据

在这里插入图片描述

【程序结果】

在这里插入图片描述

  1. 读写端正常,管道如果被写满,写端就要被阻塞。这说明管道是有限大小的缓冲区,此大小由操作系统决定。

比方说:子进程不断写入,父进程不读取(死循环啥也不干)

在这里插入图片描述

【程序结果】

在这里插入图片描述

  1. 读端正常读,写端关闭,那么读取端会在读取完管道中的所有数据后得到一个特殊的信号,表明已经到达了管道的末尾。这样读取操作就会返回值为 0,也就是read函数会返回0,而不会再被阻塞(不会再等待管道中会有新的数据)。因此,当写端关闭后,读端就知道不会再有新的数据写入管道,可以安全地关闭管道,结束通信。

在这里插入图片描述

【程序结果】

在这里插入图片描述

因此,当写端关闭后,读端就知道不会再有新的数据写入管道,可以安全地关闭管道,结束通信。

在这里插入图片描述

  1. 写端正常,读端关闭。那么这两个进程之间通信就没有意义了,因此,操作系统就要杀掉正在写入的进程。那如何杀掉呢?— 当操作系统希望终止一个正在运行的进程时,通常会发送一个特定的信号给该进程来杀掉。
    请添加图片描述
    (该图片来自于往期博客)

那么写端进程退出时究竟是收到了什么信号,我们可以来验证一下

在这里插入图片描述

【程序结果】

在这里插入图片描述

运行结果显示,子进程(写端)退出时收到的是13号信号

通过kill -l命令可以查看13对应的具体信号。

在这里插入图片描述

由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将写端进程终止的。

四、简单模拟进程池(匿名管道的应用场景)

在另一篇博客中,博主正在加工中~

五、相关代码

本篇博客的相关代码:点击跳转

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/24729.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript未知泛型——unknown和any

在 TypeScript 中&#xff0c;如果你想要表示一个泛型变量&#xff0c;但你不知道或不关心它具体是什么类型&#xff0c;你可以使用 any 类型。然而&#xff0c;any 类型会绕过类型检查&#xff0c;这可能不是你想要的。为了保持类型安全性&#xff0c;TypeScript 提供了一个特…

Day50 动态规划part09

LC198打家劫舍 偷前一家或者偷前两家和这家&#xff1a;dp[i] Math.max(dp[i-2]nums[i],dp[i-1]);代码 LC213打家劫舍II&#xff08; 未掌握&#xff09; 解题思路&#xff1a;因为成环了&#xff0c;所以首位元素一定是两者只能选择一个或者两者都不选三种情况&#xff1…

四舍五入问题

单纯输出四舍五入可以用 printf("%.nf",num); 但是这个方法有时候会出错 代表输出n位四舍五入小数 而将数四舍五入赋值给变量可以用round&#xff08;&#xff09;函数 a round(num); 表示将num四舍五入赋值给a 但是这么些只能转换位四舍五入的整数 可以改…

【学习笔记】Windows GDI绘图(十一)Graphics详解(下)

文章目录 Graphics的方法Graphics.FromImageSetClip设置裁切区域IntersectClip更新为相交裁切区域TranslateClip平移裁切区域IsVisible判断点或矩形是否在裁切区域内MeasureCharacterRanges测量字符区域MeasureString测量文本大小MultiplyTransform矩阵变换 Graphics的方法 Gr…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【07】【商品服务】分页查询_品牌分类关联与级联更新

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【07】【商品服务】分页查询_品牌分类关联与级联更新 分页查询为数据库表设计冗余字段冗余字段带来的问题以及处理品牌名发生变化&#xff0c;进行级联更新分类名发生变化&#xff0c;进行级…

SQL语句练习每日5题(四)

题目1——查找GPA最高值 想要知道复旦大学学生gpa最高值是多少&#xff0c;请你取出相应数据 题解&#xff1a; 1、使用MAX select MAX(gpa) FROM user_profile WHERE university 复旦大学 2、使用降序排序组合limit select gpa FROM user_profile WHERE university 复…

css系列:进度条

前言 技术来源于需求&#xff0c;近期遇到了做语音的需求&#xff0c;有个调整语速和音量的进度条&#xff0c;UI组件库的进度条大部分不支持拖动和点击修改当前进度&#xff0c;所以自己手写了一个。 实现思路 MDN文档介绍 <input type"range"> - HTML&am…

二叉树的前序便利,中序遍历,后序遍历

前序遍历&#xff1a; 递归&#xff1a; class Solution { public:void preorder(TreeNode *root, vector<int> &res) {if (root nullptr) {return;}res.push_back(root->val);preorder(root->left, res);preorder(root->right, res);}vector<int> …

解决 make_ext4fs is not find, it is recommanded to install android-tools-fsutils

编译cluster linux时&#xff0c;遇到make_ext4fs命令无法找到。这个工具是被软件包 android-tools-futils 包含的。这个软件包只支持18.04的系统&#xff0c;在ubuntu20.04和ubuntu22.04上&#xff0c;无法被正确的安装。会报依赖问题&#xff0c;报错的信息是依赖python相关的…

Java中的IO流字节流(FileOutputStream与FileInputStream)+编码与解码

目录 ​编辑 IO流 File0utputstream FileOutputstream写数据的3种方式 void write(int b) 一次写一个字节数据 void write(byte[] b) 一次写一个字节数组数据 void write(byte[] b,int off,int len) 一次写一个字节数组的部分数据 FileOutputstream写数据的…

搞编程学习时是如何查找资料的?

刚开始学编程时&#xff0c;我通常用百度、360这样的搜索引擎去找资料。但后来我发现&#xff0c;根据想找的东西不同&#xff0c;用的搜索地方也得变。比如说&#xff0c;找编程学习的东西&#xff0c;我就不太用浏览器了&#xff0c;因为那儿广告太多&#xff0c;信息乱七八糟…

问题:军保卡不允许开立附属卡,不能开展境外交易,不开通云闪付工功能() #其他#经验分享

问题&#xff1a;军保卡不允许开立附属卡&#xff0c;不能开展境外交易&#xff0c;不开通云闪付工功能&#xff08;&#xff09; A&#xff0e;A&#xff1a;正确 B&#xff0e;B&#xff1a;错误 参考答案如图所示

C++STL初阶(3):string模拟实现的完善

1.流提取>>的优化&#xff08;利用缓存区的思想&#xff09; istream& operator>>(istream& is,string& str) {str.clear();char c;c is.get();while (c ! \0 && c ! \n) {str c;c is.get();}return is; } 在上文的对string的实践中&#…

blazehttp下载安装和自动化测试防护效果

blazehttp下载安装和自动化测试防护效果 说明测试环境的准备网站和waf配置blazehttp下载安装和测试测试效果waf安全日志查看 说明 需要docker环境和1panel面板 本测试使用blazehttp南墙waf进行测试&#xff0c;有兴趣的同学推荐使用雷池waf 测试环境的准备 使用1panel面板&am…

为什么会有虚像

本来我就打算写虚像相关的内容&#xff0c;实际上我看不懂光学的内容&#xff0c;我只是发觉书上没有使用变分法来做&#xff0c;而只是解析几何的变换&#xff0c;这个做法完全脱离实际&#xff0c;物理书为什么会这样写不知道原因&#xff0c;但是很明显这样的内容也非常的复…

docker基础,docker安装mysql,docker安装Nginx,docker安装mq,docker基础命令

核心功能操作镜像 Docker安装mysql docker run -d --name mysql -p 3306:3306 -e TZAsia/Shanghai -e MYSQL_ROOT_PASSWORDlcl15604007179 mysql docker的基本操作 docker rm 容器名称即可 docker ps 查看当前运行的容器 docker rm 干掉当前容器 docker logs 查看容器命令日…

瑞安面试分享

瑞安面试分享 面试时间&#xff1a;07/06/2024 方式&#xff1a;在线zoom&#xff0c;雇主会发面试链接&#xff0c;提前进入准备 瑞安招前端和后端开发&#xff0c;我面的是偏数据库设计的后端开发 问题1&#xff1a;自我介绍 寒暄后开始自我介绍&#xff0c;如果是后端就…

Golang:bytes 格式和解析数字字节值(10K、2M、3G等)

bytes 格式和解析数字字节值(10K、2M、3G等) 文档 https://github.com/labstack/gommon/tree/master/bytes 安装 go get github.com/labstack/gommon/bytes代码示例 格式化 bytes.Format(13231323) // 12.62MiB解析 b, _ : bytes.Parse("2M") // 2000000完整代…

代码随想录算法训练营Day32|122.买卖股票的最佳时机II、55.跳跃游戏、45.跳跃游戏II

买卖股票的最佳时机II 122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 这里我的贪心策略是&#xff0c;判断今天和前一天的股票差值&#xff0c;若差值大于0&#xff0c;则说明能获益&#xff0c;即卖出&#xff0c;每天都比较一次&#xff0c;将所有差…

为什么Vue的watch函数无法检测到父组件的参数变化?

在 Vue 中&#xff0c;watch 函数用于观察和响应 Vue 实例上的数据变动。然而&#xff0c;如果你在父组件中直接修改了数组或对象的内容&#xff08;例如&#xff0c;通过索引直接设置一个项的值&#xff0c;或者使用 Array.prototype.push 修改数组&#xff09;&#xff0c;Vu…