Linux小黑板(9):共享内存

"My poor lost soul"
上章花了不少的篇幅讲了讲基于管道((匿名、命名))技术实现的进程间通信。进程为什么需要通信?目的是为了完成进程间的"协同",提高处理数据的能力、优化业务逻辑的实现等等,在linux中我们已经谈过了一个通信的大类——管道。根据System V标准的基础上提出了,另一套进程通信的标准。
-----前言
一、System V简介
System V,曾经也被称为AT&TSystem V,是Unix操作系统众多版本中的一支。
System V的第一个版本,发布于1983年。它引进了一些特性,例如vi编辑器和curses库。其中也包括了对DEC VAX机器的支持。同时也支持使用消息进行进程间通信,信号量和共享内存。 取自这里
为什么这么"隆重"地介绍System V呢?因为这是一套通信标准。当在后面学了共享内存的多个API后,你会发现消息队列、信号量的接口函数极为相似。

二、共享内存
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。 取自这里
文字描述显然很恼人,我们直接上图。
通信的本质,就是让不同能够看到同一份资源。

由此,我们对共享内存的理解是:其本质就是开辟在物理内存里的一块内存块,用来进行IPC通信的。
让需要通信的进程能够看到这块内存块,并在上面进行通信行为。
三、实现共享内存
(1)创建共享内存块
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key. A new shared memory segment, with size equal to the value of size rounded up to a multiple of PAGE_SIZE, is created if key has the value IPC_PRIVATE or key isn't IPC_PRIVATE, no shared memory segment corresponding to key exists, and IPC_CREAT is specified in shmflg.
这里也就简单说说shmget的参数。
key:这个参数至关重要。它是连接共享内存,找到共享内存块的关键。也就是说,一个进程只要拿到了在这个key值,就可以访问这一块内存块。
size:申请共享内存块的大小。这个size会按照PAGE_SIZE大小(向上对齐)。
shmflg:我们常用的参数就是 IPC_CREATE \ IPC_EXCL:
IPC_CREATE:如果不存在就创建、如果存在就获取
IPC_EXCL:不能单独使用。IPC_CREATE | IPC_EXCL 如果不存在就创建,存在就返回错误(保证给用户的共享内存块一定是新创建的)。
如果创建成功,shmget会返回一个有效标记内存块的标识符
那么怎么形成key这个唯一标识的关键字呢?库里给我们提供了一个函数,可以让生成的key是一个唯一标识的数字key_t类型。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
convert a pathname and a project identifier to a System V IPC key:用pathname + proj_id转换为key
并且这个pathname不是随便乱取的。而是一个"现有"、"可访问"的文件。
如何让两个进程看到一块共享内存?只需要知道key就行了。key是怎么生成的?ftok(pathname,proj_id)。这两个参数一样,不久可以生成相同的key了嘛?


如何理解key?
在操作系统中,一定会存在多个key_t类型的 有效标识符。那么这么多标识符一定会被操作系统管理。管理的本质:先描述、再组织。
我们举个例子:


我们可以看到,OS会为共享内存块维护一份结构体struct shimid_ds 用来管理共享内存块。
共享内存的生命周期随OS:

我们在学习管道通信时,一旦进程有一方结束,那么双方的通信管道也会随之关闭。为什么现如今,进程结束了,它们申请的共享内存块仍然存在呢??
管道的生命周期随进程,共享内存的生命周期随OS。
为此,我们不得不手动去关闭掉,我们编写的进程所打开的共享内存块。
ipcrm -m + shmid

(2)挂接与去关联
我们现如今拿到了共享内存块的标识符shmid,那么如何通过这个shmid找到共享内存的位置呢?
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat() attaches the System V shared memory segment identified by shmid to the address space of the calling process. The attaching address is specified by shmaddr with one of the following criteria.
通过这个函数可以通过shmid 与 共享内存块挂接,并返回该共享内存块的地址。
参数: shmaddr 、shmflag
If shmaddr is NULL, the system chooses a suitable (unused) address at which to attach the segment.
这里我们通常设置为 nullptr,那么OS就会为我们选择一个适合的挂接到了这个内存块的地址。
shmflag通常为设置为0.
返回参数:
On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate the cause of the error.
如果成功,shmat()返回挂接这个共享内存块的地址,返回-1是error的
void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if ((long long)mem == -1) // Linux下 是64位系统 一个指针大小为8字节{std::cerr << "attachShm: " << mem << std::endl;exit(3);}return mem;
}
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmdt() detaches the shared memory segment located at the address specified by shmaddr from the address space of the calling process. The to-be-detached segment must be currently attached with shmaddr equal to the value returned by the attaching shmat() call.
shmdt()会将传入的共享内存块地址去挂接(关联)。调用shmdt(),必须传入通过shmat()获取的地址。
On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
0为去关联成功,-1位失败。
void detachShm(void* start)
{int ret = shmdt(start);if(ret == -1){std::cerr << "attachShm: " << ret << std::endl;exit(4);}
}
(3)释放共享内存块
共享内存块的生命周期随OS,我们每申请共享内存空间,都得"ipcrm -m + shmid"释放内存空间,当我们不再使用时,这未免太过麻烦了。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl() performs the control operation specified by cmd on the System V shared memory segment whose identifier is given in shmid.
shmctl()通过cmd传入的命令操作,对shmid表示的共享内存块进行处理。
要在进程结束时,关闭共享内存,我们需要传入的参数是:
IPC_RMID Mark the segment to be destroyed.
void delShm(int shmid)
{if (shmctl(shmid, IPC_RMID, nullptr) == -1){std::cerr << "delShm: " << shmid << std::endl;exit(5);}
}
当然,shmctl不仅仅可以归还共享内存,当cmd传入的参数是IPC_STAT时,我们可以获取由shmid填充的结构体内容。
像这样:
int main()
{int shmid = shmget(key);struct shmid_ds ds;shmctl(shmid,IPC_STAT,&ds);ds.shm_perm.__key;ds.shm_atime;//..return 0;
}
(4)测试
有了上面对函数的理解和实现,我们可以进行一定的通信了。
我们想让Sever读取client发送的内容: "Hello Server! pid + 发送次数"。
Client:
int main()
{// 1.申请keykey_t key = getKey(PATH_NAME, PROJ_ID);std::cout << "Client key: " << key << std::endl;// 2.申请内存块int shmid = getShm(key);std::cout << "Client shmid: " << shmid << std::endl;// 3.挂接void *start = attachShm(shmid);printf("attach success, address start: %p\n", start);// 真正的通信区域const char* msg = "Hello Server!";int cnt = 0;while (true){ snprintf((char*)start,MAX_PAGE,"%s[pid:%d]编号信息:[%d]\n",msg,getpid(),cnt++);sleep(1);}// 4.去关联detachShm(start);return 0;
}
Server:
int main()
{// 1.申请keykey_t key = getKey(PATH_NAME, PROJ_ID);std::cout << "Server key: " << key << std::endl;// 2.申请内存块int shmid = createShm(key);std::cout << "Server shmid: " << shmid << std::endl;// 3.挂接void *start = attachShm(shmid);printf("attach success, address start: %p\n", start);// 真正的通信区域while (true){printf("client say : %s\n", start);sleep(1);}// 4.去关联detachShm(start);printf("detach success");// 5.关闭共享内存delShm(shmid);return 0;
}
我们来看看效果吧~

这是怎么回事??噢,原来是权限问题。

我们在创建共享内存的时候,需要附上权限大小。

这样就完成了双方进程间的通信。

四、共享内存 vs 管道

但是,管道自带同步与互斥!如果写端不写入,读端会成阻塞状态,直到写端写入,此时有数据可读。但是共享内存不一样,它没有同步与互斥操作,即便写端没有写入数据,读端照样会向内存块中读取。
总结:
①共享内存是一块存在物理内存的,由操作系统管理的内存块。它属于IPC通信方式的一种,在所有的通信方式中速度是最快的。
②共享内存的创建:1.申请(shmget) 2.挂接(shmat) 3.去关联(shmdt) 4.关闭空间(shmctl).
③管道自带同步与互斥,共享内存不支持。
本篇也就告一段落了,感谢你的阅读。
祝你好运~向阳而生。

相关文章:

Linux小黑板(9):共享内存
"My poor lost soul"上章花了不少的篇幅讲了讲基于管道((匿名、命名))技术实现的进程间通信。进程为什么需要通信?目的是为了完成进程间的"协同",提高处理数据的能力、优化业务逻辑的实现等等,在linux中我们已经谈过了一个通信的大类…...

Detr源码解读(mmdetection)
Detr源码解读(mmdetection) 1、原理简要介绍 整体流程: 在给定一张输入图像后,1)特征向量提取: 首先经过ResNet提取图像的最后一层特征图F。注意此处仅仅用了一层特征图,是因为后续计算复杂度原因,另外&am…...

一个.Net Core开发的,撑起月6亿PV开源监控解决方案
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 项目发布后,对于我们程序员来说,项目还不是真正的结束,保证项目的稳定运行也是非常重要的,而对于服务器的监控,就是保证稳定运行的手段之一。对数据库、…...

C语言数据结构初阶(2)----顺序表
目录 1. 顺序表的概念及结构 2. 动态顺序表的接口实现 2.1 SLInit(SL* ps) 的实现 2.2 SLDestory(SL* ps) 的实现 2.3 SLPrint(SL* ps) 的实现 2.4 SLCheckCapacity(SL* ps) 的实现 2.5 SLPushBack(SL* ps, SLDataType x) 的实现 2.6 SLPopBack(SL* ps) 的实现 2.7 SLP…...

K8S常用命令速查手册
K8S常用命令速查手册一. K8S日常维护常用命令1.1 查看kubectl版本1.2 启动kubelet1.3 master节点执行查看所有的work-node节点列表1.4 查看所有的pod1.5 检查kubelet运行状态排查问题1.6 诊断某pod故障1.7 诊断kubelet故障方式一1.8 诊断kubelet故障方式二二. 端口策略相关2.1 …...

Linux系统下命令行安装MySQL5.6+详细步骤
1、因为想在腾讯云的服务器上创建自己的数据库,所以我在这里是通过使用Xshell 7来连接腾讯云的远程服务器; 2、Xshell 7与服务器连接好之后,就可以开始进行数据库的安装了(如果服务器曾经安装过数据库,得将之前安装的…...

13.STM32超声波模块讲解与实战
目录 1.超声波模块讲解 2.超声波时序图 3.超声波测距步骤 4.项目实战 1.超声波模块讲解 超声波传感器模块上面通常有两个超声波元器件,一个用于发射,一个用于接收。电路板上有4个引脚:VCC GND Trig(触发)ÿ…...

逆向之Windows PE结构
写在前面 对于Windows PE文件结构,个人认为还是非常有必要掌握和了解的,不管是在做逆向分析、免杀、病毒分析,脱壳加壳都是有着非常重要的技能。但是PE文件的学习又是一个非常枯燥过程,希望本文可以帮你有一个了解。 PE文件结构…...
ACL是什么
目录 一、ACL是什么 二、ACL的使用:setacl与getacl 1)针对特定使用者的方式: 1. 创建acl_test1后设置其权限 2. 读取acl_test1的权限 2)针对特定群组的方式: 3)针对有效权限 mask 的设置方式…...

操作系统核心知识点整理--内存篇
操作系统核心知识点整理--内存篇按段对内存进行管理内存分区内存分页为什么需要多级页表TLB解决了多级页表什么样的缺陷?TLB缓存命中率高的原理是什么?段页结合: 为什么需要虚拟内存?虚拟地址到物理地址的转换过程段页式管理下程序如何载入内存?页面置…...

从零开始学习iftop流量监控(找出服务器耗费流量最多的ip和端口)
一、iftop是什么iftop是类似于top的实时流量监控工具。作用:监控网卡的实时流量(可以指定网段)、反向解析IP、显示端口信息等官网:http://www.ex-parrot.com/~pdw/iftop/二、界面说明>代表发送数据,< 代表接收数…...

第一篇博客------自我介绍篇
目录🔆自我介绍🔆学习目标🔆如何学习单片机Part 1 基础理论知识学习Part 2 单片机实践Part 3 单片机硬件设计🔆希望进入的公司🔆结束语🔆自我介绍 Hello!!!我是一名即已经步入大二的计算机小白。 --------…...
No suitable device found for this connection (device lo not available(网络突然出问题)
当执行 ifup ens33 出现错误:[rootlocalhost ~]# ifup ens33Error: Connection activation failed: No suitable device found for this connection (device lo not available because device is strictly unmanaged).1解决办法:[rootlocalhost ~]# chkc…...

【算法设计技巧】分治算法
分治算法 用于设计算法的另一种常用技巧为分治算法(divide and conquer)。分治算法由两部分组成: 分(divide):递归解决较小的问题(当然,基准情况除外)治(conquer):然后,从子问题的解构建原问题的解。 传统上&#x…...

已解决kettle新建作业,点击保存抛出异常Invalid state, the Connection object is closed.
已解决kettle新建作业,点击保存进资源数据库抛出异常Invalid state, the Connection object is closed.的解决方法,亲测有效!!! 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 一个小伙伴…...

【设计模式】 工厂模式介绍及C代码实现
【设计模式】 工厂模式介绍及C代码实现 背景 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来…...
深入浅出PaddlePaddle函数——paddle.arange
分类目录:《深入浅出PaddlePaddle函数》总目录 相关文章: 深入浅出TensorFlow2函数——tf.range 深入浅出Pytorch函数——torch.arange 深入浅出PaddlePaddle函数——paddle.arange 语法 paddle.arange(start0, endNone, step1, dtypeNone, nameNone…...
X86 ATT常用寄存器及其操作指令
X86 AT&T常用寄存器及其操作指令 常用寄存器 esp寄存器:当我们需要访问堆栈帧中的变量时,可以使用esp寄存器来获取堆栈帧的基址,以便能够正确地访问堆栈帧中的变量。ebp寄存器:当我们需要调用一个函数时,可以使用…...

Kotlin 高端玩法之DSL
如何在 kotlin 优雅的封装匿名内部类(DSL、高阶函数)匿名内部类在 Java 中是经常用到的一个特性,例如在 Android 开发中的各种 Listener,使用时也很简单,比如://lambda button.setOnClickListener(v -> …...

理光M2701复印机载体初始化方法
理光M2701基本参数: 产品类型:数码复合机 颜色类型:黑白 复印速度:单面:27cpm 双面:16cpm 涵盖功能:复印、打印、扫描 网络功能:支持无线、有线网络打印 接口类型:USB2.0…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...