当前位置: 首页 > ds >正文

Linux进程间通信----管道

进程间通信的概念

两个进程之间可以直接进行数据的传递吗?答案是不能,因为进程具有独立性,每个进程有独立的地址空间和资源,防止一个进程直接访问或干扰另一个进程的数据和状态,所以不能直接通信。

进程间通信是什么?

进程间通信最朴素的概念就是一个进程把自己的数据能够交给另一个进程,让进程之间能够进行数据交互

为什么进程间通信?

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

怎么做进程间通信?

由于进程具有独立性,所以进程间的通信需要花费成本。

所以在不破坏进程独立性的前提条件下,为了完成进程间通信我们需要借助第三方资源。

也就是说需要让不同进程看见同一份资源(一般由OS提供)。

一般规律:

        1.首先要有进程交换数据的空间

        2.这块空间不能由进程双方任意一方提供

 具体做法:

        OS提供的空间有不同样式,这就决定了不同的通信方式。

        1.管道(匿名和命名管道)

        2.共享内存

        3.消息队列

        4.信号量

下面我们针对管道进行讲解。

管道

认识管道:

什么是管道?作为进程间通信的一种方法,管道肯定也为不同进程之间提供了一个共享资源。

这个资源就是管道文件。

我们看到下面这个场景:一个文件能被同一个进程打开多次吗?答案是可以,比如显示器文件就有两个默认的stdout和stderr两个文件流。当父进程打开同一个文件两次,那么就会在父进程的文件描述符表中有两个指向同一个文件的文件描述符,我们假设是3和4,此时父进程再创建子进程,父子进程资源共享,子进程也获得了两个指向同一个文件的文件描述符3和4

此时我们的两个父子进程就相当于看见了同一份资源(文件),父进程先用w方式打开文件形成fd=3,再用r方式打开形成fd=4,子进程也继承这些,于是父子进程就可以向文件里进行数据的交换了。但是为了不会出现同时读或者同时写的情况,我们规定父进程写,子进程读,在这种情况下进行进程间通信。

关闭掉父进程的读端和子进程的写端:

匿名管道:

匿名管道之所以匿名是因为匿名管道创建的文件不会出现在磁盘,而是一个内存级文件 (只创建struct file结构对象而不在磁盘中创建文件,这个对象正常和fd数组连接,只能通过fd找到,没有文件名所以匿名。

匿名管道如何让不同进程看到同一份资源的呢?

创建子进程,子进程会继承父进程相关信息

匿名管道:只能继承具有血缘关系的进程,进行进程间通信,常见于父子进程。     

下面通过代码来验证匿名管道:

管道的接口

int pipe(int pipefd[2])

其中的参数pipefd[2]是一个输出型参数,什么是输出型参数-----通过函数将数据放到参数中。

通过pipe函数我们得到管道的读端和写端和fd存入pipefd[2]中

pipefd[0]代表读端

pipefd[1]代表写端

 函数返回值:

成功返回0,失败返回-1,错误码被设置。

管道的编码实现

#include <iostream>
#include <unistd.h>
#include<string.h>
#include <sys/wait.h>
using namespace std;int main()
{int pipefd[2];int n = pipe(pipefd);char buff[1024];if (n < 0){cerr << "pipe error:" << errno << endl;return 1;}pid_t id = fork();if (id == 0){close(pipefd[1]); // 关闭子进程写ssize_t byte_read=read(pipefd[0],&buff,sizeof(buff));if(byte_read== -1){cerr<<"read error"<<endl;}cout<<"child read:' "<<buff<<" 'from father."<<endl;close(pipefd[0]);//子进程完成任务关闭读端}else{close(pipefd[0]); // 关闭父进程读const char* msg="Hello child I am father";ssize_t byte_write=write(pipefd[1],msg,strlen(msg)+1);if (byte_write == -1) {cerr<<"write"<<endl;return 1;}close(pipefd[1]);//父进程完成任务关闭写端wait(nullptr);}return 0;
}

输出结果:

代码说明

  1. 管道创建: 使用pipe()系统调用创建管道,返回两个文件描述符,pipefd[0]用于读,pipefd[1]用于写。
  2. 进程创建: 使用fork()创建子进程,父子进程会继承管道的文件描述符。
  3. 关闭未使用的端: 子进程关闭写端,父进程关闭读端,避免资源泄漏。
  4. 通信过程:
    • 父进程通过write()向管道写入数据。
    • 子进程通过read()从管道读取数据并打印。
  5. 资源清理: 使用完毕后关闭所有文件描述符,父进程等待子进程结束。

操作系统是否允许进程直接访问管道?

因为管道是内存级文件,内存级文件本质是内核的资源,所以操作系统会允许进程直接访问管道吗?

答案是不会,因为操作系统不相信任何人,所以进程在读写文件的时候必须使用系统调用read和write。父进程通过write把数据写入管道,内核将数据复制到内核空间的环形缓冲区中,子进程接收进程通过read()从缓冲区提取数据。

管道的特性

1、管道内部自带同步与互斥机制。

一次只能被一个进程使用的资源被称为临界资源,管道在同一时间只允许一个进程对其进行读或写操作,所以管道也是一种临界资源。

临界资源需要被保护,如果不保护临界资源,就可能出现一个资源同时被多个进程使用,比如管道同时被同时读写,会导致数据不一致不完整等问题。

为了避免这些问题,内核会对管道操作进行同步和互斥:

  • 同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。
  • 互斥: 一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

2、管道的生命周期随进程

管道本身是依赖产生的内存级文件的,当所有打开该文件的进程退出,文件也随之被释放掉,所以说管道生命周期随进程。

3、管道提供的是流式服务。

对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

  • 流式服务: 数据没有明确的分割,不分一定的报文段。
  • 数据报服务: 数据有明确的分割,拿数据按报文段拿。

4、管道是半双工通信的。

在数据通信中,数据在线路上的传送方式可以分为以下三种:

  1. 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
  2. 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
  3. 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。

 管道的四种特殊情况

1.管道内没有数据,同时写端没有关闭。

这种情况下读端就会一直阻塞等待直到管道内有数据。

2.管道内部被写满,读写端都正常不关闭

这种情况下写端就会阻塞。

3.写端写完内容并且关闭写端fd。

这种情况下读端会将管道内数据读完,最后读到返回值为0,表示读结束,类似读到文件结尾EOF。

4.读端关闭不读了,写端还在写。

这种情况下OS会直接终止写端进程,通过信号13)SIGPIPE杀掉进程。

前面两种情况就能很好说明管道是同步互斥的,不会出现读取空管道或者向写满的管道写数据。

第三种情况也很好理解,对于没有写端的管道,将内容读取完后读端就会继续执行自己的其他操作,不会被挂起。

第四种情况,管道内的数据都没人读了所以写入也没有意义,操作系统就直接杀掉进程,写端进程还没执行完代码就结束属于异常退出所以会收到信号。

接下来通过这段代码来验证第四种情况下写端进程接收到的信号:

int main()
{int fd[2] = { 0 };if (pipe(fd) < 0){ //使用pipe创建匿名管道perror("pipe");return 1;}pid_t id = fork(); //使用fork创建子进程if (id == 0){//childclose(fd[0]); //子进程关闭读端//子进程向管道写入数据const char* msg = "hello father, I am child...";int count = 10;while (count--){write(fd[1], msg, strlen(msg));sleep(1);}close(fd[1]); //子进程写入完毕,关闭文件exit(0);}

 通过kill -l命令查看13号信号是什么:

管道的大小 

方法一:通过man手册我们可以查到管道的大小

方法二:使用ulimit命令

512bytes * 8 =4kb

方法三:代码测试

根据前面的情况,读端一直不读,写端一直写,管道被写满就会将写端挂起,我们可以靠这个得到管道的大小。

int main()
{int fd[2] = { 0 };if (pipe(fd) < 0){ //使用pipe创建匿名管道perror("pipe");return 1;}pid_t id = fork(); //使用fork创建子进程if (id == 0){//child close(fd[0]); //子进程关闭读端char c = 'a';int count = 0;//子进程一直进行写入,一次写入一个字节while (1){write(fd[1], &c, 1);count++;printf("%d\n", count); //打印当前写入的字节数}close(fd[1]);exit(0);}//fatherclose(fd[1]); //父进程关闭写端//父进程不进行读取waitpid(id, NULL, 0);close(fd[0]);return 0;
}

进程阻塞在65536,管道最大字节是65536.

http://www.xdnf.cn/news/9714.html

相关文章:

  • 人员睡岗检测算法AI智能分析网关V4打造工业/安防/交通等多场景应用方案
  • VMware安装Ubuntu实战分享大纲
  • Apifox 5 月产品更新|数据模型支持查看「引用资源」、调试 AI 接口可实时预览 Markdown、性能优化
  • 蓝牙芯片投影仪遥控器方案
  • 网络出版服务许可证年检
  • MySQL数据库学习笔记
  • openFuyao开源发布,建设多样化算力集群开源软件生态
  • 【大模型】Bert
  • 计算机网络 | 1.1 计算机网络概述思维导图
  • Nginx代理、缓存与Rewrite
  • 使用LSTM进行时间序列分析
  • 流程自动化引擎:让业务自己奔跑
  • C++031(变量的存储类型-auto变量)
  • 塔能空化泵节能方案:工厂能耗精准控制的革新之选
  • 博图SCL基础知识-寻址调用及新建SCL
  • 记一次前端逻辑绕过登录到内网挖掘
  • 计算机内存管理全解析:从基础原理到前沿技术(含分页/分段/置换算法/大页/NVM/CXL等技术详解
  • C++ explicit关键字有什么作用
  • Dify+MCP Server打造禅道AI智能助手
  • LeetCode 136:只出现一次的数字 - 巧用异或运算的极致解法
  • Open3D上可视化Nuscenes 数据集
  • 谷歌浏览器Google Chrome v137.0.7151.41 中文版本版+插件 v1.11.1
  • 【Echarts】象形图
  • : influxdb + grafana+JMeter
  • 自回归建模模型(AR)
  • C++进阶--C++11(03)
  • 一种字典树的Python实现
  • 什么是数字化转型,如何系统性重构业务逻辑
  • Android 构建系统中常见的 .mk 文件及其作用
  • 涨薪技术|0到1学会性能测试第88课-Web_service_call函数