进程间通信——管道

news2024/11/27 13:45:17

文章目录

  • 进程间通信的介绍
    • 进程间通信的目的
    • 进程间通信的本质
  • 匿名管道
    • 创建管道
    • 匿名管道的特征
  • 命名管道
    • 小结

进程间通信的介绍

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

进程间通信的目的

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

进程间通信的本质

进程间通信的本质就是,让不同的进程看到同一份资源。

由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以私有也可以公有(例如父子进程),因此各个进程之间要实现通信是非常困难的。

各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。

操作系统提供的这一段内存区域被我们称为:公共资源。

公共资源有了,还必须让要通信的进程都看到这一份公共资源,此时要通信的进程将有了通信的前提。之后就是进程通信,也就是访问这块公共资源的数据。

之所以有不同的通信方式,是因为公共资源的种类不能,如果公共资源是一块内存,那么通信方式就叫做共享内存,如果公共资源是一个文件,也就是struct file结构体,那么就叫做管道。

首先我们来回忆一下文件系统
在这里插入图片描述
父进程打开一个文件,操作系统在内存上创建一个struct file结构体对象,里面包含文件的各种属性,以及对磁盘文件的操作方法。

每个struct file对象中还有一个内核缓冲区,这个缓冲区中可以存放数据。

当子进程创建的时候,父进程的文件描述符表会被子进程继承下去,所以此时子进程在相同的fd处也指向父进程打开的文件。

文件描述符表一个进程维护一个,但是struct file结构体对象在内存中只有一个,由操作系统维护。

此时,父子进程将看到了同一份公共资源,也就是操作系统在内存中维护的struct file对象,并且父子进程也都和这份资源建立了连接。

此时父子进程通信的基础有了,它们就可以通信了。
父进程向文件中写内容,写完后继续干自己的事,并不破坏父进程的独立性。
子进程向文件中读内容,读完后继续干自己的事,并不破坏子进程的独立性。

这样一读一写,父子进程将完成了一次进程间通信。

而我们又知道,对文件进行IO操作时,由于需要访问硬盘,所以速度非常的慢,而且我们发现,父子间进行通信,磁盘中文件的内容并不重要,重要的是父进程写了什么,子进程又读到了什么。

此时操作系统为了提高效率,就关闭了内存中struct file和硬盘中文件进行IO的通道。
父进程写数据写到了struct file的内核缓冲区中。
子进程读数据从struct file的内核缓冲区中读取。

此时,父子间通信仍然正常进行,并且效率还非常的高,而且还没有影响进程的独立性。而这种不进行IO的文件叫做内存级文件。

这种由文件系统提供公共资源的进程间通信,就叫做管道。
在这里插入图片描述
进程A和B就通过管道建立起了连接,并且可以进程进程之间的通信。而管道又分为匿名管道和命名管道。

匿名管道

匿名管道:顾名思义,就是没有名字的文件(struct file)。 匿名管道只能用于父子进程间通信,或者由一个父进程创建的兄弟进程之间进行通信。

现在我们知道了匿名管道就是没有名字的文件,通过管道进行通信时,只需要通信双方打开同一个文件就可以。

我们通过系统调用open打开文件的时候,会指定打开方式,是读还是写。

当父进程以写方式打开一个文件的时候,创建的子进程会继承父进程的一切。
此时子进程也是以写的方式打开的这个文件。

既然是通信,势必有一方在写,一方在读,而现在父子双方都是以写的方式打开,它们怎么进行通信呢?

父进程以读和写的方式打开同一份文件两次。
在这里插入图片描述
此时的管道文件分为写端和读端,并且写端和读端各会返回一个文件描述符fd。所以父进程的文件描述符表中,和管道文件有关的文件描述符fd就有两个。
在这里插入图片描述
这样一来,创建子进程后,父子进程都可以对管道进行读和写,它们就可以进行通信了,上面的问题就解决了。

之所以命名为管道,那么就有和管道类似的性质。在生活中,我们对水管,它的流向只能是单向的,管道也一样,通过管道建立的通信只能进行单向数据通信。

是因为通过内存级文件通信的方式具有这种特点,才命名的管道。
而不是先命名管道才设计的内存级文件通信方式。
在这里插入图片描述

  • 为了防止父进程对管道进行误读,以及子进程对管道进行误写,破坏通信规则。
  • 将父进程的读端关闭,将子进程的写端关闭,使用系统调用close(fd)。

按照上面的操作,父进程进行读的操作,子进程读取父进程传输过来的数据

创建管道

建立管道的系统调用pipe
上面都是理论上的,具体到代码中是如何建立管道的呢?既然是操作系统中的文件系统提供的公共资源,当然是用系统调用来建立管道了。
在这里插入图片描述

  • 形参:int pipefd[2]是一个输出型参数,是一个数组,该数组只有两个元素,下标分别为0和1。
  • 下标为0的元素表示的是管道读端的文件描述符fd。
  • 下标为1的元素表示的是管道写端的文件描述符fd。

使用系统调用pipe,直接就会得到两个fd,并且放入父进程的文件描述符表中,不用打开内存级文件两次。

  • 返回值:int类型的整数,对管道创建情况进行反馈。
  • 返回0,表示管道创建成功。
  • 返回-1,表示管道创建失败,并且会将错误码自动写入errno中。
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>

int main()
{
    int fds[2];
    int ret = pipe(fds);
    if(ret < 0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
    std::cout<<"fds[0]: "<<fds[0]<<std::endl;
    std::cout<<"fds[1]: "<<fds[1]<<std::endl;

    return 0;
}

在这里插入图片描述
可以看到,创建管道后返回的两个fd值,果然是3和4,因为0,1,2分别被stdin,stdout,stderr占用。

知道了如何使用系统调用创建管道以后,接下来就创建子进程,然后关闭不需要的端口了,原理已经清楚,直接看代码。

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

#define MAX 1024

using namespace std;
//子写 父读
int main()
{
    int status = 0;
    //1、建立管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    //pipe函数成功返回0
    assert(n == 0);
    (void)n;  //没有用到n,防止编译器告警
    cout << "pipefd[0]" << pipefd[0] << " " << "pipefd[1]" << pipefd[1] << endl;
    //2、创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    if(id == 0)  //子进程
    {
        close(pipefd[0]);
        int cnt = 10;
        while(cnt)
        { 
            char message[MAX]; 
            snprintf(message, sizeof(message), "hello, I am child,pid: %d, cnt : %d",getpid(), cnt);
            cnt--;
            write(pipefd[1], message, strlen(message));
            sleep(1);
        }

        exit(0);
    }
    //父进程  返回子进程id
    close(pipefd[1]);
    char buffer[MAX]; 
    while(1)
    {
        ssize_t n  = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            cout << "child say:" << buffer << "to father" << endl;
        }
        else break;
    }

    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        cout << "wait sucess" << endl;
    }
    return 0; 
}

上面的管道实现的是子进程进行写,父进程进行读
在这里插入图片描述

匿名管道的特征

  • 写入快,读取慢,write调用阻塞,直到有进程读走数据。
  • 写入慢,读取块,read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • 管道写端对应的文件描述符被关闭,则read返回0。
  • 管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,让write进程退出。

命名管道

命名管道:顾名思义,有名字的管道(内存级文件)。

如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

注意:普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。

命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

  • 指令:mkfifo 文件名
  • 功能:创建命名管道文件

在这里插入图片描述
如上图所示,此时就创建了一个命名管道,可以看到,文件类型是p,而且该文件还有inode,说明在磁盘上是真实存在的。

当磁盘中有了命名管道文件以后,两个进程将可以通过这个管道文件进行通信了,步骤和匿名管道非常相似。

一个进程以写方式打开管道文件。
另一个进程以读端方式打开管道文件。
此时两个进程将建立了连接,然后将可以进行通信了。

我们知道,进程间通信的前提是,要通信的进程能够看到同一份公共资源,那么命名管道是如何做到这一点的呢?

让不同的进程打开指定路径下同一个管道文件。

路径 + 文件名 = 唯一性

所以说,命名管道是通过利用这种唯一性来让要通信的进程都看到这块内存级文件的。

命名管道的系统调用mkfifo/unlink
创建管道文件:
可以在shell中通过命令的方式创建管道文件,两个进程直接去使用它。也可以像文件一样,在进程中创建管道文件,此时就需要用到系统调用。
在这里插入图片描述

  • 第一个形参:管道文件的名字
  • 第二个形参:创建管道文件的权限
  • 返回值:0表示创建成功,-1表示创建失败。
    在这里插入图片描述
    在这里插入图片描述此时就有了这样一个管道文件,结果和使用命名mkfifo的结果是一样的。

再次运行程序,就会报错,管道文件已经存在,所以说,如果管道文件已经存在了,就没有必要再使用系统调用mkfifo。

删除管道文件:

向管道文件中写如数据,这些数据是不会IO到磁盘中的。
在这里插入图片描述
在这里插入图片描述
让程序开始疯狂向管道文件中写入内容,再查看管道文件,发现文件的大小没有变化。

和匿名管道一样,向命名管道写文件时,不会和磁盘进行IO,而是将数据写到了struct file结构体的缓冲区中,数据写入了内核中。

这样看来,命名管道文件我们能不能看到不重要。
可以在使用完管道文件以后,再将管道文件删除。

在这里插入图片描述

  • 形参:要删除的管道文件名称(路径加名字)
  • 返回值:删除成功返回0,失败返回-1。

通信代码及演示
我们创建两个进程进行通信,一方叫做sever,另一方叫做client,sever负责创建管道文件,并且从管道中读取数据,client负责向管道中写入数据。

sever.c代码:
在这里插入图片描述
创建好管道文件以后,使用系统调用open以写的方式打开文件,再通过系统调用read读取管道中的数据。

client.c代码:
在这里插入图片描述
在server.c创建好管道文件以后,再使用open以写方式打开管道文件,再通过write将从键盘上获取的数据写入到管道文件中。

运行效果:
在这里插入图片描述
client输入什么,sever就输出什么,此时两个无关的进程就成功进行了通信。
有了匿名管道的基础,命名管道就很简单了,不同之处只在于需要创建管道文件和打开管道文件,而匿名管道的pipe系统调用直接就将管道文件创建好并且打开了。其他的操作都一样。

小结

匿名管道和命名管道的区别:

匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开用open,删除用unlink函数。
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

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

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

相关文章

算法刷题:和为s的两个数

和为s的两个数 .题目链接题目详情算法原理我的答案 . 题目链接 和为s的两个数 题目详情 算法原理 这里我们是利用单调性来使用双指针的对撞指针来解决问题 因为数组给的是有序递增的,因此我们设置两个指针left和right来解决问题,当nums[left]与nums[right]相加会有三种情况:…

apple iCloud photo close

关闭掉就不当心图片上传到服务器&#xff08;暗地里有没有执行上传就不知道了&#xff09;&#xff0c;然后接电脑存放还要从服务器上下载很麻烦&#xff0c;但是你要确保自己手机内存卡足够多 关闭iCloud会提示&#xff0c;从服务器下载图片

混合键合(Hybrid Bonding)工艺解读

随着半导体技术的持续演进&#xff0c;传统的二维芯片缩放规则受到物理极限的挑战&#xff0c;尤其是摩尔定律在微小化方面的推进速度放缓。为了继续保持计算性能和存储密度的增长趋势&#xff0c;业界开始转向三维集成电路设计与封装技术的研发。混合键合技术就是在这样的背景…

算法学习——LeetCode力扣贪心篇4

算法学习——LeetCode力扣贪心篇4 763. 划分字母区间 763. 划分字母区间 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xf…

第13集《佛说四十二章经》

和尚尼慈悲&#xff01;诸位法师、诸位居士&#xff0c;阿弥陀佛&#xff01; 请大家打开讲义第十八面&#xff0c;第三十六章、辗转获胜。 佛陀在《法华经》上&#xff0c;对我们的三界果报&#xff0c;清楚的开示说&#xff1a;三界无安&#xff0c;犹如火宅&#xff0c;众…

地理信息数据要素价值

文章目录 前言一、地理信息数据成为生产要素的重要内涵二、推动地理信息数据要素价值实现的重点及方式(一)公共地理信息数据(二)企业地理信息数据前言 面对数字经济发展新形势新需求,我们将统筹发展与安全,推动数据资源向数据资产转变,发挥时空数据作为新型生产要素价值…

Windows 系统盘(C盘)爆红如何清理、如何增加C盘空间

1、简介 Windows系统中&#xff0c;系统和保留占用太多的空间&#xff0c;一旦系统盘分配空间较少&#xff0c;使用一段时间后&#xff0c;备份文件、临时文件、系统更新记录等都会在占用系统盘较大空间&#xff0c;导致系统盘空间不够使用&#xff0c;会造成应用运行卡顿。如何…

uniapp 开发一个密码管理app

密码管理app 介绍 最近发现自己的账号密码真的是太多了&#xff0c;各种网站&#xff0c;系统&#xff0c;公司内网的&#xff0c;很多站点在登陆的时候都要重新设置密码或者通过短信或者邮箱重新设置密码&#xff0c;真的很麻烦 所以准备开发一个app用来记录这些站好和密码…

每日一题——LeetCode1436.旅行终点站

方法一 个人方法 两次遍历set 终点站不通往其他任何城市&#xff0c;那么终点站只会出现在[cityA,cityB]的第二位&#xff0c;利用set第一次遍历保存所有站点&#xff0c;第二次遍历去除所有在第一位出现的站点&#xff0c;剩下的站点就是不通往任何站点的终点站&#xff1a; …

Java中的String类的常用方法(对于字符串的常用操作)

目录 一、获取指定索引的字符 二、 获取指定字符或者字符串的索引位置 三、判断字符串是否以指定内容开头或结尾 四、替换指定的字符或者是字符串 五、获取字符串的子串 六、将字符串转换为字符数组 七、比较字符串的内容是否相等 八、连接字符串 九、比较两个字符串的大…

[CTF]-PWN:C++文件更换libc方法(WSL)

C文件与C文件更换libc有很多不一样的地方&#xff0c;我是在写buu的ciscn_2019_final_3才意识到这个问题&#xff0c;C文件只需要更换libc和ld就可以了&#xff0c;但是C文件不同&#xff0c;除了更换libc和ld&#xff0c;它还需要更换libstdc.so.6和libgcc_s.so.1 更换libc和…

指针习题回顾(C语言)

目录 数组指针和指针数组 编程题&#xff1a; 字符串逆序 字符串左旋 题目1概述&#xff1a; 代码实现&#xff1a; 题目2概述&#xff1a; 代码实现&#xff1a; 调整奇偶顺序 题目概述&#xff1a; 代码实现&#xff1a; 冒泡排序 二级指针 代码解读&#xff1a; …

力扣---通配符匹配

题目描述&#xff1a; 给你一个输入字符串 (s) 和一个字符模式 (p) &#xff0c;请你实现一个支持 ? 和 * 匹配规则的通配符匹配&#xff1a; ? 可以匹配任何单个字符。 * 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09;。 判定匹配成功的充要条件是&#xff…

lazarus:LCL 嵌入 fpwebview 组件,做一个简单浏览器

从 https://github.com/PierceNg/fpwebview 下载 fpwebview-master.zip 简单易用。 先请看 \fpwebview-master\README.md cd \lazarus\projects\fpwebview-master\demo\lclembed 修改 lclembed.lpr 如下&#xff0c;将 fphttpapp. 注释掉&#xff0c;因为我用不上。 progr…

【STM32 CubeMX】I2C层次结构、I2C协议

文章目录 前言一、I2C的结构层次1.1 怎样在两个设备之间传输数据1.2 I2C如何传输数据1.3 硬件框图1.4 软件层次 二、IIC协议2.1 硬件连接2.2 I2C 总线的概念2.3 传输数据类比2.3 I2C信号2.4 I2C数据的含义 总结 前言 在STM32 CubeMX环境中&#xff0c;I2C&#xff08;Inter-In…

相机图像质量研究(24)常见问题总结:CMOS期间对成像的影响--摩尔纹

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

红队学习笔记Day5 --->总结

今天先不讲新知识&#xff0c;来小小的复习一下 1.8888&#xff1f;隧道端口你怎么回事 在做隧道和端口转发的时候&#xff0c;我们常见的是通过一台跳板机&#xff0c;让外网的机器去远程连接到内网的一些机器&#xff0c;这时候就常见一些这样的命令 以防忘了&#xff0c;先…

面试突击1

1.当线程没有拿到资源时&#xff0c;用户态和内核态的一个切换 在操作系统中&#xff0c;进程和线程是执行程序的基本单位。为了管理这些单位&#xff0c;操作系统使用了一种称为“进程状态”的机制&#xff0c;其中包括用户态和内核态两种状态。这两种状态代表了进程或线程在…

ubuntu屏幕小的解决办法

1. 安装vmware tools , 再点自适应客户机 执行里面的vmware-install.pl这个文件 &#xff1a;sudo ./vmware-install.pl 执行不了可以放到家目录&#xff0c;我放在了/home/book 里面 最后点这个自适应客户机 然后我这里点不了是因为我点了控制台视图和拉伸客户机&#xff0c…

[word] word怎么取消隐藏文字 #职场发展#微信

word怎么取消隐藏文字 Word有很多实用的技巧&#xff0c;学会了可以节省大量的时间在编辑上。今天就给大家分享下word怎么取消隐藏文字这个小技能。 1.选中内容设置 首先先显示段落符号标记(快捷鍵Ctrl Shift8)&#xff0c;之后选中文本内容。 2.设置取消隐藏的文字 点击开始…