【Linux】进程间通信——system V共享内存
目录
写在前面的话
System V共享内存原理
System V共享内存的建立
代码实现System V共享内存
创建共享内存shmget()
ftok()
删除共享内存shmctl()
挂接共享内存shmat()
取消挂接共享内存shmdt()
整体通信流程的实现
写在前面的话
上一章我们讲了进程间通信的第一种方式 --- 管道,这一章我们将继续讲解进程间的通信的第二种方式 --- system V共享内存。
在讲解之前,还是先建议去把进程间通信管道的方式和原理搞明白,这样理解起system V共享内存来会简单许多。
System V共享内存原理
共享内存是一种进程间通信(IPC)的机制,允许多个进程共享同一块内存区域,以便它们可以直接读取和写入其中的数据,从而实现高效的数据共享和通信。

在共享内存中,当进程task_struct创建或连接到共享内存段时,操作系统会为每个进程分配一个虚拟地址空间mm_srtuct,将这个虚拟地址通过页表 映射到相同的物理内存区域。这样,多个进程就可以通过自己的虚拟地址访问相同的物理内存,实现对同一块内存的共享访问。通过虚拟地址到物理地址的映射,多个进程可以看到同一个共享内存.
那么如何释放共享内存呢?
也很简单,只需要将每个进程 和 共享内存建立的映射去掉,然后释放掉共享内存即可.
System V共享内存的建立
假设有很多进程都在用共享内存,这样内存中也会出现大量的 共享内存块,所以OS要把这些共享内存块管理起来,方式:先描述再组织,这样要把共享内存属性抽象成数据结构,然后利用一些方式将这些数据结构组织起来.所以:
1. 共享内存 = 共享内存块 + 共享内存块对应的内核数据结构
2.共享内存块一定不属于任何一个进程,而是属于操作系统.
建立共享内存的大体流程如下:
创建共享内存段:使用
shmget()系统调用来请求创建一个共享内存段。该调用需要指定共享内存的大小、权限和标志等参数,并返回一个唯一的共享内存标识符。连接到共享内存段:使用
shmat()系统调用将当前进程附加到共享内存段。这个调用将返回共享内存段的地址,并将该地址映射到当前进程的虚拟地址空间。访问共享内存:连接到共享内存的进程可以通过在其地址上执行内存操作,直接读取和写入共享内存段中的数据。进程可以使用指针、数组或结构体等方式在共享内存段中存储和访问数据。
分离共享内存:当进程完成对共享内存的访问后,使用
shmdt()系统调用将其与共享内存段分离。分离后,进程将无法再访问共享内存段,但共享内存段仍然存在。删除共享内存段:当不再需要共享内存段时,可以使用
shmctl()系统调用删除它。这个调用需要指定共享内存标识符和特定的控制操作,比如传递IPC_RMID参数表示删除共享内存段.
具体的使用方法及原理,我们在下面马上讲解。
代码实现System V共享内存
按我们上面所说的,第一步是利用shmget()建立共享内存段,我们来看一下它的用法.
创建共享内存shmget()

这个函数作用是创建并获取一个共享内存。
先说第二个参数,是size,代表的是要创建的共享内存有多大
再来说第三个参数shmflg,代表我们要设置的选项,共有两个选项:
1.IPC_CREATE:创建共享内存,如果底层已经存在,则获取并返回;如果不存在,则创建共享内存然后再返回,
2.IPC_EXCL:单独使用它没有意义,一般和IPC_CREATE合起来使用,见下:
3.IPC_CREATE | IPC_EXCL:如果底层不存在,则创建共享内存并返回;如果底层存在,则出错返回。言外之意,如果返回成功,那么一定是一个全新的内存块!
最后再来说第一个参数 key.
我们利用共享内存通信时会有一个问题,要通信的对方进程,怎么保证对方看到的共享内存就是我创建的呢?毕竟有很多共享内存.
这个我们就需要通过key,key数据是多少不重要,只要保证在系统里唯一即可! 两个通信端A 和 B,只要使用同一个key,便可以找到同一块共享内存。因为key是唯一的,即这个共享内存块也是唯一的!
相当于是给每个共享内存块编了个号,这个号码是唯一的,所以只要拿到了编号,就能找到相同的共享内存块。
那么这个唯一的key值如何得到呢?这里需要用到ftok()函数
ftok()
我们先来看一下函数的使用:

-
pathname:一个字符串,用于标识一个文件的路径名。通常会选择一个已经存在的文件,因为ftok()函数将使用该文件的inode编号和proj_id参数通过算法来生成键值key。 -
proj_id:一个整数,作为用于生成键的项目标识号。该参数通常取一个非负整数。
然后我们来看一下返回值:

看到如果成功的话,生成的键值被返回,否则-1被返回。
这些说清楚了,我们来用一下吧:
首先四个文件,comm.hpp,里面包含了必要的头文件及宏定义,这个便不再展示了;
Log.hpp是日志文件,上一章我们写了,这里不写也可以,直接cout输出也行。
shmClient.cc中我们写入以下代码:
#include "comm.hpp"
#include "Log.hpp"
int main()
{key_t k = ftok(PATH_NAME,PROJ_ID);Log("create key done",Debug) << " client key : " << k << endl;return 0;
}
shmServer中拷贝一份代码,然后把输出语句中的client改成server,然后我们编译运行来及看一下效果:

可以发现生成了一样的key值,这样便可以找到同一块共享内存块了.
这样shmget的第一个参数也讲完了,接下来说一下返回值.

返回值是如果建立成功,则返回这段共享内存的标识符,否则返回-1并且错误码被设置。
删除共享内存shmctl()
当我们创建好共享内存后,最后还需要删除它,因为共享内存的生命周期是随内核的!
不关闭的话,只要操作系统一直在运行,那么它就一直存在,占用空间资源。所以必须需要删除.
有两种方法删除:手动指令删除、代码删除
指令删除:
我们首先创建了一个共享内存,然后该进程执行完毕,进程退出。 
我们在终端输入
ipcs -m
来查看当前共享内存的使用情况

可以发现确实没有释放掉。然后我们可以利用
ipcrm -m shmid
来删除对应的共享内存,此时我输入这条指令后便没有了这块内存了。

这里提到了perms,这个是权限的意思,我们可以在shmget的第三个选项 加上权限,如下:
int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
此时perms就变成了666.
代码删除
每次手动删除太麻烦了,我们直接卸载程序里到时候自动帮我们删除不更方便吗
所以我们需要用到一个函数shmctl()

-
shmid:共享内存段的标识符(ID),即通过调用shmget()函数创建共享内存时返回的shmid。 -
cmd:控制命令,用于指定要执行的操作类型。可以使用以下命令之一:IPC_STAT:获取共享内存段的状态信息,将结果存储在buf参数指向的struct shmid_ds结构体中。IPC_SET:设置共享内存段的状态信息,使用buf参数中提供的值。IPC_RMID:删除共享内存段,将其标记为删除状态,并在释放最后一个进程的附加段之后销毁。
-
buf:一个指向struct shmid_ds结构体的指针,用于传递或接收共享内存段的状态信息。
这是struct shmid_ds结构体指针的内容:
struct shmid_ds {struct ipc_perm shm_perm; // 共享内存的权限信息size_t shm_segsz; // 共享内存的大小time_t shm_atime; // 上一次连接共享内存的时间time_t shm_dtime; // 上一次与共享内存断开连接的时间time_t shm_ctime; // 上一次修改共享内存的时间pid_t shm_cpid; // 创建共享内存的进程IDpid_t shm_lpid; // 最后一个操作共享内存的进程IDunsigned short shm_nattch; // 当前连接到共享内存的进程数量// 其他字段... };
其中第二个参数cmd我们目前只使用第3个IPC_RMID删除共享内存的选项。
第三个参数buf我们暂且不使用,直接传入nullptr。
int n = shmctl(shmid,IPC_RMID,nullptr);
挂接共享内存shmat()
我们创建好了共享内存,当然需要将进程的地址空间与其挂接上,然后 才能使用,这里使用到了函数shmat().

shmat() 函数接受三个参数:
-
shmid:共享内存段的标识符(ID),即通过调用shmget()函数创建共享内存时返回的shmid。表示你想挂接哪一个共享内存。 -
shmaddr:共享内存段连接到进程地址空间的首地址。通常将其设置为NULL,指示系统选择适当的地址。如果想要指定特定的地址,可以传递一个非空的地址值。但不建议这样使用。 -
shmflg:标志参数,用于指定连接共享内存的选项。常用的选项有:SHM_RDONLY:以只读方式连接共享内存,不允许写入。SHM_RND:将shmaddr参数忽略,系统选择一个地址以进行连接。
其他选项参考 shmat() 函数的文档以获得更多详细信息。
它的返回值是void*,是一个指向共享内存段的指针,即连接到进程地址空间的首地址。我们需要将结果强转为我们需要的类型,一般为char*,和malloc的使用类似。
所以可以如下这样使用:
char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
shmaddr便是连接的进程地址空间的首地址。
取消挂接共享内存shmdt()
删除共享内存时,无论有多少个进程与共享内存连接,都会被直接清理掉,这种方式不太好,所以在删除共享内存前,可以先取消挂接。当挂接数为0时,再释放共享内存。
同样先来看一下用法:
![]()
这个参数正好是我们刚才shmat返回的进程地址空间的首地址shmaddr,表示将共享内存从调用进程的地址空间中分离,使得该进程无法再访问该共享内存。
再来看一下返回值:
![]()
如果取消挂接成功,则返回0,失败则返回-1.
所以我们可以直接这么使用:
//3.将指定的共享内存,挂接到自己的地址空间char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);//这里就是通信的逻辑//4.将指定的共享内存,从自己的进程地址空间取消关联int n = shmdt(shmaddr);assert(n != -1);
整体通信流程的实现
认识了上面的接口,加上我们在共享内存的建立中所说的步骤,我们便可以制作一个完整的利用System V共享内存通信的流程了。
0. 我们要利用ftok()生成共享内存唯一标识key.
1. 然后我们利用key调用shmget()函数创建共享内存
2. 接着我们需要利用shmat()挂接上共享内存
3. 然后实现通信的流程
4. 利用shmdt()取消挂接
5. 利用shmctl()删除共享内存(一般是server,谁创建的谁删除)
这里一共四个文件,分别为Log.hpp(日志信息),comm.hpp(共用的头文件),shmServer.cc, shmClient.cc
Log.hpp(日志信息)
#pragma once #include <iostream> #include <ctime> #include<string> using namespace std;#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3string msg[] = {"Debug ","Notice","Warning","Error" };ostream& Log(string message,int level) {cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;return cout; }comm.hpp(共用的头文件)
#pragma once #include<iostream> #include<string> #include<cstring> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> using namespace std;#define PATH_NAME "/home/hyx" #define PROJ_ID 0x66 #define SHM_SIZE 4096 //最好是页(PAGE:4096)的整数倍,假设是4097,OS也会申请4096*2的空间,剩下的4095空间相当于浪费了shmServer.cc
#include "comm.hpp" #include "Log.hpp" 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);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,SHM_RDONLY);Log("attach shm done",Debug) << " shmid: " << shmid << endl;// sleep(10);//这里就是通信的逻辑//将共享内存当做一个大字符串// char buffer[SHM_SIZE];//结论1:只要双方使用shm,一方直接向共享内存中写入数据。另一方就可以立马看到// 共享内存是所有进程IPC,速度最快的! 因为不需要过多的拷贝!表现为不需要将数据拷贝给操作系统for(;;){printf("%s\n",shmaddr);if(strcmp(shmaddr,"quit") == 0) break;sleep(1);}//4.将指定的共享内存,从自己的进程地址空间取消关联int n = shmdt(shmaddr);assert(n != -1);Log("detach shm done",Debug) << " shmid: " << shmid << endl;// sleep(10);//5..删除共享内存n = shmctl(shmid,IPC_RMID,nullptr);assert(n != -1);Log("delete shm done",Debug) << " shmid: " << shmid << endl;return 0; }shmClient.cc
#include "comm.hpp" #include "Log.hpp" int main() {key_t k = ftok(PATH_NAME,PROJ_ID);if(k < 0){Log("create key done", Error) << "client key : " << k << endl;exit(1);}Log("create key done",Debug) << " client key : " << k << endl;//获取共享内存int shmid = shmget(k,SHM_SIZE,IPC_CREAT);if(shmid < 0){Log("create shm failed", Error) << "client key : " << k << endl;exit(2);}Log("create shm success", Debug) << "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", Debug) << "client key : " << k << endl;// sleep(10);//使用//client将共享内存看做一个char类型的bufferchar a = 'a';for(;a <= 'e'; a++){//我们是每一次都向shmaddr[共享内存起始地址]写入snprintf(shmaddr,SHM_SIZE-1,\"hello,server, my pid is: %d, inc : %c\n",getpid(),a);sleep(2);}strcpy(shmaddr,"quit");//去关联int n = shmdt(shmaddr);assert(n != -1);Log("detach shm success",Debug) << "client key : " << k << endl;// sleep(10);//client 不需要删除共享内存,server负责这些,和client没关系。return 0; }
然后我们编译运行,分两个窗口观察:

client

可以看到,双方都实现了通信。
到这里System V共享内存就介绍完毕了,如果有不懂或疑问的地方,欢迎评论区或私信哦~
相关文章:
【Linux】进程间通信——system V共享内存
目录 写在前面的话 System V共享内存原理 System V共享内存的建立 代码实现System V共享内存 创建共享内存shmget() ftok() 删除共享内存shmctl() 挂接共享内存shmat() 取消挂接共享内存shmdt() 整体通信流程的实现 写在前面的话 上一章我们讲了进程间通信的第一种方式…...
【数据结构】快速排序
快速排序是一种高效的排序算法,其基本思想是分治法。它将一个大问题分解成若干个小问题进行解决,最后将这些解合并得到最终结果。 快速排序的主要思路如下: 选择一个基准元素:从待排序的数组中选择一个元素作为基准(…...
人机融合智能中的事实与价值
在人机融合智能中,事实和价值分别扮演着不同的角色和功能。 事实是客观存在的真实描述,可以通过数据、观测和验证等方式获取。在人机融合智能中,人工智能通过处理和分析大量的数据来提供客观事实的支持。例如,在搜索引擎中&#x…...
JVM | 从类加载到JVM内存结构
引言 我在上篇文章:JVM | 基于类加载的一次完全实践 中为你讲解如何请“建筑工人”来做一些定制化的工作。但是,大型的Java应用程序时,材料(类)何止数万,我们直接堆放在工地上(JVM)…...
Golang之路---04 并发编程——WaitGroup
WaitGroup 为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出,前面使用了 time.Sleep 这种简单的方式。 由于写的 demo 都是比较简单的, sleep 个 1 秒,我们主观上认为是够用的。 但在实际开发中,开发人员是无法…...
React(4)
1.属性(props)初始 状态state都是组件内部写的,也就是A组件内的state就只能A组件里面用,其他组件复用不了。因此属性props就可以。 比如一个导航栏,首页有,购物车有,我的有,他们三个…...
STM32 CubeMX USB_(HID 鼠标和键盘)
STM32 CubeMX STM32 CubeMX USB_HID(HID 鼠标和键盘) STM32 CubeMX前言 《鼠标小节》一、STM32 CubeMX 设置USB时钟设置USB使能UBS功能选择 二、代码部分添加代码鼠标发送给PC的数据解析实验效果 《键盘小节》STM32 CubeMX 设置(同上…...
[PM]敏捷开发之Scrum总结
在项目管理中,不少企业和项目团队也发现传统的项目管理模式已不能很好地适应今天的项目环境的要求。因此,敏捷项目管理应运而生,本文将为大家介绍Scrum敏捷项目管理以及应用方法。 什么是Scrum敏捷项目管理 敏捷项目管理作为新兴的项目管理模…...
大数据Flink(五十七):Yarn集群环境(生产推荐)
文章目录 Yarn集群环境(生产推荐) 一、准备工作...
web集群学习:源码安装nginx配置启动服务脚本
1、源码安装nginx,并提供服务脚本。 1、源码安装会有一些软件依赖 (1)检查并安装 Nginx 基础依赖包 pcre-devel 、openssl-devel # rpm -qa | egrep pcre-devel | openssl-devel(2)安装 Nginx 所需的 pcre 库 正则支…...
LNMP
lNmp安装: 一、LNMP LNMP架构是目前成熟的企业网站应用模式之一,指的是协同工作的一整套系统和相关软件, 能够提供动态Web站点服务及其应用开发环境。LNMP是一个缩写词,具体包括Linux操作系统、nginx网站服务器、MySQL数据库服务…...
Python网络爬虫在信息采集中的应用及教程
Python网络爬虫在信息采集中的应用与法律警告 摘要 随着互联网的发展,我们每天都面临着海量的信息。这些信息蕴含着无尽的价值,而要从中获取有用的数据,网络爬虫就成了我们的得力助手。Python作为一门简单而又强大的编程语言,被…...
云主机测试Flink磁盘满问题解决
问题描述: 使用云主机测试Flink时,根目录满了。 经排查发现运行Flink任务后根目录空间一直在减少,最后定位持续增加的目录是/tmp目录 解决方法: 修改Flink配置使用一个相对较大的磁盘目录做为Flink运行时目录 # Override the…...
iOS开发-NSOperationQueue实现上传图片队列
iOS开发-NSOperationQueue实现上传图片队列 在开发中,遇到发帖需要上传图片,需要上传队列,这时候用到了NSOperationQueue 一、NSOperation与NSOperationQueue 什么NSOperation NSOperation为控制任务状态、优先级、依赖关系以及任务管理提…...
通过 CCIP 构建跨链应用(5 个案例)
Chainlink 的跨链互操作性协议(CCIP)是一种新的通用跨链通信协议,为智能合约开发人员提供了以最小化信任的方式在区块链网络之间传输数据和通证的能力。 目前,部署在多个区块链上的应用程序面临着资产、流动性和用户的碎片化问题…...
基于 yolov8 的人体姿态评估
写在前面 工作中遇到,简单整理博文内容为使用预训练模型的一个预测 Demo测试图片来源与网络,如有侵权请告知理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停…...
计算机视觉(六)图像分类
文章目录 常见的CNNAlexnet1乘1的卷积 VGG网络Googlenet(Inception V1、V2、V3)全局平均池化总结 Resnet、ResnextResNet残差网络ResNeXt网络 应用案例VGGResnet 常见的CNN Alexnet DNN深度学习革命的开始 沿着窗口进行归一化。 1乘1的卷积 VGG网络…...
解决:vue通过params传参刷新页面参数丢失问题以及实现vue路由可选参数的解决办法
目录 🙋♂️ 实现params传参,刷新页面不丢参 🙋♂️ 实现vue配置可选路由参数 🙋♂️ 参考资料 解决vue 通过 name 和 params 进行页面传参时,刷新页面参数丢失问题以及vue路由实现可选参数 🙋♂…...
将postman接口导出的json转换为markdown
您可以使用 Postman 官方提供的工具或第三方工具将 Collection 文件转换为 Markdown 文件。 方式一 Postman 官方提供的工具是 Newman,它是一个命令行工具,可以帮助您运行和测试 Postman Collection,还可以将 Collection 转换为多种格式&am…...
教您一招解决找素材困难好的方法
创作视频内容时,找到合适的素材是至关重要的。然而,有时候寻找视频素材可能会变得困难。本文将分享一些实用的方法,帮助您轻松解决找视频素材困难的问题。 素材库和在线平台是寻找视频素材的首选方法。 利用专业的视频剪辑工具 在电脑上安…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
