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

img

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。

本篇导航

  • 0. 进程间通信原理
  • 1. 匿名管道
    • 1.1 通信原理
    • 1.2 接口介绍
  • 2. 命名管道
    • 2.1 接口介绍
  • 3. 共享内存
    • 3.1 通信原理
    • 3.2 接口介绍

在这里插入图片描述

0. 进程间通信原理

进程间的是相互独立的。那么想要让两个进程间进行通信,本质是让其看到同一份资源。因为进程具有独立性,所以大多时候让两个或多个进程看到同一份资源是最费力的

根据看到资源的方式不同,将进程通信划分为以下几种:

  1. 匿名管道通信
  2. 命名管道通信
  3. 共享内存

其中,匿名管道通信与命名管道通信的本质都是让进程看到同一份内存级文件

内存级文件是一个仅存储在内存中的文件.不会刷新到磁盘中

1. 匿名管道

1.1 通信原理

管道实际上是一份内存级文件,其被创建出来,通过文件的方式去访问.

内存模型如下:

image-20231211213419971

其中file_r为写缓冲区,file_w为读缓冲区(缓冲区本质上也为一个内存级文件)

创建管道时,系统会为其分配两个fd.一个为读端,一个为写端.但是 管道只能进行单向通信.

为了方便控制,通常情况下,我们会手动关闭我们不需要的那个fd.(以下为了方便测试,规定由父进程写读,子进程写)

那么在匿名管道通信时如何让多个进程看到同一份内存级文件呢?

子进程会继承父进程的大多数资源,file_struct也在其中.但因为操作系统节省资源的特性,文件并不会被创建多份

所以可以通过创建子进程的方法来让多个进程看到同一份资源

所以 匿名管道的特点之一:仅能在有关系的进程中进行通信(父子进程,兄弟进程)

创建一个子进程时,内存模型如下:

image-20231211214629837

(通信本质是让不同进程看到同一份资源,所以资源的准备需要在进程创建之前!!!)

此时子进程也能够访问这个内存级文件了.这时双方就可以根据fd,按照访问文件的方式去访问这个内存级文件.也就是可以进行通信

1.2 接口介绍

创建匿名管道使用的函数为 int pipe(int pipefd[2])

image-20231211215326099

image-20231211215432856

其中 **int pipefd[2]**为输出型参数 pipefd[0]为读端,pipefd[1]为写端

该接口创建完管道,并为用户分配所需读写端的fd,将其存入该数组后返回给用户.

如果创建成功则返回0,如果创建失败则返回-1,同时设置errno

这是一份简单的管道通代码.创建管道需要在创建子进程前才能被共享到!

#include<iostream>
#include<unistd.h>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>
#include<string>
using namespace std;
#define N 2
#define NUM 1024void Read(int rfd)
{while (true) {char buffer[1024];int n=read(rfd,buffer,sizeof(buffer));if(n<=0){cout<<"wait write into pipe"<<endl;}else {cout<<buffer;}}
}
void Write(int wfd)
{string example="i am a child,hello linux communitate  ";pid_t self=getpid();example+=to_string(self);int flag=example.size();int cnt=0;while(true){example.erase(flag);example+=" "+to_string(cnt++)+"\n";int n=write(wfd,example.c_str(),example.length());if(n<=0){cout<<"pipe close"<<endl;break;}sleep(1);}
}int main()
{int pipefd[2];pipe(pipefd);if(pipe(pipefd)<0){perror("create pipe failed\n");}cout<<"0: "<<pipefd[0]<<endl;cout<<"1: "<<pipefd[1]<<endl;pid_t id = fork();if(id==0) //0 read 1 write{//writeclose(pipefd[0]);Write(pipefd[1]);}else {//readclose(pipefd[1]);Read(pipefd[0]); }return 0;
}

image-20231211220606452

完成了匿名管道的通信.

管道通信为单向的.读端会将读取的内容从管道中取走.先写入的数据会被先取走(与队列的原理相似)


父进程会随着子进程发送信息的频率而读取信息.(上文写端进行了休眠,而读端并没有)

所以:

读写端正常.当管道中没有内容时,读端会阻塞等待


如果我们重复的写入一段内容而不读取呢?

void Write(int wfd)
{string example="i am a child,hello linux communitate  ";pid_t self=getpid();example+=to_string(self);int flag=example.size();int cnt=0;while(true){example.erase(flag);example+=" "+to_string(cnt++)+"\n";int n=write(wfd,example.c_str(),example.length());if(n<=0){cout<<"pipe close"<<endl;break;}cout<<cnt<<endl;;}
}

将写端逻辑做出如上更改,当写不进去时,输出 “pipe full”;

void Read(int rfd)
{while(true){};while (true) {char buffer[1024];int n=read(rfd,buffer,sizeof(buffer));if(n<=0){cout<<"wait write into pipe"<<endl;}else {cout<<buffer;}}
}

将读端做出如上更改.手动阻塞进程

image-20231211222740715

观察到写端阻塞,等待读端读取

所以

读写端正常.当管道写满时,写端会阻塞等待读端读取


将写端设置为一段时间后自动关闭.读端不会被阻塞.read返回0,可以根据这个特性做出行为

所以

写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞


将读端设置为一段时间后自动关闭.为了节省资源.写端将被操作系统关闭

void Read(int rfd)
{int cnt=5;while (cnt>0) {char buffer[1024];int n=read(rfd,buffer,sizeof(buffer));if(n<=0){cout<<"wait write into pipe"<<endl;}else {cout<<buffer;}cnt--;}cout<<"read close"<<endl;
}
//main 中修改的部分//readclose(pipefd[1]);Read(pipefd[0]);close(pipefd[0]);int status=0; waitpid(id,&status,0);cout<<"receive signal : "<<(status& 0x7f)<<endl;

image-20231212125908227

收到13号信号,进程被终止.13号信号为SIGPIPE

image-20231212125942952

所以

读端被关闭.写端也被关闭


综上,匿名管道通信时有四种情况:

  1. 读写端正常.当管道中没有内容时,读端会阻塞等待
  2. 读写端正常.当管道写满时,写端会阻塞等待读端读取
  3. 写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞
  4. 读端被关闭.节省资源.写端也被关闭

所以我们可以得到匿名管道有以下特征:

  1. 具有血缘关系的进程才可以进行通信
  2. 管道只能单向通信
  3. 父子进程是会进程协同的,同步与互斥
  4. 管道是面向字节流的
  5. 管道基于内存级文件.其生命周期随进程

2. 命名管道

与匿名管道大同小异.都是基于文件级的通信,但是命名管道在指定路径下创建了一个具有名称的内存级文件.

这使得 没有血缘关系的进程也能够看到同一份资源,所以此时,不同的进程也可以进行通信了

2.1 接口介绍

我们可以使用mkfifo 依照创建文件的方法,在指定目录下创建出内存级文件.

image-20231212131141204

其表示文件属性的权限位,显示其为一个管道文件.

删除这个管道文件我们通常使用unlink

image-20231212131253908

在语言层面上,也为我们封装了该接口

image-20231212131352469

pathname:为指定路径 mode:为权限

#pragma once
#include <sys/stat.h>
#include <unistd.h>
#include"log.hpp"
#define FIFO_PATH "./myfifo"
class InitPipe{
public:InitPipe(){int n=mkfifo(FIFO_PATH,MODE);if(n!=0){log(FATAL,"create pipe failed");exit(0);}}~InitPipe(){unlink(FIFO_PATH);}
};

接口使用

为了避免每次退出进程时,还要去手动释放该管道文件.所以利用RAII的方式来存储管道文件

之后使用该管道时,根据读写文件那一套来即可.

sever.cpp:

#include"log.hpp"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "create_pipe.hpp"
int main(int argc,char * argv[])
{InitPipe pipe;Log log(SCREEN|CLASS_FILE);int fd=open(FIFO_PATH,O_RDONLY);if (fd < 0){log(FATAL, "error string: %s, error code: %d", errno, errno);}while(true){char sz[1024]={0};int x=read(fd,sz,sizeof(sz));if(x > 0){cout<<"client say# "<< sz <<endl;log(INFO,sz);}else if(x==0){break;}else break;}}

client.cpp:

#include <fcntl.h>
#include<unistd.h>
#include<iostream>
#include<string>
using namespace std;
int main()
{int fd=open("./myfifo",O_WRONLY);if (fd < 0){cout<<"failed"<<endl;exit(0);}string s1;while(true){cout<<"client say @";getline(cin,s1);write(fd,s1.c_str(),s1.length());}}

image-20231212132046069

(上文用到的log可以查看我下一篇博客的日志插件)

3. 共享内存

共享内存相较于前两种通信方式,速度上有明显的优势.

其不会涉及到复制写入/读取的内容.而是直接写入内存.而管道是将内容复制到文件当中,在复制出来

3.1 通信原理

共享内存的本质就是将一段真实的 物理内存,映射到PCB的共享内存当中.当多个进程映射同一个物理内存时,通过对内存数据直接的读写,就可以实现通信.

同样,我们需要使用系统调用接口去申请这段共享内存.使用系统调用接口去释放这段共享内存

共享内存没有像管道一样的同步互斥,需要用户自己去规定.

3.2 接口介绍

申请共享内存:

image-20231212133159479

key:可以理解为申请共享内存的一段密钥,该密钥在系统中是唯一的,就可以申请到唯一的一块共享内存,也是系统内核去校验两个共享是否相同的一个手段

通过ftok去申请:

image-20231212133445721

该函数是一个算法结合两个参数去生成一个唯一的key.所以这两个参数可以根据使用情况去定制.

若申请成功,则返回key,若申请失败则返回-1,并设置errno.

size:为申请的共享内存大小,一般为4096的整数倍.

shmflg具有以下两个值:

  1. IPC_CREAT (申请一段共享内存,若不存在则创建并返回shmid,若存在则返回shmid(用户级的key))
  2. IPC_CREAT | IPC_EXEL| 八进制权限信息 (申请一段共享内存,若不存在则创建并返回shmid,若存在则创建失败) 需要带上权限信息

为什么会有第二个选项呢?用来保证你申请的共享内存是一段新的,唯一被您使用的内存

shmid是什么?与上文的key类似,内核使用key去操作控制共享内存,而用户通过shmid完成如上操作

返回值为 成功返回shmid,失败返回-1,并设置errno

image-20231212134537899

获取共享内存地址.

char * address = (char *)shmat(shmid,nullptr,0);

image-20231212220619538

**取消挂接该地址.**若成功则返回0,失败返回-1

image-20231212220746275

对该共享内存进行控制,一般用来删除共享内存

cmd参数填上IPC_RMID 表示删除当前内存

下面是一个简单的示例demo:

config.hpp

#pragma once
#include <cerrno>
#include <cstring>
#include <sys/ipc.h>
#include<sys/types.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
string PATH= "/tmp";
int MODE= 255;
#define SIZE 4096
class Init{
public:Init(){_key=ftok(PATH.c_str(), MODE);if(_key==-1){cout<<"create failed"<<endl;strerror(errno);exit(0);}}int CreateShm(){//需要加上权限 否则创建失败int shmid=shmget(_key,SIZE,IPC_CREAT|IPC_EXCL|0666);if(shmid==-1){cout<<"creatshm failed"<<endl;strerror(errno);exit(0);}return shmid;}int GetShm(){int shmid=shmget(_key,SIZE,IPC_CREAT|0666);if(shmid==-1){cout<<"getshm failed"<<endl;strerror(errno);exit(0);}return shmid;}void destoryShmid(int shmid){if(shmctl(shmid,IPC_RMID, nullptr)!=-1)cout<<"destory success";}private:key_t _key;
};

processaa.cpp 接受方

#include "config.hpp"
#include <cstddef>
#include <sys/shm.h>
#include<iostream>
using namespace std;
int main()
{Init it;int shmid=it.GetShm();cout<<"get success a"<<endl;char * address = (char *)shmat(shmid,nullptr,0);while(true)cout<<address<<endl;
}

processbb.cpp 发送方

#include "config.hpp"
#include<iostream>
#include <cstddef>
using namespace std;
int main()
{Init it;int shmid=it.CreateShm();cout<<"create success b"<<endl;char * address = (char *)shmat(shmid,nullptr,0);cout<<"address success "<<endl;int cnt=5;while (cnt-->0) {fgets(address,4096,stdin);}it.destoryShmid(shmid);    
}

image-20230905164632777

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

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

相关文章

用户访问一个购物网站时TCP/IP五层参考模型中每一层的功能

当用户访问一个购物网站时&#xff0c;网络上的每一层都会涉及不同的协议&#xff0c;具体网络模型如下图所示。 以下是每个网络层及其相关的协议示例&#xff1a; 物理层&#xff1a;负责将比特流传输到物理媒介上&#xff0c;例如电缆或无线信号。所以在物理层&#xff0c;可…

Dell服务器iDRAC9忘记密码, 通过RACADM工具不重启 重置密码

系列文章目录 文章目录 系列文章目录前言一、RACADM工具二、linux环境1.解压安装RACADM工具测试RACADM工具重置iDRAC密码 Windows环境 前言 一、RACADM工具 RACADM工具 官网参考信息 https://www.dell.com/support/kbdoc/zh-cn/000126703/%E5%A6%82%E4%BD%95-%E9%87%8D%E7%BD…

计算机网络-封装成帧透明传输(组帧方法)

文章目录 数据链路层功能概述封装成帧透明传输组帧方法字符计数法字符填充法零比特填充法违规编码法 字符填充法为啥复杂和不兼容 数据链路层功能概述 类似老板让小秘书送文件给别的公司&#xff0c;小秘书告诉傻子怎么把该文件送到别的公司的小秘书&#xff0c;然后别的公司的…

修改UnityEngine dll

修改UnityEngine dll 由于有些版本的dll与热重载并不兼容&#xff0c;需要小幅修改代码。 使用dnspy工具 我们使用 dnspy 来修改 dll文件。而dnspy只能在Win下运行&#xff0c;故哪怕是mac版本dll&#xff0c; 你也得先将相应dll复制到Win下后再修改。下载 dnspy&#xff0c…

FastAdmin青动CRM-E售后

应用介绍 一款基于FastAdminThinkPHP和uniapp开发的CRM售后管理系统&#xff0c;旨在助力企业销售售后全流程精细化、数字化管理&#xff0c;主要功能&#xff1a;客户、合同、工单、任务、报价、产品、库存、出纳、收费&#xff0c;适用于&#xff1a;服装鞋帽、化妆品、机械机…

服务器使用过程中遇到常见故障及解决方案(包括蓝屏死机、无法删除的文件如何清理、网络卡、服务器连接不上等)

互联网时代&#xff0c;服务器的安全性和稳定性尤为重要&#xff0c;支撑着整个互联网行业的信息和数据安全。最近经常有客户咨询服务器的日常故障排除方法。由于服务器复杂的硬件结构和繁琐的运行原理&#xff0c;经常会出现这样那样的问题&#xff0c;有时即使是最小的问题也…

item_get_video-获取视频详情(bili.item_get_video)

B站&#xff08;Bilibili&#xff09;的item_get_video API用于获取视频的详细信息。通过调用该API&#xff0c;您将能够获得视频的基本信息、元数据、播放链接等。这使得开发者可以轻松地将B站视频集成到自己的应用程序或网站中&#xff0c;为用户提供更丰富的内容和更好的体验…

2-12 SDATR的训练与测试

2.12 SDATR的训练与测试 使用环境:3卡服务器SDATR 服务器代码地址:/home/lihuanyu/code/036SDATR 本地代码地址:F:\BaiduNetdiskDownload\code\036SDATR 2.12.1 训练文件修改 输入数据修改 载入词汇修改 短点保存修改 权重保存修改 其他位置修改:

springboot果蔬配送商城

技术架构&#xff1a; java mysql bootstrap jquery mybatis springboot 有需要该项目的小伙伴可以私信我你的Q。 功能介绍&#xff1a; 系统基于Java技术进行开发&#xff0c;后台数据库使用MySQL&#xff0c;在Windows环境下使用idea开发工具进行开发&#xff0c;主…

【计算机网络基础篇】学习笔记系列之二《游览器输入URL后发生了什么?》

文章目录 1&#xff0c;问题提出2&#xff0c;输入URL过程用到的协议3&#xff0c;输入URL过程分析3.1&#xff0c;孤单小弟 - HTTP3.2&#xff0c;真实地址查询 - DNS3.2&#xff0c;指南好帮手 - 协议栈3.3&#xff0c;可靠传输 - TCP3.4&#xff0c;远程定位- IP3.5&#xf…

陶哲轩如何用 GPT-4 辅助数学研究

关于陶哲轩&#xff08;Terence Tao&#xff09;用 GPT-4 进行数学研究的话题始于陶本人在 微软 Unlocked 上发表的 Embracing Change and Resetting Expectations 一文。文中提到&#xff1a; …… I could feed GPT-4 the first few PDF pages of a recent math preprint and…

[学习笔记]刘知远团队大模型技术与交叉应用L6-基于大模型文本理解和生成介绍

介绍 NLP的下游运用可以分为&#xff1a;NLU(理解)和NLG(生成) 信息检索&#xff1a;NLU 文本生成&#xff1a;NLG 机器问答&#xff1a;NLUNLG 大模型在信息检索 大模型在机器问答 大模型在文本生成 信息检索-Information Retrieval (IR) 背景 谷歌搜索引擎目前同时集成了…

Docker 可视化工具

1、Portainer 概念介绍 Portainer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 Portainer分为开源社区版&#xff08;CE版&#xff09;和商用版&#xff08;BE版/EE版&#xff09;。 Porta…

嵌入式学习第十六天!(Linux文件查看、查找命令、标准IO)

Linux软件编程 1. Linux&#xff1a; 操作系统的内核&#xff1a; 1. 管理CPU 2. 管理内存 3. 管理硬件设备 4. 管理文件系统 5. 任务调度 2. Shell&#xff1a; 1. 保护Linux内核&#xff08;用户和Linux内核不直接操作&#xff0c;通过操作Shell&#xff0c;Shell和内核交互…

idea常用设置

1、内存优化 根据自己电脑本身的内存&#xff0c;对idea安装包里bin目录下的idea64.exe.vmoptions文件进行修改 -server -Xms256m -Xmx2048m -XX:MaxPermSize1024m -XX:ReservedCodeCacheSize256m -ea -Dsun.io.useCanonCachesfalse -Djava.Net.preferIPv4Stacktrue -Djsse.e…

CSS:水平垂直居中

公共的 CSS 样式&#xff1a; .parent {width: 300px;height: 300px;background-color:#d0e4fe; }.child {width: 100px;height: 100px;background-color:orange; }HTML: <div class"parent"><div class"child"></div> </div>最…

docker-compose部署laravel项目实战(主机nginx连接项目容器)(详细配置过程)

我用的是主机上的nginx,没有用docker安装nginx&#xff0c; 所以需要先在主机上安装nginx # 更新系统yum sudo yum update# 安装安装包sudo yum install epel-release sudo yum install wget# 安装Nginx sudo yum install nginx #启动 sudo systemctl start nginx #开机自启动…

Centos7配置登录失败处理导致root被锁定处理办法

1、应用场景 root用户被系统锁定&#xff0c;无法登录系统。 2、问题现象 root锁定无法登录系统 3、原因 设置登录失败处理并对root用户生效&#xff0c;一直尝试错误的root密码或暴力破解root密码&#xff0c;导致无法自动解锁Linux的root账户 4、解决方案 1.将虚拟机开…

机器学习中的有监督学习和无监督学习

有监督学习 简单来说&#xff0c;就是人教会计算机学会做一件事。 给算法一个数据集&#xff0c;其中数据集中包含了正确答案&#xff0c;根据这个数据集&#xff0c;可以对额外的数据希望得到一个正确判断&#xff08;详见下面的例子&#xff09; 回归问题 例如现在有一个…

[SWPUCTF 2021 新生赛]easyupload1.0

发现是上传文件第一想到是文件木马 <?php eval ($_POST[123]);?>木马上传burp修改后缀发现flag里面这个是假的 我们猜想是在phpinfo我们上传<?php eval(phpinfo(););?>木马上传burp修改后缀里面 CtrlF 发现flag