(26)Linux 进程通信之共享内存(共享储存空间)

共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

1、共享内存的通信原理 

在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

共享内存原理图:

 上图:当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

2、为什么共享内存速度最快?

借助上图说明:Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制

(1)Proc A 到共享内存       (2)共享内存到 Proc B

因为直接在内存上操作,所以共享内存的速度也就提高了。

最简单的共享内存的使用流程

  • ①ftok函数生成键值
  • ②shmget函数创建共享内存空间
  • ③shmat函数获取第一个可用共享内存空间的地址
  • ④shmdt函数进行分离(对共享存储段操作结束时的步骤,并不是从系统中删除共享内存和结构)
  • ⑤shmctl函数进行删除共享存储空间

3、ftok函数生成键值 

每一个共享存储段都有一个对应的键值(key)相关联(消息队列、信号量也同样需要)。

//使用此函数,需导入此头文件
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
  • const char *pathname: 一个以null结尾的字符串,表示文件系统中文件的路径。文件应该在调用ftok()时存在。在多个进程中生成相同的键时,需要使用相同的文件路径。
  • int proj_id: 一个非零的整数,作为生成键的种子。在多个进程中生成相同的键时,需要使用相同的proj_id。
     

返回值:成功返回键值(相当于32位的int)。出错返回-1

总之,ftok() 是一个标准C库函数,用于为System V IPC对象生成键。它需要提供文件系统中文件的路径和一个非零整数作为参数。通过使用相同的文件路径和非零整数,可以在多个进程之间生成相同的键,以便共享IPC对象。 

例如:key_t key = ftok( “/tmp”, 66);

 4、shmget函数创建共享存储空间并返回一个共享存储标识符

所需头文件:#include<sys/shm.h>函数原型: int shmget(key_t key, size_t size, int shmflg);
  • [参数key]:由ftok生成的key标识,标识系统的唯一IPC资源。
  • [参数size]:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
  • [参数shmflg]:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
  • [返回值]:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。

例如:int id = shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);创建一个大小为4096个字节的权限为0666(所有用户可读可写,具体查询linux权限相关内容)的共享存储空间,并返回一个整形共享存储标识符,如果key值已经存在有共享存储空间了,则出错返回-1。

     int id = shmget(key,4096,IPC_CREAT|0666);创建一个大小为4096个字节的权限为0666(所有用户可读可写,具体查询linux权限相关内容)的共享存储空间,并返回一个共享存储标识符,如果key值已经存在有共享存储空间了,则直接返回一个共享存储标识符。

 

 5、shmat 函数:挂接共享内存

//使用此函数,需导入此头文件
#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
  • int shmid: 一个整数,表示共享内存段的标识符(ID)。这个值通常是通过调用 shmget() 函数获得的。
  • const void *shmaddr: 一个指针,表示附加共享内存段的首选地址。通常将此参数设置为NULL,让系统自动选择一个合适的地址。
  • int shmflg: 一个整数,表示附加共享内存段的标志。常用标志包括:
  •     SHM_RDONLY: 以只读方式附加共享内存段。
  •     0: 以读写方式附加共享内存段。

返回值:

  • 成功时,shmat() 返回一个非空指针,表示共享内存段在当前进程地址空间的起始地址。
  • 失败时,返回 (void *)-1,并设置相应的 errno。

总之,shmat() 是一个Linux系统调用函数,用于将共享内存段附加到当前进程的地址空间。它需要提供共享内存段的标识符、首选地址和标志作为参数。成功时,它会返回一个指向共享内存段起始地址的指针,用于后续的内存访问操作。 

6、shmctl ( ):销毁共享内存 

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • [参数shmid]:共享存储段标识符。
  • [参数cmd]:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。
  • [参数*buf]:设置为NULL即可。
  • [返回值]:成功返回0,失败返回-1。

7、shmdt函数进行分离 

  • 当不需要对此共享内存进行操作时候,调用shmdt函数进行分离,不是删除此共享存储空间哟。
  • 所需头文件:#include<sys/shm.h>
  • 函数原型: int shmdt(const void *addr);
  • addr为shmat函数返回的地址指针
  • 返回值:成功返回0;错误返回-1

例如:int ret = shmdt(addr);
下面是一个例子,希望对你对上面的内容理解有所帮助。

comm.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std; //不推荐#define PATH_NAME "/home/whb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍#define FIFO_NAME "./fifo"class Init
{
public:Init(){umask(0);int n = mkfifo(FIFO_NAME, 0666);assert(n == 0);(void)n;Log("create fifo success",Notice) << "\n";}~Init(){unlink(FIFO_NAME);Log("remove fifo success",Notice) << "\n";}
};#define READ O_RDONLY
#define WRITE O_WRONLYint OpenFIFO(std::string pathname, int flags)
{int fd = open(pathname.c_str(), flags);assert(fd >= 0);return fd;
}void Wait(int fd)
{Log("等待中....", Notice) << "\n";uint32_t temp = 0;ssize_t s = read(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;
}void Signal(int fd)
{uint32_t temp = 1;ssize_t s = write(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;Log("唤醒中....", Notice) << "\n";
}void CloseFifo(int fd)
{close(fd);
}

Log.hpp:

#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3const std::string msg[] = {"Debug","Notice","Warning","Error"
};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif

Makefile:

.PHONY:all
all:shmClient shmServershmClient:shmClient.ccg++ -o $@ $^ -std=c++11
shmServer:shmServer.ccg++ -o $@ $^ -std=c++11
.PHONNY:clean
clean:rm -f shmClient shmServer

shmServer.cc:

#include "comm.hpp"// 是不是对应的程序,在加载的时候,会自动构建全局变量,就要调用该类的构造函数 -- 创建管道文件
// 程序退出的时候,全局变量会被析构,自动调用析构函数,会自动删除管道文件
Init init; string TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof buffer, "0x%x", k);return buffer;
}int main()
{// 我们之前为了通信,所做的所有的工作,属于什么工作呢:让不同的进程看到了同一份资源(内存)// 1. 创建公共的Key值key_t k = ftok(PATH_NAME, PROJ_ID);assert(k != -1);Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;// 2. 创建共享内存 -- 建议要创建一个全新的共享内存 -- 通信的发起者int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //if (shmid == -1){perror("shmget");exit(1);}Log("create shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 3. 将指定的共享内存,挂接到自己的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);Log("attach shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 这里就是通信的逻辑了// 将共享内存当成一个大字符串// char buffer[SHM_SIZE];// 结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据。//         共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)// 结论2: 共享内存缺乏访问控制!会带来并发问题 【如果我想一定程度的访问控制呢? 能】int fd = OpenFIFO(FIFO_NAME, READ);for(;;){Wait(fd);// 临界区printf("%s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;// sleep(1);}// 4. 将指定的共享内存,从自己的地址空间中去关联int n = shmdt(shmaddr);assert(n != -1);(void)n;Log("detach shm done", Debug) << " shmid : " << shmid << endl;// sleep(10);// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存n = shmctl(shmid, IPC_RMID, nullptr);assert(n != -1);(void)n;Log("delete shm done", Debug) << " shmid : " << shmid << endl;CloseFifo(fd);return 0;
}

shmClient.cc:

#include "comm.hpp"int main()
{Log("child pid is : ", Debug) << getpid() << endl;key_t k = ftok(PATH_NAME, PROJ_ID);if (k < 0){Log("create key failed", Error) << " client key : " << k << endl;exit(1);}Log("create key done", Debug) << " client key : " << k << endl;// 获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if(shmid < 0){Log("create shm failed", Error) << " client key : " << k << endl;exit(2);}Log("create shm success", Error) << " client key : " << k << endl;// sleep(10);char *shmaddr = (char *)shmat(shmid, nullptr, 0);if(shmaddr == nullptr){Log("attach shm failed", Error) << " client key : " << k << endl;exit(3);}Log("attach shm success", Error) << " client key : " << k << endl;// sleep(10);int fd = OpenFIFO(FIFO_NAME, WRITE);// 使用// client将共享内存看做一个char 类型的bufferwhile(true){ssize_t s = read(0, shmaddr, SHM_SIZE-1);if(s > 0){shmaddr[s-1] = 0;Signal(fd);if(strcmp(shmaddr,"quit") == 0) break;}}CloseFifo(fd);// 去关联int n = shmdt(shmaddr);assert(n != -1);Log("detach shm success", Error) << " client key : " << k << endl;// sleep(10);// client 要不要chmctl删除呢?不需要!!return 0;
}

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

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

相关文章

端侧AI的“春风化雨手”,翻开中国科技下一页

大模型是一年多来全球科技圈的最大热点&#xff0c;手机厂商想要借助大模型的锋芒&#xff0c;打造高端形象&#xff0c;获得新的增长&#xff0c;这无可厚非。 不过&#xff0c;大家注意到没有&#xff0c;越是“AI强者”&#xff0c;对待大模型越举重若轻。 简单来说&#xf…

43-函数的声明定义,函数表达式定义,函数的调用,声明提升,参数,形参,实参

1.函数声明定义 function 函数名(){} 2.函数表达式定义 匿名式函数表达式 var 函数名 function(){} 命名式函数表达式 var 函数名 function 函数关键字(){} 3.函数的调用 var fn function f(){alert("aaa");}//函数名&#xff08;&#xff09;//函数想调用几次…

rpb/rpc文件说明与matlab读取

什么是rpb/rpc文件&#xff1f; rpb文件是用来存储用于遥感数据几何校正的RPC&#xff08;Rational Polynomial Coefficients &#xff09;模型的文件。类似的还有RPC文件&#xff0c;rpb与rpc文件只是格式不同&#xff0c;但包含的信息一致。其用于从图像坐标转换到地理坐标&a…

acwing4986.互质数的个数

题目不难 有个好的细节想着分享一下 一开始写的有点问题&#xff5e;需要特判掉一个... #include<bits/stdc.h> using namespace std; using ll long long; const int N 1e510;const ll mod 998244353;ll qmi(ll a,ll b){ll ans 1;while(b){if(b&1)ans ans*a%…

浅谈6种流行的API架构风格

前言 API在现代软件开发中扮演着重要的角色&#xff0c;它们是不同应用程序之间的桥梁。编写业务API是日常开发工作中最常见的一部分&#xff0c;选择合适的API框架对项目的成功起到了至关重要的作用。本篇文章将浅谈一下当前6种流行的API架构风格的优点、缺点以及适用场景。 …

反向传播(Back Propagation)

目录 回归简单模型的梯度计算 反向传播计算图链式求导链式法则定理&#xff1a; Forward 前馈计算反向传播Back Propagation例子线性模型的计算图计算前馈过程反向传播过程&#xff08;逆向求导&#xff09; 练习 Pytorch中的前馈过程和反向传播过程Tensor代码小结 回归 简单模…

中药房数字化-亿发中药饮片信息化建设方案,中药材饮片智能追溯

中药&#xff08;包括中成药、颗粒剂、中药饮片等&#xff09;是中医临床的重要工具和武器&#xff0c;中药材是其中的核心要素。在这一体系中&#xff0c;“药材好&#xff0c;药才好”是关键&#xff0c;因为只有中药材的品质稳定和高效&#xff0c;才能最大限度地确保中医治…

Android音视频: 引入FFmpeg

本文你可以了解到 本文将介绍如何将上一篇文章编译出来的 FFmpeg so 库&#xff0c;引入到 Android 工程中&#xff0c;并验证 so 是否可以正常使用。 一、开启 Android 原生 C/C 支持 在过去&#xff0c;通常使用 makefile 的方式在项目中引入 C/C 代码支持&#xff0c;随…

Minitab的单因子方差分析的结果

单因子方差分析概述 当有一个类别因子和一个连续响应并且想要确定两个或多个组的总体均值是否存在差异时&#xff0c;可使用 单因子方差分析。如果经检验&#xff0c;发现至少有一组存在差异&#xff0c;请使用单因子方差分析中的比较对话框来标识存在显著差异的组对。 例如&…

前端布局——垂直、水平居中

行内元素 方法一&#xff1a;给行内元素设置行高 <div class"box"><span>行内元素</span> </div> <style type"text/css">.box{width: 100%;height: 200px;background-color: orange;line-height: 200px;text-align: cent…

代码随想录算法训练营第三天| LeetCode203.移除链表元素、707.设计链表、206.反转链表

文章目录 一、203. 移除链表元素感受代码二、707.设计链表感受代码206.反转链表感受总结一、203. 移除链表元素 感受 我对这道题。从理论上来说太熟悉了。咸鱼讲数据结构常用的方法他都会讲。但是我没上机没写过。到后面上机还是写不出来。giao。 代码 第一次写,想说一下,…

LeetCode刷题:141. 环形链表

题目&#xff1a; 是否独立解答出&#xff1a;否&#xff0c;有思路&#xff0c;但是代码报错&#xff0c;参考解题代码后&#xff0c;修改通过 解题思路&#xff1a;利用循环与哈希表存储每一个节点&#xff0c;如果发现添加不进去说明&#xff0c;存在环&#xff0c;正常来说…

x3daudio1_7.dll如何恢复,这6个方法都能修复x3daudio1_7.dll丢失问题

x3daudio1_7.dll文件缺失”。那么&#xff0c;什么是x3daudio17.dll文件&#xff1f;它的作用和影响又是什么呢&#xff1f;本文将详细介绍x3daudio17.dll文件的定义、作用和影响&#xff0c;并提供6个修复方法来解决这个问题。 一、x3daudio1_7.dll是什么&#xff1f; x3dau…

推荐熊猫电竞赏金电竞系统源码

熊猫电竞赏金电竞系统源码&#xff0c;包含APP、H5和搭建视频教程&#xff0c;支持运营级搭建&#xff0c;这套源码是基于ThinkPHPUniaapp框架开发的。 系统是一套完整的电竞平台开发源码&#xff0c;包括赛事管理、用户系统、竞猜系统、支付系统等模块。源码结构清晰&#xff…

vue3+vite开发生产环境区分

.env.development VITE_APP_TITLE本地.env.production VITE_APP_TITLE生产-ts文件中应用 console.log(import.meta.env.VITE_APP_TITLE)在html中应用&#xff0c;需要安装 html 模板插件 pnpm add vite-plugin-html -Dvite.config.ts中 import { createHtmlPlugin } from v…

非常好用的个人工作学习记事本Obsidian

现在记事本有两大流派&#xff1a;Obsidian 和Notion&#xff0c;同时据说logseq也很不错 由于在FreeBSD下后两种都没有相关ports&#xff0c;所以优先尝试使用Obsidian Obsidian简介 Obsidian是基于Markdown文件的本地知识管理软件&#xff0c;并且开发者承诺Obsidian对于个…

算法-二分专题

文章目录 概念应用场景代码模板OJ练习寻找指定元素1题目描述输入描述输出描述样例题解 寻找指定元素2题目描述输入描述输出描述样例题解 寻找指定元素3题目描述输入描述输出描述样例题解 寻找指定元素4题目描述输入描述输出描述样例题解 寻找指定元素5题目描述输入描述输出描述…

Qt添加资源文件

ui->setupUi(this);//1. 使用本地文件&#xff1a;ui->actionasdasdas->setIcon(QIcon("本地绝对路径"));ui->actiona1->setIcon(QIcon("C:/Users/满满/Desktop/output/picture/1.jpg"));//2. 使用资源文件&#xff1a;ui->actionasdasd…

内 存 取 证

1.用户密码 从内存中获取到用户admin的密码并且破解密码&#xff0c;以Flag{admin,password}形式提交(密码为6位)&#xff1b; 1&#xff09;查看帮助 -h ./volatility_2.6_lin64_standalone -h 2&#xff09;获取内存镜像文件的信息 imageinfo ./volatility_2.6_lin64_stand…

自动化测试数据校验神器!

在做接口自动化测试时&#xff0c;经常需要从接口响应返回体中提取指定数据进行断言校验。 今天给大家推荐一款json数据提取神器: jsonpath jsonpath和常规的json有哪些区别呢&#xff1f;在Python中&#xff0c;json是用于处理JSON数据的内置模块&#xff0c;而jsonpath是用…