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

进程间通信(命名管道 共享内存)

文章目录

  • 命名管道
    • 原理
    • 命令创建命名管道
    • 函数创建命名管道
  • 共享内存
    • 原理
    • shmget
      • FIOK
    • 代码应用:
      • prems
      • nattch

命名管道

用于两个毫无关系的进程间的通信。

原理

Linux文件的路径是多叉树,故文件的路径是唯一的。
让内核缓冲区不用刷新到磁盘中,一旦刷新就拖慢了操作系统。所以磁盘中有个特殊文件,在内存中写入不会刷新到磁盘,让两个进程在内存中通信,该文件叫做命名管道。(命名:有路径就有名字;管道:用于内存通信)
在这里插入图片描述

命令创建命名管道

可以直接使用系统命令创建命名管道
在这里插入图片描述
p打头的文件就是管道文件
在这里插入图片描述
echo是进程,cat也是进程。两个进程基于管道实现了通信。写入的时候,管道的大小依旧为0。
在这里插入图片描述

函数创建命名管道

在这里插入图片描述
返回值:成功返回0,失败返回-1
在这里插入图片描述
删除指定路径的文件:unlink
在这里插入图片描述
返回值:成功返回0,失败返回-1
在这里插入图片描述
代码如下:
client.cc 负责写:

#include "namedPipe.hpp"// client write
int main()
{NamePiped fifo(comm_path, User);if (fifo.OpenForWrite()){std::cout << "client open namd pipe done" << std::endl;while (true){std::cout << "Please Enter > ";std::string message;getline(std::cin, message);fifo.WriteNamedpipe(message);}}return 0;
}

namePipe.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>const std::string comm_path = "./myfifo";//管道的名字
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096//一次读多少字节class NamePiped
{
private:bool OpenNamedPipe(int mode)//权限{_fd = open(comm_path.c_str(),mode);if(_fd < 0)return false;return true;}
public:NamePiped(const std::string &path, int who) : _fifo_path(path), _id(who),_fd(DefaultFd){if (_id == Creater){int res = mkfifo(_fifo_path.c_str(), 0666);if (res != 0){perror("mkfifo");}std::cout << "Creater creat Named pipe" << std::endl;}}bool OpenForRead(){return OpenNamedPipe(Read);}bool OpenForWrite(){return OpenNamedPipe(Write);}int ReadNamedpipe(std::string *out){char buffer[BaseSize];int n = read(_fd,buffer,sizeof(buffer));if(n > 0){buffer[n] = 0;*out = buffer;}return n;}int WriteNamedpipe(const std::string &in){return write(_fd,in.c_str(),in.size());}~NamePiped(){if (_id == Creater){sleep(5);int res = unlink(_fifo_path.c_str());if (res != 0){perror("unlink");}std::cout << "Creater free Named pipe" << std::endl;}if(_fd != DefaultFd) close(_fd);}private:const std::string _fifo_path;  //管道路径int _id;  //身份(user/creater)int _fd;  //文件描述符
};

server.cc 读端:

#include "namedPipe.hpp"// server read:管理命名管道的生命周期(创建与删除)
int main()
{NamePiped fifo(comm_path, Creater);if(fifo.OpenForRead()){std::cout << "server open named pipe done" << std::endl;while (true){std::string message;int res = fifo.ReadNamedpipe(&message);if (res > 0)//读内容{std::cout << "Client send >" << message << std::endl;}else if(res == 0)//写端关闭{std::cout << "Client quit" << std::endl;break;}else//出错{std::cout << "fifo.ReadNamedpipe default" << std::endl;break;}}}return 0;
}

运行:
在这里插入图片描述
光运行./server(读端),不运行写端会创建出管道,但读没有打印出server open named pipe done。说明没有打开管道。
在这里插入图片描述
然后运行了./client(写端)才会打开管道。说明对于读端而言,如果我们打开文件,但还没有写端,就会阻塞在open调用中,直到对方打开。简称进程同步。
在这里插入图片描述
相反如果读端关闭,写端还在写,写端就会收到SIGPIPE信号,让写端进程直接退出
在这里插入图片描述

共享内存

匿名管道和命名管道就是复用文件的代码。
有人从零开始写本地通信方案的代码:System V IPC 有三种方式通信
在这里插入图片描述
因为只能本地通信,且和文件的整合度不高,所以目前这种方案已经快被淘汰了。

原理

假设A进程在物理内存中创建一段内存空间,然后在A进程的地址空间中的共享区申请一片空间,再把虚拟地址与共享内存的映射关系填入页表。拥有映射关系后就可以往创建的内存中写入。B进程的虚拟地址也通过页表与创建的内存映射。这样进程A与进程B看到同一块资源,上面的技术叫做共享内存。
在这里插入图片描述
补充:
1.以上操作都是由操作系统做的,所以操作系统肯定提供给用户系统调用了。
2.AB进程通信,CD进程也要通信,所以共享进程在系统中可以存在很多份,且功能也不一样。操作系统就要对共享内存做管理!先描述,再组织。类似struct Shm的结构体。
在这里插入图片描述
综上所述:共享内存 = 共享内存空间(数据) + 共享内存属性

shmget

创建共享内存的函数调用:IPC_CREAT用于获取;IPC_CREAT | IPC_EXCL用于创建。
在这里插入图片描述
key比较特殊,是标识共享内存的唯一性字段,用于寻找共享空间。
在这里插入图片描述
综上:用户设置key值,AB两个进程就能看见同一块共享空间。
key值是给用户看的,后面会说shmid是给操作系统看的,所以操作系统不创建key值。
但是又不建议用户自己设置key,容易冲突,就给用户提供了函数ftok,由一些算法形成的随机数。

FIOK

同一个pathname,同一个proj_id,就能由算法形成同一个key。
在这里插入图片描述
返回值:成功后返回的是key的值;失败后返回-1
在这里插入图片描述

代码应用:

先验证共享内存的几个特性:
共享内存,不随着进程的结束释放。
Shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>#define gShmSize 4096const std::string gpathname = "/root/test/shm";
const int gproj_id = 0x66;std::string ToHex(key_t k)
{char buffer[128];snprintf(buffer,sizeof(buffer),"0x%x",k);return buffer;
}key_t GetCommKey(const std::string pathname, int proj_id)
{key_t res = ftok(pathname.c_str(), proj_id);if (res < 0){perror("create ftok failing");}return res;
}int GetShm(int key)
{int shmid = shmget(key,gShmSize,IPC_CREAT | IPC_EXCL);if(shmid < 0){perror("shmget fail");}return shmid;
}#endif

server.cc

#include "Shm.hpp"int main()
{key_t k = GetCommKey(gpathname,gproj_id);std::cout << "key: " << ToHex(k) << std::endl;int shmid = GetShm(k);std::cout << "shmid:" << shmid << std::endl;return 0;
}

运行两次,发现第二次并没有创建,并显示已经存在,说明共享内存并没有像子进程一样运行完就被父进程或系统回收。
在这里插入图片描述
把创建共享内存改成获取共享内存:

int GetShm(int key)
{int shmid = shmget(key,gShmSize,IPC_CREAT);if(shmid < 0){perror("shmget fail");}return shmid;
}

发现获取的话可以一直获取。
在这里插入图片描述
如果不释放共享内存,就会一直存在,生命周期随内核。
查共享内存:

ipcs -m

在这里插入图片描述
key:属于用户形成,内核使用的一个字段,用户不能使用key来进行shm管理。
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值。
删共享内存用命令shmid删除:

ipcrm -m [shmid]

在这里插入图片描述
也可以用操作系统提供的函数:shmctl删除。cmd删除是IPC_RMID,就是位图。
在这里插入图片描述
返回值:移植成功,返回0;失败,返回-1
在这里插入图片描述
综上所述:创建共享内存,使用后删除的代码就能写出来了:

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cerrno>#define gCreater 1
#define gUser 2
#define gShmSize 4096const std::string gpathname = "/root/test/shm";
const int gproj_id = 0x66;class Shm
{
private:key_t GetCommKey(){key_t key = ftok(_pathname.c_str(), _proj_id);if (key < 0){perror("create ftok failing");}return key;}int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget fail");}return shmid;}public:Shm(const std::string pathname, const int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who){_key = GetCommKey();if (_who == gCreater){CreaterGetShmid();}else if (_who == gUser){UserGetShmid();}std::cout << "shmid: " << _shmid << std::endl;std::cout << "key: " << ToHex(_key) << std::endl;}~Shm(){if (_who == gCreater){int res = shmctl(_shmid, IPC_RMID, nullptr);if (res < 0)std::cout << "shmctl fail " << std::endl;elsestd::cout << "shmid remove done " << std::endl;}}std::string ToHex(key_t k){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;}bool CreaterGetShmid(){if (_who == gCreater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL);sleep(10); //为了创建完看见删除的效果if (_shmid > 0)return true;}std::cout << "Create shmid succeed" << std::endl;return false;}bool UserGetShmid(){if (_who == gUser){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT);if (_shmid > 0)return true;}std::cout << "Get shmid succeed" << std::endl;return false;}private:key_t _key;int _shmid;int _who;const std::string _pathname;const int _proj_id;
};#endif

创建出共享内存后10秒钟后删除
在这里插入图片描述

prems

perms是共享内存的权限:如果创建的时候,加上权限,perms的值会更改。

bool CreaterGetShmid(){if (_who == gCreater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);if (_shmid > 0)return true;}std::cout << "Create shmid succeed" << std::endl;return false;}

在这里插入图片描述

nattch

nattch是该共享内存挂接的数量。
挂接的函数:shmat(at有attach的意思)
在这里插入图片描述
返回值:一旦挂接成功,返回地址空间中共享内存的起始地址。(跟malloc返回值类似)
挂接代码:

std::string RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "gUser";elsereturn "None";}// 挂接void *AttachShm(){void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}

一个进程挂接上了为1,两个进程挂接上了为2。
在这里插入图片描述
去掉进程与共享内存的关联的函数:shmdt
在这里插入图片描述
代码如下:

void DetachShm(void *shmaddr){if(shmaddr == nullptr) return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}

效果:0-》1-》0
在这里插入图片描述
上面都是准备工作:下面开始通信。
只运行读端:
现象:发现不等写端写入,读端一直在读。
在这里插入图片描述
读端和写端都运行:
现象:写端2秒写一次,读端1秒读1次,数据就有重复。(相比较通道读完数据就没有了)
在这里插入图片描述
读端和写端都运行,然后把写端关闭后再次运行写端:
现象:读端重新读取
在这里插入图片描述
综上所述:与管道不一样,共享内存不提供保护机制。
缺点:会造成数据不一致问题(在纸上写字,写一半被别人拿走了)
优点:共享内存收所有进程IPC,速度最快的。因为共享内存大大减少了数据的拷贝次数。
在这里插入图片描述

http://www.lryc.cn/news/475872.html

相关文章:

  • Python 网络爬虫教程:从入门到高级的全面指南
  • 深度学习:正则化(Regularization)详细解释
  • Freertos学习日志(1)-基础知识
  • CentOS9 Stream 支持输入中文
  • 基于向量检索的RAG大模型
  • 【力扣 + 牛客 | SQL题 | 每日5题】牛客SQL热题216,217,223
  • Unity humanoid 模型头发动画失效问题
  • 最全Kafka知识宝典之Kafka的基本使用
  • 机器学习中的数据可视化:常用库、单变量图与多变量图绘制方法
  • CodeQL学习笔记(3)-QL语法(模块、变量、表达式、公式和注解)
  • 代码随想录训练营Day11 | 226.翻转二叉树 - 101. 对称二叉树 - 104.二叉树的最大深度 - 111.二叉树的最小深度
  • “死鱼眼”,不存在的,一个提词小技巧,拯救的眼神——将内容说给用户,而非读给用户!
  • 深度学习在复杂系统中的应用
  • vue3图片懒加载
  • 总结一些高级的SQL技巧
  • 无人机飞手考证热,装调检修技术详解
  • AI资讯快报(2024.10.27-11.01)
  • 范式的简单理解
  • 活着就好20241103
  • 《华为工作法》读书摘记
  • 【Unity基础】初识UI Toolkit - 运行时UI
  • 20.体育馆使用预约系统(基于springboot和vue的Java项目)
  • unity3d————三角函数练习题
  • 如何在Linux系统中使用Git进行版本控制
  • Ubuntu编译linux内核指南(适用阿里云、腾讯云等远程服务器;包括添加Android支持)
  • [MySQL]DQL语句(一)
  • GPT原理;ChatGPT 等类似的问答系统工作流程如下;当用户向 ChatGPT 输入一个问题后:举例说明;ChatGPT不是通过索引搜索的传统知识库
  • 目前最新最好用 NET 混淆工具 .NET Reactor V6.9.8
  • 计算布尔二叉树的值
  • Java-I/O框架09:InputStreamReader、OutputStreamWriter使用