【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信号将写端进程终止的。

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

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

五、相关代码

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【启程Golang之旅】让文件操作变得简单

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

【python解决】查询报%d format: a number is required, not str问题

【Python解决】查询报%d format: a number is required, not str问题 在Python中&#xff0c;字符串格式化是一种常见的操作&#xff0c;用于创建包含变量的字符串。如果你在使用%操作符进行格式化时遇到了%d format: a number is required, not str的错误&#xff0c;这意味着…

计算机缺失msvcp100.dll如何解决?教你5种简单高效的修复方法

在现代科技发展的时代&#xff0c;计算机已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;在使用计算机的过程中&#xff0c;我们常常会遇到各种问题和困扰。其中之一就是计算机找不到msvcp100.dll文件。这个问题可能会给我们的生活和工作带来很多不便&#xff0c;下…

插入排序和希尔排序详解

插入排序详见&#xff1a;点这里 希尔排序(Shells Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。其是基于插入排序改进而来的。 希尔排序大致分为两步预排序和插入排序两大步。 预排序是将变量分为 n…

Docker大学生看了都会系列(八、Dokcerfile部署go项目)

系列文章目录 第一章 Docker介绍 第二章 2.1 Mac通过Homebrew安装Docker 第二章 2.2 CentOS安装Docker 第三章 Docker常用命令 第四章 常用命令实战 第五章 Docker镜像详解 第六章 Docker容器数据卷 第七章 Dockerfile详解 第八章 Dokcerfile部署go项目 文章目录 一、前言二、环…

【Vue】面经基础版-案例效果分析

面经效果演示 功能分析 通过演示效果发现&#xff0c;主要的功能页面有两个&#xff0c;一个是列表页&#xff0c;一个是详情页&#xff0c;并且在列表页点击时可以跳转到详情页底部导航可以来回切换&#xff0c;并且切换时&#xff0c;只有上面的主题内容在动态渲染 实现思路…

【Vue】练习-Vuex中的值和组件中的input双向绑定

目标 实时输入&#xff0c;实时更新&#xff0c;巩固 mutations 传参语法 实现步骤 代码示例 App.vue <input :value"count" input"handleInput" type"text"> <script>export default {methods: {handleInput (e) {// 1. 实时获取…

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

文章目录 Graphics的方法AddMetafileComment添加注释BeginContainer和EndContainer新建、还原图形容器不指定指定源与目标矩形指定源与目标矩形 Clear清空并填充指定颜色CopyFromScreen截图CopyPixelOperation DrawImage绘制图像DrawImage的GraphicsDrawImageAbort回调ExcludeC…

NSSCTF中的popchains、level-up、 What is Web、 Interesting_http、 BabyUpload

目录 [NISACTF 2022]popchains [NISACTF 2022]level-up [HNCTF 2022 Week1]What is Web [HNCTF 2022 Week1]Interesting_http [GXYCTF 2019]BabyUpload 今日总结&#xff1a; [NISACTF 2022]popchains 审计可以构造pop链的代码 <php class Road_is_Long{public $…

桑基图Cannot set properties of undefined (setting ‘dataIndex‘)

前端写桑基图的时候碰到以上bug 原因是&#xff1a; 桑基图中的name值有重复的&#xff0c;把重复的name值去掉就好了&#xff0c;或者如果name排查太麻烦&#xff0c;可以用唯一id作为name,增加些字段&#xff0c;展示时用fomatter的方式 参照https://www.cnblogs.com/lempe…

详解FedAvg:联邦学习的开山之作

FedAvg&#xff1a;2017年 开山之作 论文地址&#xff1a;https://proceedings.mlr.press/v54/mcmahan17a/mcmahan17a.pdf 源码地址&#xff1a;https://github.com/shaoxiongji/federated-learning 针对的问题&#xff1a;移动设备中有大量的数据&#xff0c;但显然我们不能收…

GPT-4o仅排第二!北大港大等6所高校联手,发布权威多模态大模型榜单!

多模态大模型视频分析能力榜单出炉&#xff1a; Gemini 1.5 Pro最强&#xff0c;GPT-4o仅排第二&#xff1f; 曾经红极一时的GPT-4V屈居第三。 3.5研究测试&#xff1a;hujiaoai.cn 4研究测试&#xff1a;askmanyai.cn Claude-3研究测试&#xff1a;hiclaude3.com 最近&#…

python代码中参数的默认值

python中的函数&#xff0c;可以给形参指定默认值。 带有默认值的参数&#xff0c;可以在调用的时候不传参。 如上图所示&#xff0c;在给函数设定形参的时候可以给函数形参设定默认值&#xff0c;当然默认参数的形参应该在非默认形参的后面。 如果在调用函数的时候&#xff…

【机器学习】因TensorFlow所适配的numpy版本不适配,用anaconda降低numpy的版本

目录 0 TensorFlow最高支持的numpy版本 1 激活你的环境&#xff08;如果你正在使用特定的环境&#xff09; 2 查找可用的NumPy版本 3 安装特定版本的NumPy 4. 验证安装 5.&#xff08;可选&#xff09;如果你更改了base环境 0 TensorFlow最高支持的numpy版本 要使用 …

测试基础11:测试用例设计方法-等价类划分

课程大纲 1、概述 1.1测试用例设计方法意义 穷举测试&#xff1a;每种输入都测一次。最完备&#xff0c;但不现实。 使用设计方法&#xff0c;用最少的数据&#xff08;成本&#xff09;&#xff0c;实现最大的测试覆盖。 1.2常用设计方法 ①等价类划分 ②边界值分析 ③错误推…

SpringBoot+Vue网上购物商城系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 系统角色对应功能 用户商家管理员 系统功能截图

【安装笔记-20240608-Linux-免费空间之三维主机免费空间】

安装笔记-系列文章目录 安装笔记-20240608-Linux-免费空间之三维主机免费空间 文章目录 安装笔记-系列文章目录安装笔记-20240608-Linux-免费空间之三维主机免费空间 前言一、软件介绍名称&#xff1a;三维主机免费空间主页官方介绍 二、安装步骤测试版本&#xff1a;openwrt-…

ROS学习记录:栅格地图格式

一、机器人导航所使用的地图数据&#xff0c;就是ROS导航软件包里的map_server节点在话题 /map 中发布的消息数据&#xff0c;消息类型是nav_msgs消息包中的OccupancyGrid&#xff0c;它的中文意思的占据栅格&#xff0c;是一种正方形小格子组成的地图。 二、对障碍物进行俯视&…

基于STM32智能小车

一、前置准备 前置知识&#xff1a;需要学习stm32&#xff0c;建议去b站看江科大的视频&#xff0c;讲的很详细&#xff0c;学完串口那一块就可以制作了&#xff0c;软件用的是Keil5&#xff0c;开发语言C语言&#xff0c;手机连接蓝牙模块软件是蓝牙调试器。 需要准备的器件…

const详解

关键字const用来定义常量&#xff0c;如果一个变量被const修饰&#xff0c;那么它的值就不能再被改变。 但是&#xff0c;可以通过取地址进行修改。 将const 在指针前进行修饰&#xff0c;那么就修饰指针所指向的变量。 但是指针变量可以被修改。 将const 在指针后进行修饰&am…