【LInux】进程间通信 -- 匿名管道

news2024/9/21 3:25:12

前言

我们在学习进程管理,进程替换时,都强调了进程的独立性,那进程间通信是什么?这好像和进程的独立性相矛盾吧?
那么今天,我们就来学习进程间通信,和第一种通信方式 – 管道

在这里插入图片描述

文章目录

  • 前言
  • 一. 进程间通信
  • 二. 管道
  • 三. 匿名管道的使用
    • 1. pipe的使用
    • 2. 准备通信
    • 3. 匿名管道的特点和场景
  • 结束语

一. 进程间通信

进程间通信,并没有破坏进程的独立性这一特点,这点我们在管道讲解
而进程通信的目的有如下几个:

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

进程间通信的三种常用方法

  1. 管道

匿名管道pipe
命名管道

  1. System V进程间通信

System V消息队列
System V共享内存
System V信号量

  1. POSIX进程间通信

消息队列
共享内存
信息号
互斥量
条件变量
读写锁

进程依旧具有独立性,一个进程不可能可以直接从另一个进程的堆,栈区获取数据,那么进程间通信是怎么实现的呢?
要让两个不同的进程,进行通信,前提条件是:先让不同进程,看到同一份“资源”

而管道就是,看到同一份“资源”的实现方式之一
在这里插入图片描述

二. 管道

管道是Unix中最古老的进程间通信形式
我们把一个进程连接到另一个进程的一个数据流称为一个“管道”
我们在Linux指令中使用的| 就是管道
who | wc -lwho可以查看有几个用户登录服务器,wc可以统计有几行文本行
这里就是who创建进程,先显示有几个用户,然后将数据传输给wc,wc处理完再输出结果
在这里插入图片描述

我们再具象的理解管道

当我们创建一个进程,OS会创建task_struct维护,管理进程。而该结构体里有一个strust file_struct*,指向一个结构体,该结构体是管理文件的,里面存储了打开文件的文件描述符默认0,1,2分别是标准输出,标准输入,标准错误
而Linux下一切皆文件,管道也是文件,但是是OS为了实现进程间通信,而临时创建的一个内存文件。默认是空闲文件描述符的后两个。如下图

在这里插入图片描述

而创建子进程,需要重新创建task_struct ,但是struct files_struct的内容是拷贝父进程的,但也仅是拷贝,拷贝一份文件描述符和文件的映射关系,不会重新创建新文件
fork创建子进程后,只会赋值进程相关的数据结构对象,不会复制父进程曾经打开的文件对象!就像浅拷贝一样

在这里插入图片描述

这种管道,只支持单向通信,叫做匿名管道。因为文件只有一个缓冲区,所以读写同时只能进行一项
所以我们需要手动确定数据流向,关闭不需要的文件描述符fd

三. 匿名管道的使用

接下来,我们就来简单模拟一下进程间通信。

用匿名管道实现进程通信,需要父进程创建匿名管道,创建的方法是使用pipe函数
在这里插入图片描述

该函数的参数较为特殊:是输出型参数。类似waitpid的status。
我们传过去一个2大小的整型数组,pipe函数内部会将创建的管道的读和写两个文件描述符写入这个数组,返回给我们

返回值:成功调用,返回0;错误,返回-1,并设置错误码

1. pipe的使用

我们首先使用一下pipe函数,看一下其效果

#include<iostream>
#include<cerrno>
#include<unistd.h>
#include<string.h>

using std::cout;
using std::endl;

int main()
{
    //创建管道所需传参的数组
    int pipefd[2]={0};
    
    //1.创建管道
    int n=pipe(pipefd);
    if(n<0)
    {
        //如果返回值小于0,即-1,还会设置错误码
        //我们再把错误码对应的错误信息,打印一下
        cout<<"pipe error,"<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"pipefd[0]:"<<pipefd[0]<<endl;
    cout<<"pipefd[1]:"<<pipefd[1]<<endl;

    return 0;
}

在这里插入图片描述
正如前面所说,管道的两个文件描述符,默认使用当前空闲的前两个文件描述符
管道创建的两个文件描述符,默认第一个是读,第二个是写

记忆法
pipe[0]的是读端,0 -> 嘴巴 -> 读
pipe[1]的是写端,1 -> 笔 -> 写

2. 准备通信

我们知道了管道的创建方法,接下来就可以准备实现父子进程通信了。
我们上面说到,进程间通信的前提条件就是:让不同的进程,看到同一份资源。
管道就可以是这份资源,我们模拟父子进程通信,让子进程往管道里写数据,然后父进程接收数据

代码如下:

#include<iostream>
#include<cerrno>
#include<cassert>
#include<unistd.h>
#include<string.h>
#include<string>
#include<sys/types.h>

using std::cout;
using std::endl;

int main()
{
    //创建管道所需传参的数组
    int pipefd[2]={0};
    
    //1.创建管道
    int n=pipe(pipefd);
    if(n<0)
    {
        //如果返回值小于0,即-1,还会设置错误码
        //我们再把错误码对应的错误信息,打印一下
        cout<<"pipe error,"<<errno<<":"<<strerror(errno)<<endl;
        return 1;
    }
    cout<<"pipefd[0]:"<<pipefd[0]<<endl;//读端
    cout<<"pipefd[1]:"<<pipefd[1]<<endl;//写端

    //2.创建子进程
    pid_t id = fork();
    //获取错误。意料之外,使用if;意料之中,用assert
    //此处应该使用if,但为了简单一些,使用assert
    assert(id!=-1);

    if(id==0)
    {
        //子进程
        
        //3.关闭不需要的fd
        close(pipefd[0]);//关闭子进程的读端

        //4.开始通信
        const std::string namestr="hello ,我是子进程";
        int cnt=1;//计数器

        char buffer[1024];//write的字符数组

        while(true)
        {
            //将内容写入buffer字符串
            snprintf(buffer,sizeof(buffer)-1,"%s,计数器:%d,我的PID:%d\n",namestr.c_str(),cnt++,getpid());
            //将内容写入管道
            write(pipefd[1],buffer,strlen(buffer));
            sleep(1);
        }

        //关闭子进程的写端,再exit退出
        close(pipefd[1]);
        exit(0);
    }

    //父进程

    //3.关闭不需要的fd
    //让父进程进行读取
    close(pipefd[1]);//关闭父进程的写端

    //4.开始通信
    char buffer[1024];
    while(true)
    {
        //读取的大小,至少要留一个位置写入\0
        int n = read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            cout<<"我是父进程, child give me a message: "<<buffer<<endl;
        }
    }

    
    //关闭父进程的读端,结束进程
    close(pipefd[0]);
    return 0;
}

在这里插入图片描述
这样我们就实现了父子进程的通信。

3. 匿名管道的特点和场景

  1. 当我们将子进程写入数据的sleep(1)注释掉。程序运行的结果就变得不一样了。
    在这里插入图片描述
    我们看到,子进程写了很多次,父进程才进行了一次读取。
    我们再改变一下,在子进程写入后的sleep改成sleep(5)
    在父进程读取数据时,我们10个字节,10个字节的读取
    在这里插入图片描述
    程序运行结果就又变了
    在这里插入图片描述

这两个实验验证出了这样一个结论:
在匿名管道的通信中,写入的次数,和读取的次数,不是严格匹配的,读写次数的多少没有强相关 — 因为缓冲区的读写都是以字节为单位 — 字节流

  1. 接下来,我们再做个实验:
    我们让父进程正常的读取数据,但子进程每次写入间隔10秒。
    在这里插入图片描述
    观察运行,我们发现,我们让子进程写入变慢,但是父进程的读取也变慢了。这是怎么回事呢?

首先,管道文件的数据类似队列,写入一次是入队列,读取是出队列,只要读取,数据就没了。
所以,子进程写入变慢,父进程没有东西可读,就进入了阻塞状态。

  1. 我们再让父进程的读取变慢,子进程正常写入
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

我们发现,管道进行了65536(从0开始)次写入之后就没有写入了。等父进程时间一到,读取了很多的X
而每次写入,我们都只写入1个字节。所以管道最多可以写入65536字节,也就是2的16次方64kb16个数据块。

  1. 如果我们将子进程的写端关闭,父进程的读端继续,会发生什么呢?
    我们让子进程写入一次数据就关闭写端,父进程仍然一直读取数据,但要对read的返回值多作一个判断
    在这里插入图片描述
    运行结果如下
    在这里插入图片描述

当我们关闭子进程的写端,父进程再读取就会读到文件尾,就会返回0,父进程就终止了。

  1. 如果我们关闭父进程的读端,结果又会是这样呢?

直接说结论:
当一个管道只有写端,没有读端,代表着无论怎么写,都不会有人获取,这是没有意义的事,而操作系统不会维护无意义的,低效率的,或者浪费资源的事情。OS会杀死一直在写入的这个进程!通过13号信号 SIGPIPE,杀死进程

接下来,我们总结一下匿名管道的特点和场景

特点

  1. 单向通信半双工的一种情况,双方同时只能一方写入
    因为匿名管道只有一个缓冲区,同时只能有一方进行读写。
    全双工,双方可以同时写入
  2. 匿名管道的本质是文件,因为fd的生命周期随进程,所以管道的生命周期也是随进程的。
  3. 匿名管道通信,通常用来进行具有“血缘关系”的进程之间的进程通信,因为匿名管道是内存级文件,所以只有创建的子进程可以获得父进程创建的匿名管道,所以常用于父子间通信 -- pipe 打开匿名管道。
    4.在匿名管道的通信中,写入的次数,和读取的次数,不是严格匹配的,读写次数的多少没有强相关 — 因为缓冲区的读写都是以字节为单位 — 字节流
  4. 具有一定的协同能力,让read和write能够按照一定的步骤进行通信 — 自带同步机制

场景

  1. 如果我们read,读端,读取完毕了所有的管道数据,如果对方不发,读端就会堵塞
  2. 如果我们write,写端,将管道写满了,那就暂时不能继续写了,需要读端读取数据,被读取的数据从管道中去除,就可以继续写入
  3. 如果我们关闭了写端读取完毕管道数据,再读,就会读到文件尾,read就会返回0
  4. 写端一直写,读端关闭,操作系统会发送13号 SIGPIPE信号杀死写端的进程。

当单次写入的数据量不大于PIPE_BUF时,LInux将保证写入的原子性
反之大于PIPE_BUF时,Linux不再保证写入的原子性。
目前的理解是,保证写入时不会被读取

结束语

本篇博客的内容到此就结束了。

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

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

相关文章

高效管理 Linux 进程:如何后台执行程序、查看进程、终止任务

目录 前言一、nohup命令详解1-1、nohup命令介绍1-2、语法格式1-2-1、基础语法介绍1-2-2、执行脚本文件1-2-3、执行python文件1-2-4、拓展延申&#xff1a;在服务器上运行后台进程1-2-5、nohup和&的区别 二、进程查看2-1、jobs命令&#xff08;基本不用&#xff09;2-2、ps命…

Android进阶宝典—在Compose中跳转Fragment

使用场景 我们原有的项目中基本采用的是单Activity架构&#xff0c;页面之间的跳转都是通过Navigation进行的&#xff0c;举个简单的例子。 在这种单Activity架构模式下&#xff0c;有一天我们想把MainActivity或者BFragment使用Compose重构&#xff0c;这个时候我们就需要去…

PasteSpider软件优势介绍

PasteSpider采用.netcore编写&#xff0c;运行于linux服务器的docker/podman里面&#xff0c;涉及的技术或者工具有podman/docker,registry,nginx,top,ssh,git,svn等。 PasteSpider可以更好的为你执行服务的升级和维护工作。支持集群模式安装&#xff0c;也支持单例模式运行。…

如何为 Apple 官方 SwiftUI 示例中的图表元素加上首显动画?

0. 概览 在 Apple 官方教程示例 Animating Views and Transitions 中,苹果为我们展示了如何为 SwiftUI 中的各种视图添加动画和过渡效果。 在示例的最后,我们在完成 3 种不同数据类型(Elevation, Heart Rate, Pace)切换的同时,顺面收获了美美的图表元素动画效果: 不过,…

科研方向与个人思考

文章目录 关于科研选题与方法的若干思考如何选题选题tips确定研究问题之后如何读论文——与一篇优秀论文作者的博弈阅读论文的技巧代码实现过程中成果发表四步走&#xff1a;科技论文的书写顺序学会利用工具科研的正确姿势 研究生时间线个人思考入门一个领域的步骤&#xff08;…

【有功-无功协调优化】基于改进多目标粒子群优化算法(小生境粒子群算法)的配电网有功-无功协调优化研究(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

Windows 安装 GDAL C++库

Windows 安装 GDAL C库1. 方法1&#xff1a;下载配置网友编译的GDAL版本1.1 下载1.2 配置1.3 测试1.4 缺点2. 方法2&#xff1a;自己编译3. 参考1. 方法1&#xff1a;下载配置网友编译的GDAL版本 1.1 下载 CSDN: GDAL&#xff0c;geos联合编译的库&#xff0c;版本为1.8.0&am…

VMware虚拟机搭建

网络步骤 VMWARE虚拟机NAT模式上网设置 VM虚拟机设置 虚拟机全局设置 启动虚拟机选择【虚拟网络编辑器】 如果需要管理员权限点【更改设置】&#xff0c;没有提示这忽略这一步 选择NAT模式&#xff0c;更改下面的子网IP&#xff0c;改成你需要的任何一个子网网段&#xff08;…

KDZD608屏蔽服效率测试仪

一、产品概述 KDZD608型屏蔽服效率试验装置&#xff08;带电作业用屏蔽服屏蔽效率试验装置&#xff09;是对屏蔽服进效率试验的专用设备&#xff0c;广泛用于电力系统、生产厂家和科研单位不可缺少的检测设备。KDZD608型屏蔽服效率试验装置根据最新国家标GB6568.2-86《带电作业…

9.含冰蓄冷空调的冷热电联供型微网多时间尺度优化调度

说明书 MATLAB代码&#xff1a;含冰蓄冷空调的冷热电联供型微网多时间尺度优化调度 关键词&#xff1a;冰蓄冷空调 CCHP-MG 多时间尺度优化 冷热电联供 参考文档&#xff1a;《含冰蓄冷空调的冷热电联供型微网多时间尺度优化调度》完全复现 仿真平台&#xff1a;MATLAB yal…

算法学习day57

算法学习day57 1.力扣647. 回文子串1.1 题目描述1.2分析1.3 代码 2.力扣 516.最长回文子序列2.1 题目描述2.2 分析2.3 代码 3.参考资料 1.力扣647. 回文子串 1.1 题目描述 题目描述&#xff1a; 给定一个字符串&#xff0c;计算这个字符串中有多少个回文子串。 具有不同开始…

TenserRT(一)模型部署简介

第一章&#xff1a;模型部署简介 — mmdeploy 0.12.0 文档 pytorch.onnx.export方法参数详解&#xff0c;以及onnxruntime-gpu推理性能测试_胖胖大海的博客-CSDN博客 我们来谈谈ONNX的日常 - Oldpan的个人博客 初识模型部署 训练&#xff1a;网络结构&#xff08;深度学习框…

【Windows】更换笔记本键盘操作

目录 一、拆旧 1、电脑保持关机状态 2、拆下电池 3、拧开中间这块区域的螺丝 4、拿键盘进行对照&#xff0c;找到对应的固定键盘的螺丝的位置&#xff0c;拧开 5、直接撬起原本的键盘&#xff0c;注意不要扯到下面的排线&#xff0c;小心点 二、换新 6、换新键盘&#…

公司新招了个腾讯拿38K的人,让我见识到了什么才是测试天花板···

5年测试&#xff0c;应该是能达到资深测试的水准&#xff0c;即不仅能熟练地开发业务&#xff0c;而且还能熟悉项目开发&#xff0c;测试&#xff0c;调试和发布的流程&#xff0c;而且还应该能全面掌握数据库等方面的技能&#xff0c;如果技能再高些的话&#xff0c;甚至熟悉分…

ubuntu_修改libc.so.6 或者 libm.so.6的软链接导致sudo ls 等命令失效的解决方法

1. 背景 运行一个binary 应用程序, 提示报错: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.27 not found (required by 我的应用程序string 里一下符号标, 确实没有然后下载了一个 glibc-2.27, 安装到 usr/local/下, 并将 libm-2.27.so 和 libc-2.27.so 复制到 /lib/x8…

点云的处理

一、激光点云 激光点云指的是由三维激光雷达设备扫描得到的空间点的数据集&#xff0c;每一个点云都包含了三维坐标&#xff08;XYZ&#xff09;和激光反射强度&#xff08;Intensity&#xff09;&#xff0c;其中强度信息会与目标物表面材质与粗糙度、激光入射角度、激光波长以…

基于imx8m plus开发板全体系开发教程3:Ubuntu 20.04 编译

前言&#xff1a; i.MX8M Plus 开发板是一款拥有 4 个 Cortex-A53 核心&#xff0c;运行频率 1.8GHz;1 个 Cortex-M7 核心&#xff0c;运行频率 800MHz;此外还集成了一个 2.3 TOPS 的 NPU&#xff0c;大大加速机器学习推理。 全文所使用的开发平台均为与NXP官方合作的FS-IMX8…

websorm启动vue项目修改内容后自动运行内存溢出

手动启动vue项目正常运行&#xff0c;修改部分内容保存后会自动重新run一下&#xff0c; 这个时候就报错内存溢出&#xff0c;然后很悲伤的需要再手动重启一下。 &#xff08;在网上查了好多方法就不单独加链接了&#xff09; 前3个方法都试过对于我的项目无效&#xff0c;第4…

017 - C++ 中的静态(static)

本期我们将讨论 C 中的 static&#xff08;静态&#xff09;。 static 关键字在 C 中有两个意思&#xff0c;这个取决于上下文。 简单了解 第一种情况是在类或结构体外部使用 static 关键字&#xff0c;另一种是在类或者结构体内部使用 static。 基本上我们可以这样理解&am…

背包问题基础与应用

背包问题 理论基础 01背包 背包中的每个物品只能用一次 物品编号重量价值物品1115物品2320物品3430 定义&#xff1a;dp[i][j]表示从下标0-i的物品中任取&#xff0c;放进容量为j的背包的最大价值 初始化&#xff1a; dp [[0] * (bag_size 1) for _ in range(len(weigh…