【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.hqwc.cn/news/454805.html

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

相关文章

Python类与对象

目录 面向对象 定义类 创建对象 类的成员 实例变量 构造方法 实例方法 类变量 类方法 封装性 私有变量 私有方法 使用属性 继承性 Python中的继承 多继承 方法重写 多态性 继承与多态 鸭子类型测试与多态 面向对象 类和对象都是面向对象中的重要概念。面向…

嵌入式基础知识-组合逻辑与时序逻辑电路

本篇来介绍嵌入式硬件电路的相关知识&#xff1a;组合逻辑电路与时序逻辑电路 根据电路是否具有存储功能&#xff0c;将逻辑电路分为组合逻辑电路和时序逻辑电路。 1 组合逻辑电路 组合逻辑电路&#xff0c;是指在任何时刻&#xff0c;电路的输出状态只取决于同一时刻的输入…

Redis缓存过期淘汰策略详讲

前言 查看redis最大占用内存 1&#xff09;命令查看 config get memory2&#xff09;进入redis配置文件&#xff0c;查看maxmemory vim /myredis/redis.conf3&#xff09;redis默认内存多少可用 如果不设置最大内存大小或者设置最大内存大小为0&#xff0c;在64位操作系统…

解决Python xlwings报错AttributeError ‘NoneType‘ object has no attribute apps

一、问题背景 今天&#xff0c;遇到了一个问题&#xff1a;以前调试好的python使用xlwings操作wps表格的脚本突然不能运行了&#xff0c;遇到了很多莫名问题&#xff0c;下面记录分享下&#xff1a; 开始报错如下&#xff1a; D:\PycharmProjects\tiku\venv\Scripts\python.e…

【Gephi项目实战-带数据集】利用gephi绘制微博肖战超话120位用户关系图,并计算整体网络指标与节点指标

数据集在评论区&#xff0c;B站演示视频在评论区&#xff01; 简介 最近2天需要用到gephi做社会网络分析&#xff0c;于是从0开始接触gephi并摸索出了gephi的基本使用指南。下面将结合真实的节点文件与边文件&#xff0c;利用gephi绘制社会网络并计算相关测量指标。整个过程会…

文件夹正在使用无法删除(重命名)解决办法

1、问题描述 相信都遇到文件夹无法删除&#xff0c;或者无法重命名的情况。如果将文件夹正在使用的文件都已经关闭后&#xff0c;文件夹仍旧无法删除或重命名。 这个时候大概率是有隐藏的进程没有关闭&#xff0c;可以重启电脑&#xff0c;或者采用下面的方式关闭对应文件夹的…

error getting ip from ipam: operation get is not supported on blockkey

无论是否通过注释指定ip&#xff0c;都不支持cni Claim操作。 查了好久。发现是版本问题&#xff0c;我的calico版本太老了。是3.5的calico &#xff0c;使用 kubernetes 数据存储时&#xff0c;不支持 Calico IPAM。 需要更新calico到3.6以上&#xff0c;支持 kubernetes 数…

STM32--HAL库定时器学习记录(易懂)--持续学习

一、什么是定时器 定时器就是计数器&#xff0c;通过计数完成一系列功能。 二、定时器的分类 定时器分为基本定时器、通用定时器、高级定时器。级别不同&#xff0c;功能不同。级别越高&#xff0c;功能越强。 三、定时器&#xff08;计数器&#xff09;三个重要寄存器 预分…

Python算法题集_反转链表

Python算法题集_反转链表 题41&#xff1a;反转链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【列表反转】2) 改进版一【直接赋值】3) 改进版二【递归大法】 4. 最优算法 本文为Python算法题集之一的代码示例 题41&#xff1a;反转链表 …

ChatGPT~免费攻略【2024新春福利】

ChatGPT能干什么&#xff1f; 这个问题我也不好回答&#xff0c;于是看看ChatGPT怎么回答的如下图 从回答上看还是很抽象&#xff0c;不够具体。但能确定的是语言方面的理解和回答。当然也许是问题也太抽象了。 我们试试再具体的问题“如何才能学习到鸿蒙系统性开发技术” 换…

111.乐理基础-五线谱-五线谱的节奏型、打拍子

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;110.乐理基础-五线谱-五线谱的速度-CSDN博客 首先必须先看 打拍子 这些东西 简谱里的节奏型总结图&#xff1a; 换成五线谱的节奏型&#xff1a;简谱里会把两个八分音符用根横线连起来&#xff0c;所以五线谱里也…

2,cdc放缩位图

类似地&#xff0c;用pDC->StretchBlt来缩放&#xff0c;只是加上了两个参数&#xff0c;原始位图的宽高。 void CMy1_showbitmapView::StretchBitMap(CDC * pDC) { //CBitmap对象 CBitmap bitmap; //CDC对象 CDC dcMemory; //加载资源 bitmap.LoadBitmapW(IDB_BITMAP1); /…