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

【Linux】进程间通信——命名管道和共享内存

目录

命名管道(named pipe)

命令行中使用

代码中使用

共享内存(shared memory)

shmget

ipcs命令

shmctl

shmat/shmdt

简单通信


命名管道(named pipe)

之前我们说了匿名管道,但是匿名管道有一个特点就是只能具有血缘关系的进程间可以进行通信,那如果我想让两个毫无关系的进程进行通信,该怎么办呢?

还是不要忘记进程间通信的本质,就是让两个进程看到同一份资源,如果两个进程毫无关系,那么我们可以让它们看到某个路径下的一个文件,这样它们就看到同一份资源了。并且,因为这类文件是唯一路径下的唯一文件,它是可以确定的,所以叫命名文件,创建的管道也就叫命名管道

不同的进程以不同的方式打开同一个文件,那么这两个进程的文件描述符表中的指针指向不同的文件结构体对象,但是不同的文件结构体对象指向的文件的缓冲区是一样的,所以,两个进程可以看到相同的内容。

命令行中使用

我们可以首先在命令行中用一下管道文件,先查一下手册

man mkfifo

这个命令的基本使用就是mkfifo + 文件名

我们也可以看到这个文件的类型是p,表示pipe,管道文件

创建完管道文件后就可以用两个进程 (命令就是进程),一个往管道文件中写,一个往管道文件中读(可以echo向文件中写,cat从文件中读)

现象就是一个往管道文件中写,如果没人读,那么进程就会阻塞等待;同理,一个往管道文件中读,如果没人写,那么进程也会阻塞等待

代码中使用

上面是在命令行上进行的操作,如果我们要用代码来操作就需要用相关的接口

man 3 mkfifo

第一个参数就是路径加文件名,第二个参数就是要创建的文件的权限

其实我们的任务就是创建一个管道文件,然后用文件操作(open、close、read、write)的方式向管道文件中写和读

我们可以创建一个服务器端和一个客户端,把客户输入的内容通过命名管道传给服务器,创建下面几个文件:

Comm.hpp中放共同的用得到的代码,PipeClient.cc放客户端的代码,PipeServer.cc放服务器端的代码,我们就来非常简单的模拟实现一下

基本代码如下,代码的效果就是先开服务器端,再开客户端,客户端写什么,服务器端都可以收到

//Comm.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<cerrno>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
using namespace std;#define PATH "./fifo"
#define Mode 0666
class Fifo
{public:Fifo(const char*path):_path(path){umask(0);int n=mkfifo(_path.c_str(),Mode);if(n==0){cout<<"touch fifo success"<<endl;}else{cout<<"fail to fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;}}~Fifo(){int n=unlink(PATH);if(n==0){cout<<"fifo remove"<<endl;}else{cout<<"fail to remove fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;}}private:string _path;
};
//PipeServe.cc
#include"Comm.hpp"int main()
{Fifo fifo(PATH);int rfd=open(PATH,O_RDONLY);//如果要是读先open,没人写,就要阻塞等待在这if(rfd<0){cout<<"fail to read open fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;return 1;}cout<<"read open success"<<endl;char buffer[1024]={0};while(1){ssize_t n=read(rfd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]=0;cout<<"client say : "<<buffer<<endl;}else if(n==0){cout<<"client quit,me too"<<endl;break;}else {cout<<"fail to read errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;break;}}
close(rfd);return 0;
}
//PipeClient.cc
#include"Comm.hpp"int main()
{int wfd=open(PATH,O_WRONLY);if(wfd<0){cout<<"fail to write open fifo errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;return 1;}string inbuffer;while(1){cout<<"please input your message"<<endl;getline(cin,inbuffer);if(inbuffer=="quit")break;int n=write(wfd,inbuffer.c_str(),inbuffer.size());if(n<0){cout<<"fail to write errno is "<<errno<<" strerror is "<<strerror(errno)<<endl;break;}}close(wfd);return 0;
}

共享内存(shared memory)

之前说的无论是匿名管道还是命名管道都是管道,它们都是基于文件实现的,下面我们要说的是system V版本下的进程间通信的方式共享内存,消息队列,信号量

下面我们先说什么是共享内存

在谈论进程间通信时永远不要忘掉进程间通信的本质:让不同的进程看到同一份资源。

所以,共享内存就是在物理内存中开辟一段空间,然后将这块空间通过页表映射到各个进程的地址空间(虚拟内存)的共享区中,这样,不同的进程就可以看到同样的一份资源了。

有了上面的理论基础我们就知道了我们需要学习什么样的系统调用,有申请共享内存的,有把共享内存进行挂接到地址空间的,有去挂接的,也有删除共享内存的。当然这只是我们粗略的去描述,具体有啥我们下面来看:

shmget

首先要介绍的就是申请共享内存的系统调用

man shmget

它如果成功了就返回共享内存的一个标识符,这个跟下面的key不同,这个标识符是给人看的,是我们通过这个标识符唯一确定一个共享内存

它有三个参数,第一个是在内核中唯一标识一个共享内存的key值,就是内核通过这个值唯一确定一个共享内存,因为内核中存在多个共享内存这是正常的。具体说这个值是怎么创建的呢,当然我们可以自己给一个值,但是我们给的值容易过于简单,容易重复,所以就提供了一个接口

man ftok

这个函数有两个参数,其实我们可以随便给一个路径,一个proj_id,只要它们是唯一的即可,这样就可以生成唯一的key值,不同的进程间只要传入相同的路径和proj_id,它们就能获得相同的key值,找到同一块共享内存。

第二个参数就是你要创建的共享内存的大小,单位是字节

第三个参数是一些选项,你可以设置,基本的选项有:

IPC_CREAT:共享内存不存在就创建,如果存在就直接获取

IPC_EXCL:不能单独使用,没意义

IPC_CREAT | IPC_EXCL:共享内存不存在就创建,存在就出错返回

但是在这些选项后面还有按位或上权限,一般给0666

我们简单的写一个使用它们的代码

#include<iostream>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<cstring>
#include<cerrno>
using namespace std;
#define PATH "/home/user100"
#define proj_id 111
#define SIZE 4096
int main()
{key_t key=ftok(PATH,proj_id);if(key<0){cout<<"ftok fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;return 1;}int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);if(shmid<0){cout<<"shmid fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;return 2;}cout<<"make shm success"<<endl;return 0;
}

然后就运行,发现,第一次运行成功,第二次运行失败

因为共享内存在程序结束后并不会删除,也就是说,共享内存的生命周期随内核,我们关掉xShell再开启也不会消失,因为云服务器是一直运行着的,除非我们重启系统共享内存才会消失

ipcs命令

我们可以通过命令和代码删除共享内存

图中的perm其实就是我们代码中设置的权限(permission)

nattch是这个共享内存挂接(attach)到了几个进程中

并且我们要说明的是内核中共享内存的大小是以4kb为基本单位的,也就是说如果我们给4097个字节,它也是会申请8kb的大小的,只不过会显示4097个字节

ipcs可以查看目前都有哪些消息队列,共享内存和信号量,shmid为1的共享内存就是我们刚才创建的,添加不同的选项可以分别查看

-q:显示消息队列的信息。(massage queue)

-m:显示共享内存的信息。(shared memory)

-s:显示信号量的信息。(semaphore array)

ipcrm -m shmid 就是删除共享内存,要是删消息队列和信号量也是以此类推

shmctl

下面就是用代码删除了,我们需要用到下面的接口

man shmctl

这个其实是对共享内存进行控制,当然了,控制也包括删除

第一个参数就是shmid,就是shmget的返回值

第二个参数是一些选项,不过我们删除要用到的选项是IPC_RMID

第三个参数是你传一个这个类型的结构体指针过去,然后它把共享内存的信息给你传回来,我们一般不需要信息的话给个nullptr就行了

我们也可以看一下内核都暴露给我们什么样的信息:

struct shmid_ds中第一个内容就是第二张图片的struct ipc_perm。

我们简单的用一下就行了

 int ret=shmctl(shmid,IPC_RMID,nullptr);if(ret<0){cout<<"remove shm fail errno is "<<errno<<" error string is "<<strerror(errno)<<endl;return 3;}cout<<"remove shm success,shmid is "<<shmid<<endl;

shmat/shmdt

上面我们讲了共享内存的创建和删除,下面我们就要说它该如何挂接和去挂接到进程的地址空间中呢?

man shmat(attach)挂接

man shmdt(detach)去关联

shmat第二个参数是一个地址,就是你想挂接到地址空间的哪里,但是我们一般给nullptr,就让OS随便找合适的空间即可

第三个仍然是一些选项,我们一般给0即可

返回值就是,挂接到的地址空间的首地址

shmdt参数就是shmat的返回值

我们简单来用一下

void *addr = shmat(shmid, nullptr, 0);if ((long long)addr == -1){cout << "shmat fail errno is " << errno << " error string is " << strerror(errno) << endl;}cout << "shmat success" << endl;sleep(5);int ret1 = shmdt(addr);if (ret1 < 0){cout << "shmdt fail errno is " << errno << " error string is " << strerror(errno) << endl;}cout << "shmdt success" << endl;sleep(2);

简单通信

上面是一些基础的使用,下面我们就像命名管道写代码完成两个进程间简单的通信,我们还是创建那么几个文件

我们不得不说,共享内存是最快的进程间通信的方式,因为一个进程只需要把数据写入到它的地址空间中(其实就是写到了物理内存中),另一个进程就可以看到,这是它的优点,但是共享内存是不提供进程间协同的机制的,就是你写你的我读我的,这就会导致你可能还没写完我就读了,这就导致信息丢失。但是我们知道管道是提供的,于是呢,我们可以利用管道的特点(写端不写,读端阻塞等待)实现共享内存的同步机制,基本代码如下:

//server.cc
#include "Comm.hpp"
#include "pipe.hpp"
int main()
{key_t key = Getkey();//获取keyint shmid = ServerGetshm(key);//创建共享内存void *addr = Attach(shmid);//挂接int *address = (int *)addr;Fifo fifo;//创建管道int rfd = Serveropenfifo();//以读方式打开文件for (int i = 0; i < 10; i++){wait(rfd);//写端不写,就阻塞在这里int *tmp = address;cout << "Server get ";while (*(tmp) != 0){cout << *(tmp);tmp++;}cout << endl;}Detach(addr);//去挂接removeshm(shmid);//删除共享内存return 0;
}
//client.cc
#include "Comm.hpp"
#include"pipe.hpp"
int main()
{key_t key = Getkey();//获取keyint shmid = ClientGetshm(key);//获取共享内存void *addr = Attach(shmid);//挂接int *address = (int *)addr;int wfd= clientopenfifo();//以写方式打开文件for (int i = 0; i < 10; i++){*(address + i) = i + 1;wakeup(wfd);//写完了一部分完整的数据就通知唤醒读端sleep(1);}Detach(addr);//去挂接return 0;
}
//comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <cerrno>
using namespace std;
#define PATH "/home/user100"
#define proj_id 111
#define SIZE 4096key_t Getkey()
{key_t key = ftok(PATH, proj_id);if (key < 0){cout << "ftok fail errno is " << errno << " error string is " << strerror(errno) << endl;return -1;}return key;
}int ServerGetshm(key_t key)
{int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);if (shmid < 0){cout << "Server Get shm fail errno is " << errno << " error string is " << strerror(errno) << endl;return -1;}cout << "make shm success" << endl;return shmid;
}
int ClientGetshm(key_t key)
{int shmid = shmget(key, SIZE, IPC_CREAT | 0666);if (shmid > 0)cout << "Client Get shm success" << endl;return shmid;
}void *Attach(int shmid)
{void *addr = shmat(shmid, nullptr, 0);if ((long long)addr == -1){cout << "shmat fail errno is " << errno << " error string is " << strerror(errno) << endl;return nullptr;}cout << "shmat success" << endl;return addr;
}int Detach(void *addr)
{int ret1 = shmdt(addr);if (ret1 < 0){cout << "shmdt fail errno is " << errno << " error string is " << strerror(errno) << endl;return -1;}cout << "shmdt success" << endl;return 0;
}int removeshm(int shmid)
{int ret = shmctl(shmid, IPC_RMID, nullptr);if (ret < 0){cout << "remove shm fail errno is " << errno << " error string is " << strerror(errno) << endl;return -1;}cout << "remove shm success,shmid is " << shmid << endl;return 0;
}
//pipe.hpp#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;#define Pipepath "./fifo"
#define Mode 0666
class Fifo
{
public:Fifo(const char *path = Pipepath): _path(path){umask(0);int n = mkfifo(_path.c_str(), Mode);if (n == 0){cout << "touch fifo success" << endl;}else{cout << "fail to fifo errno is " << errno << " strerror is " << strerror(errno) << endl;}}~Fifo(){int n = unlink(Pipepath);if (n == 0){cout << "fifo remove" << endl;}else{cout << "fail to remove fifo errno is " << errno << " strerror is " << strerror(errno) << endl;}}private:string _path;
};int Serveropenfifo()
{int rfd = open(Pipepath, O_RDONLY);if (rfd < 0){cout << "fail to ropen fifo errno is " << errno << " strerror is " << strerror(errno) << endl;return 1;}return rfd;
}
int clientopenfifo()
{int wfd = open(Pipepath, O_WRONLY);if (wfd < 0){cout << "fail to wopen fifo errno is " << errno << " strerror is " << strerror(errno) << endl;return 1;}return wfd;
}void wakeup(int wfd)
{char ch='A';write(wfd,&ch,sizeof(ch));
}
void wait(int rfd)
{char buffer[10]={0};read(rfd,buffer,sizeof(buffer));
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 2024年公共文化与社会服务国际会议(ICPCSS 2024)
  • 事务的学习
  • C#小结:未能找到类型或命名空间名“xxx”(是否缺少 using 指令或程序集引用?)
  • 容器docker 架构命令案例
  • 文心快码——百度研发编码助手
  • 大模型/NLP/算法面试题总结3——BERT和T5的区别?
  • WindowsMac共享文件夹设置
  • MSPM0G3507(三十六)——超声波PID控制小车固定距离
  • 景联文科技以高质量多模态数据集赋能AI大模型,精准匹配提升模型性能
  • 通用型I2C接口的应用之综合应用(N32G45XVL-STB)
  • 强化学习编程实践-4-基于蒙特卡洛的方法
  • HQL案例大全之1. 查询没有学全所有课的学生的学号、姓名(特殊:应该先连接,在筛选)
  • 鸿蒙HarmonyOS应用开发为何选择ArkTS不是Java?
  • eNSP:防火墙设置模拟公司配置(二)
  • python入门基础知识·二
  • 收藏网友的 源程序下载网
  • [译]CSS 居中(Center)方法大合集
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • Laravel Mix运行时关于es2015报错解决方案
  • leetcode讲解--894. All Possible Full Binary Trees
  • Python爬虫--- 1.3 BS4库的解析器
  • SpringBoot 实战 (三) | 配置文件详解
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 订阅Forge Viewer所有的事件
  • 离散点最小(凸)包围边界查找
  • 三栏布局总结
  • 数据仓库的几种建模方法
  • 译有关态射的一切
  • 云大使推广中的常见热门问题
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • # wps必须要登录激活才能使用吗?
  • # 达梦数据库知识点
  • # 飞书APP集成平台-数字化落地
  • ### RabbitMQ五种工作模式:
  • ###C语言程序设计-----C语言学习(3)#
  • #if等命令的学习
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (windows2012共享文件夹和防火墙设置
  • (三)Kafka 监控之 Streams 监控(Streams Monitoring)和其他
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Framework 服务实现监控可观测性最佳实践
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .NET+WPF 桌面快速启动工具 GeekDesk
  • .net反编译工具
  • .Net开发笔记(二十)创建一个需要授权的第三方组件
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .net知识和学习方法系列(二十一)CLR-枚举
  • .pub是什么文件_Rust 模块和文件 - 「译」