【共享内存】System V共享内存{通信原理/相关接口/代码测试}

文章目录

  • 1.初识共享内存
    • 1.0浅谈System V
    • 1.1什么是共享内存?
    • 1.2Linux-System V共享内存
    • 1.3图解共享内存
    • 1.4对共享内存的理解
  • 2.创建共享内存
    • 2.1共享内存如何创建?
    • 2.2代码运行与测试
    • 2.3shm与pipe的区别
    • 2.4shm缺乏访问控制
  • 3.代码理解shm
    • 3.1Log.hpp
    • 3.2common.hpp
    • 3.3shmServer.cpp
    • 3.4shmClient.cpp

1.初识共享内存

1.0浅谈System V

在Linux下一切皆文件的情况下,这套模式并不太好。

  1. 使用复杂
  2. System V的一套接口是OS单独拉出来的一个模块,在大项目中不易于集成化,需要单独特殊化处理。【但仍与文件有关 】

1.1什么是共享内存?

共享内存是Unix系统下的多进程间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。它允许多个不相关的进程访问同一个物理内存区域,从而使它们能够相互通信和共享数据。这是进程间通信中最简单的方式之一。

共享内存允许多个进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。这使得共享内存成为进程间共享数据的一种最快的方法。

然而,由于多个进程可以同时访问共享内存,因此需要某种同步机制(如互斥锁和信号量)来避免对共享内存的冲突访问。当一个进程正在向共享内存区写数据时,其他进程在数据被写入完成前不应该去读或写这些数据。

共享内存的使用通常包括以下步骤:

创建或打开共享内存对象。
将指定的共享内存映射到进程的地址空间,以便进行访问。
在完成通信后,撤销对共享内存的映射。
删除共享内存对象。
需要注意的是,共享内存是在多个进程之间共享一段内存区域,因此,对于这段内存的访问和管理需要谨慎处理,以避免出现数据不一致或冲突的情况。

总的来说,共享内存是一种高效的进程间通信方式,它允许多个进程直接读写同一块内存区域,而不需要进行数据的拷贝,从而大大提高了通信的效率。但是,它也需要适当的同步机制来确保对共享内存的正确访问。

1.2Linux-System V共享内存

Linux下的System V共享内存是Unix系统V版本引入的一种进程间通信(IPC)机制。它允许多个进程共享同一块物理内存区域,以实现高效的数据交换和通信。

在System V共享内存中,进程通过系统调用(如shmget、shmat、shmdt等)来创建、映射和解除映射共享内存段。这些系统调用提供了对共享内存的管理和操作功能。

具体来说,shmget函数用于创建或获取一个共享内存段的标识符。它需要提供一个唯一的键值(key)来标识共享内存段,并指定共享内存的大小和访问权限。成功时,shmget返回一个共享内存标识符(shmid),失败时返回-1。

shmat函数用于将共享内存段映射到进程的地址空间中。它接受共享内存标识符(shmid)和可选的映射地址作为参数。如果映射地址为空,则系统会自动选择一个合适的地址进行映射。成功时,shmat返回映射后的地址,失败时返回-1。

一旦共享内存映射到进程的地址空间,多个进程就可以通过直接读写这块内存区域来进行通信。由于共享内存是直接在物理内存中分配的,因此它的访问速度非常快,是进程间通信中最快的方式之一。

需要注意的是,多个进程同时访问共享内存时,可能会出现数据竞争和不一致的问题。因此,需要使用同步机制(如信号量)来确保对共享内存的正确访问。

当不再需要共享内存时,可以使用shmdt函数来解除映射,并使用shmctl函数来删除共享内存段。这些操作可以确保资源被正确释放,避免内存泄漏和不必要的资源浪费。

总的来说,Linux下的System V共享内存是一种高效的进程间通信机制,它允许多个进程直接读写同一块内存区域,而不需要进行数据的拷贝和传输。然而,由于共享内存访问的复杂性,需要谨慎处理同步和并发访问的问题。

1.3图解共享内存

  1. 共享内存和动态库都被映射到进程地址空间的共享区
    在这里插入图片描述
  2. 通信原理图

在这里插入图片描述

1.4对共享内存的理解

  1. 共享内存提供者,是操作系统
  2. 操作系统要管理共享内存 ==> 先描述在组织
  3. 共享内存 == 共享内存块 + 共享内存的内核数据结构
  4. 对共享内存的管理转变成对数据结构的增删查改

OS怎么知道:

  1. 如何知道是否需要释放这块内存时
  2. 当前有多少进程和共享内存关联?
  3. 这块内存有多大?
  4. 如何禁止一些进程访问这块内存(权限问题)?

需要知道该内存的一系列属性信息 ⇒ 通过内核数据结构获取属性信息

2.创建共享内存

共享内存是有很多个的 类似于这样

在这里插入图片描述

2.1共享内存如何创建?

假设A进程(server)与B进程(client)通信

A进程调用shmget()接口

在这里插入图片描述

返回值

在这里插入图片描述
共享内存的用户层标识符(类似fd但不同)

key

  1. 通过调用ftok() 获取的系统中唯一的一个值(数值是几不重要,只要唯一即可–>用来唯一标识指定共享内存块的)
  2. A创建了共享内存块,B怎么看到这个共享内存块,如何保证二者可以访问同一块资源呢?AB通过唯一的key在内核中访问同一个共享内存块 ⇒ 看到了同一块“资源”
  3. AB如何获取同一个key?AB调用同一个ftok()根据相同的参数通过某种算法计算出同一个key(这个经过算法加算出来的key一般在内核中是唯一的,存在出错的情况,此处不讨论)
  4. A调用ftok()获取一个key,在内存中创建一个共享内存块,以此key标识这个shm(将此key存入该shm的数据结构中),B通过ftok()相同的参数相同的算法获取相同的key,根据这个key,去内核中寻找对应的shm,至此,AB两个进程看到了同一块“资源”

size

创建的shm的大小
共享内存的大小 最好是页(PAGE: 4096)的整数倍 4kb
OS管理物理内存时 page的大小以4kb为单位
若申请4097 OS会开辟2个page【对page向上取整】
而第二个page的4095 申请shm的人无法使用 OS其他进程也无法使用 ==> 大大的浪费/zz行为

shmflg

一般有两个选项

IPC_CREAT单独使用:创建shm时,若底层已经存在想要创建的shm,获取并返回shmid;若不存在,创建并返回shmid
IPC_EXCL单独使用:无意义
IPC_CREAT and IPC_EXCL:若底层不存在,创建并返回shmid;若底层存在想要创建的shm,出错返回。⇒ 只要返回成功,此时使用的一定是一个全新的shm

ftok()

在这里插入图片描述
在这里插入图片描述

shmctl

在这里插入图片描述

在这里插入图片描述

建立/删除映射

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

shmid:要将哪个共享内存建立映射
shmaddr:要建立映射的虚拟地址 不建议指定 传空指针让OS帮我们建立映射
shmflg:可以传参以只读方式挂接等
在这里插入图片描述

shmid: 这是一个共享内存标识符,通常是通过 shmget 函数获得的。
nullptr: 这是一个指向 shmat 应该放置附加内存的起始地址的指针。由于这里传入的是 nullptr,内核将自动选择一个合适的地址。
0: 这是一个标志位,用于控制共享内存的访问权限和附加方式。在这里,0 表示使用默认的读/写权限。
返回值:

shmaddr: 这是一个指向附加的共享内存段的指针。如果 shmat 调用成功,shmaddr 将指向共享内存段的起始地址。如果调用失败,shmaddr 将是 (char *) -1,并且 errno 将被设置为指示错误原因的值。
注意:在实际代码中,你应该检查 shmaddr 是否为 (char *) -1 来确定 shmat 调用是否成功,并相应地处理任何错误。

另外,请注意,shmat 的返回值类型是 void*,但在这个例子中,它被强制转换为 char*。这样做是为了方便进行指针算术和内存访问,因为 char 类型的大小是 1 字节,所以 char* 指针可以逐字节地遍历内存。

与malloc的使用类似

在这里插入图片描述
在这里插入图片描述

int shmid = shmget(k, SHM_SIZE, 0);

shmget 是一个在 Unix 和类 Unix 系统(如 Linux)中用于创建或获取共享内存段的系统调用。共享内存是多个进程间通信的一种方式,允许多个进程共享同一块内存区域。
k: 这是共享内存段的键(key)。通常,这是一个整数,用于唯一标识共享内存段。如果键的值为 IPC_PRIVATE,那么将创建一个新的、唯一的共享内存段。
SHM_SIZE: 这是你希望创建的共享内存段的大小(以字节为单位)。如果共享内存段已经存在,这个值将被忽略,但通常你会希望它与你期望的大小相匹配。
第三个参数是共享内存段的权限标志。当你传递 0 作为这个参数时,你实际上没有指定任何权限标志。那么函数将尝试获取一个已经存在的共享内存段,并且不会检查或修改其权限。
函数的返回值 shmid 是一个整数,代表共享内存段的标识符。如果函数成功,它将返回这个标识符;如果失败,它将返回 -1。
int shmid = shmget(k, SHM_SIZE, 0);
这行代码尝试获取一个与键 k 关联的共享内存段,如果它不存在并且没有 IPC_CREAT 标志,则调用将失败。返回的标识符(如果成功)将存储在 shmid 变量中。

shmid vs key

shmid: 用户层标定shm唯一性
key:内核层面上唯一标识shm的
只创建时用key,大多数情况用户访问shm用shmid
【匿名/命名管道等基于文件的生命周期随进程】

当进程结束,shm还在吗?

若不显示删除,sm仍然存在,System V shm生命周期随内核。
怎么删?命令行命令删/代码调用接口删(建议第二种)

2.2代码运行与测试

在这里插入图片描述

while : ; do ipcs -m ; sleep 1 ; done

在这里插入图片描述

物理内存中的shm映射到进程地址空间后,进程地址空间的shm属于内核空间还是用户空间?

在这里插入图片描述

映射到虚拟地址空间的shm属于用户空间,当AB两个进程将同一个物理内存的shm映射到各自的PAS(Process Address Space),AB进程通信时,直接进行内存级的读写即可,无需经过其他调用。

匿名管道/命名管道中的pipe/fifo为什么要通过调用系统接口read/write来进行通信?

pipe/fifo都是文件,是OS进行维护和管理的,而映射到PAS的shm相当于一个进程的

以上的工作:创建key/创建shm/映射shm/删除映射/删除shm是在干嘛?

让不同的进程看到同一份“资源”(内存)

2.3shm与pipe的区别

shm在通信方法中速度较快

pipe的通信方式

在这里插入图片描述

shm的通信方式

在这里插入图片描述

2.4shm缺乏访问控制

  1. 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方就可以立马看到对方写入的数据。
    共享内存是所有进程间通信(IPC),速度最快的,不需要过多的拷贝(不需要将数据给操作系统)
  2. 共享内存缺乏访问控制 会带来并发问题
    缺乏访问控制导致:写入方写了一部分数据 读取方就读走了并对这半个数据进行了处理得到了错误的结果
    写入方不在/写入方不写 读取方仍在读 双方压根不知道对方的存在 即写入方只知道一味的写 读取方只知道一味的读

3.代码理解shm

3.1Log.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 tip[] = {"Debug","Notice","Warning","Error"};std::ostream &Log(std::string message, int option)
{// 获取时间戳 time_t timestamp; time(timestamp);time_t timestamp = time(nullptr);if (timestamp == std::time_t(-1)){std::cerr << "获取时间失败" << std::endl;exit(1);}// 获取格式化时间 tm *localtime(const time_t *__timer)tm *timeinfo = std::localtime(&timestamp);std::cout << " | "<< 1900 + timeinfo->tm_year << "-"<< 1 + timeinfo->tm_mon << "-"<< timeinfo->tm_mday << " "<< timeinfo->tm_hour << ":"<< timeinfo->tm_min << ":"<< timeinfo->tm_sec<< " | "<< tip[option]<< " | "<< message;return std::cout;
}#endif

3.2common.hpp

#pragma once#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std; // 实际项目中 不展开#define PATH_NAME "/home/lhr/linux" // 此路径一定要有访问权限
#define PROJ_ID 0x66                // 0~255
#define SHM_SIZE 4096               // 共享内存的大小 最好是页(PAGE: 4096)的整数倍 4kb
#define FIFO_NAME "./fifo"          // 当前路径下创建一个fifo管道文件string ConvertToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof buffer, "0x%x", k);return buffer;
}class Init
{
public:Init(){umask(0);int n = mkfifo(FIFO_NAME, 0666);assert(n == 0);(void)n;Log("create fifo success", Notice) << std::endl;}~Init(){unlink(FIFO_NAME);Log("remove fifo success", Notice) << std::endl;}
};#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;
}//服务端调用read去阻塞等待数据 直到有人向管道写数据 等待停止
void Wait(int fd)
{//no_use仅仅是实现“读数据”这一动作的对象 其中的数据无意义Log("等待中....", Notice) << std::endl;//read接口从fd读存到tmp 无数据则阻塞等待uint32_t no_use = 0;ssize_t s = read(fd, &no_use, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;
}//客户端调用write向管道写数据 此动作使得管道中添加数据 服务端停止等待
void Wake(int fd)
{//no_use仅仅是实现“写数据”这一动作的对象 其中的数据无意义uint32_t no_use = 1;ssize_t s = write(fd, &no_use, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;Log("唤醒中....", Notice) << std::endl;
}void CloseFifo(int fd)
{close(fd);
}

3.3shmServer.cpp

#include "common.hpp"// 程序加载时自动构建全局对象 -- 调用构造函数创建管道文件
// 程序退出时自动析构全局对象 -- 调用析构函数删除管道文件
Init init;int main()
{// 1. 创建公共的Key值key_t k = ftok(PATH_NAME, PROJ_ID);assert(k != -1);Log("create key done", Debug) << " server key : " << ConvertToHex(k) << endl;// 2. 创建shm shm创建成功后会自动全部置0// server是通信的发起者 建议创建一个全新的共享内存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];int fd = OpenFIFO(FIFO_NAME, READ);while (true){Wait(fd);// 临界区printf("%s\n", shmaddr);if (strcmp(shmaddr, "quit") == 0)break;}CloseFifo(fd);// 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;return 0;
}

3.4shmClient.cpp

#include "common.hpp"int main()
{Log("Client pid is : ", Debug) << getpid() << endl;// 创建公共的Key值key_t k = ftok(PATH_NAME, PROJ_ID); // typedef int key_tif (k < 0){Log("create key failed", Error) << " client key : " << ConvertToHex(k) << endl;exit(1);}Log("create key done", Debug) << " client key : " << ConvertToHex(k) << endl;// 获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if (shmid < 0){Log("obtain shm failed", Error) << " shmid : " << shmid << endl;exit(2);}Log("obtain shm success", Debug) << " shmid : " << shmid << endl;//sleep(10);// 将指定的共享内存 挂接到自己的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);if (shmaddr == nullptr){Log("attach shm failed", Error) << " shmid : " << shmid << endl;exit(3);}Log("attach shm success", Debug) << " shmid : " << shmid << endl;//sleep(10);// 开始通信   client将共享内存看做一个char 类型的bufferint fd = OpenFIFO(FIFO_NAME, WRITE);while (true){ssize_t s = read(0, shmaddr, SHM_SIZE - 1); // 从stdin读数据存到shmif (s > 0){shmaddr[s - 1] = 0; // abcd\n ==> abcd0Wake(fd);if (strcmp(shmaddr, "quit") == 0)break;}}CloseFifo(fd);/* 每一次都向共享内存的起始地址写入char *msg = "hello server, I'm Client. my pid: %d, inc: %c\n";for (char a = 'a'; a <= 'z'; a++){shmaddr[a - 'a'] = a;                              // 直接当成字符数组来看待snprintf(shmaddr, SHM_SIZE - 1, msg, getpid(), a); // 库函数格式化输入数据sleep(1);}strcpy(shmaddr, "quit");*/// 删除映射int n = shmdt(shmaddr);assert(n != -1);(void)n;Log("detach shm success", Debug) << " shmid : " << shmid << endl;//sleep(10);// client不需要chmctl删除return 0;
}

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

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

相关文章

HarmonyOS 数据持久化 关系型数据库之 查询逻辑编写

前面两篇文章 HarmonyOS 数据持久化 关系型数据库之 初始化操作 和 HarmonyOS 数据持久化 关系型数据库之 增删改逻辑编写 我们已经编写了 初始化 和 增删改 操作的基本逻辑 最后 收尾一下查询的函数 我们还是打开编辑器 然后 打开项目 找到 我们正在写的这个 relationalClass…

FPGA - 时钟Buffer的探究

1、IBUF : FPGA上所有的输入信号必须进过IBUF,vivado会自动给所有输入信号分配IBUF OBUF&#xff1a;FPGA上所有的输入信号必须进过IBUF,vivado会自动给所有输入信号分配OBUF BUFG:专用时钟的资源&#xff0c;目的是减少时钟抖动、增强时钟的驱动能力&#xff0c;vivado不会给信…

L2-2 老板的作息表(Python)

作者 陈越 单位 浙江大学 新浪微博上有人发了某老板的作息时间表&#xff0c;表示其每天 4:30 就起床了。但立刻有眼尖的网友问&#xff1a;这时间表不完整啊&#xff0c;早上九点到下午一点干啥了&#xff1f; 本题就请你编写程序&#xff0c;检查任意一张时间表&#xff0c…

Linux Docker安装redis缓存数据库

文章目录 一、查找Redis镜像二、拉取redis镜像三、创建数据目录和配置文件四、创建redis容器 一、查找Redis镜像 首先到docker镜像仓库下载redis镜像。地址&#xff1a;https://hub.docker.com/搜索redis&#xff0c;如下&#xff1a;找到对应想要下载的版本&#xff1a; 二、…

Leetcode - 二分查找 | 在排序数组中查找元素的第一个和最后一个位置

题目一&#xff1a;二分查找 二分查找 看到这道题之后&#xff0c;很快就能想到暴力的解法&#xff0c;把数组遍历一遍就能找到答案&#xff0c;时间复杂度O(n)。 假设存在一批数字[1&#xff0c;1&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#x…

面试宝典-【redis】

目录 1.什么是缓存穿透 ? 怎么解决 ? 2.什么是布隆过滤器 3.什么是缓存击穿 ? 怎么解决 ? 4.什么是缓存雪崩 ? 怎么解决 ? 5.redis做为缓存&#xff0c;mysql数据如何与redis进行同步?(双写) 6.排他锁是如何保证读写、读读互斥的呢&#xff1f; 7.你听说过延…

Java EE之wait和notify

一.多线程的执行顺序 由于多个线程执行是抢占式执行&#xff0c;就会导致顺序不同&#xff0c;同时就会导致出现问题&#xff0c;就比如俩个线程同时对同一个变量进行修改&#xff0c;我们难以预知执行顺序。 但在实际开发中&#xff0c;我们希望代码按一定的逻辑顺序执行&am…

C++ 多状态dp

目录 按摩师 打家劫舍 打家劫舍2 删除并获得点数 粉刷房子 按摩师 面试题 17.16. 按摩师 最大值问题 f : 预约此次的最长时间 g &#xff1a;不预约此次的最长时间 出现的错误&#xff1a;return max(f[n - 1]), g[n - 1]); 注意&#xff1a;①题目没给nums的范围&…

uniapp 云开发笔记

uniapp云开发官方文档https://uniapp.dcloud.io/uniCloud/learning.html 新建 关联云空间 云函数获取用户openID uniCloud API列表https://uniapp.dcloud.io/uniCloud/cf-functions.html#unicloud-api%E5%88%97%E8%A1%A8 自建云函数login event中包含前端传来的参数 uniCloud.…

Linux第74步_“设备树”下的LED驱动

使用新字符设备驱动的一般模板&#xff0c;以及设备树&#xff0c;驱动LED。 1、添加“stm32mp1_led”节点 打开虚拟机上“VSCode”&#xff0c;点击“文件”&#xff0c;点击“打开文件夹”&#xff0c;点击“zgq”&#xff0c;点击“linux”&#xff0c;点击“atk-mp1”&am…

使用阿里云服务器搭建网站教程,就这么简单!

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云百科aliyunbaike.com以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&a…

OS-Copilot:实现具有自我完善能力的通用计算机智能体

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ AI 缩小了人类间的知识和技术差距 论文标题&#xff1a;OS-Copilot: Towards Generalist Computer Agents with Self-Improvement 论文链接&#xff1a;https://arxiv.org/abs/2402.07456 项目主页&a…