【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量
文章目录
- 一、system V共享内存
- 1. 共享内存的原理
- 2. 共享内存相关函数
- 3. 共享内存实现通信
- 4. 共享内存的特点
- 二、system V消息队列(了解)
- 三、system V信号量(信号量)
一、system V共享内存
1. 共享内存的原理
共享内存是一种在多个进程之间进行进程间通信的机制。它允许多个进程访问相同的物理内存区域,从而实现高效的数据交换和通信。
因为进程具有独立性(隔离性),内核数据结构包括对应的代码、数据与页表都是独立的。OS系统为了让进程间进行通信,必须让不同的进程看到同一份资源。所以共享内存的原理如下:
1.申请一块空间
2.将创建好的内存映射进进程的地址空间。
共享内存让不同的进程看到同一份的资源就是在物理内存上申请一块内存空间,将创建好的内存分别与各个进程的页表之间建立映射,然后在虚拟地址空间中将虚拟地址填充到各自页表的对应位置,建立起物理地址与虚拟地址的联系。


我们把创建好的内存称为共享内存,把进程和共享内存建立映射关系的操作称为挂接,把取消进程和内存的映射关系称为去关联,把释放内存称为释放共享内存。
共享内存的建立: 在物理内存当中申请共享内存空间;将申请到的共享内存挂接到地址空间,即建立映射关系。
共享内存的释放: 共享内存与地址空间去关联,即取消映射关系;释放共享内存空间,即将物理内存归还给系统。
对共享内存的理解:
-
共享内存不属于通信的任意一个进程,其属于操作系统,由操作系统所管理。
-
管道的本质是文件,操作系统已经有相应的内核数据结构来管理文件,因此不需要再去设计新的内核数据结构去管理管道。而共享内存是专门为了进程间通信而设计的,操作系统可能会有很多共享内存,那么操作系统就需要将这些共享内存管理起来。
-
管理的方式是先描述再组织,那么共享内存就等于共享内存块加上共享内存对应的内核数据结构。
-
对共享内存的修改包括对属性的修改和对内容的修改。
2. 共享内存相关函数
shmget:用来创建或者获取共享内存。失败时返回-1。

参数:
shmflg: 通常被设置成两个选项: IPC_CREAT、 IPC_EXCL
- IPC_CREAT:共享内存不存在,则创建,如果存在则获取;
- IPC_EXCL:无法单独使用,IPC_CREAT | IPC_EXCL:如果不存在就创建,如果存在就出错返回
size: 共享内存的大小
key: 共享内存字段的名字,通信的进程需要通过该key值找到同一个共享内存,从而进行通信。因此key能保证多个进程看到同一份共享内存,能进行唯一性标识。
ftok:生成key。失败时返回-1。

ftok函数将pathname和project id 经过一定的算法转换成 key,pathname必须存在,projectid不能为0。
OS一定会存在很多的共享内存,共享内存本质就是在内存中申请一块空间,而key能进行唯一标识。OS申请的,自然要做管理,共享内存也是如此,如何管理:先描述,在组织。所以共享内存=物理内存块+共享内存的相关属性。进程如果在内存中创建了共享内存,为了让共享内存在系统中保证唯一的,通过key来进行标识,只要让另一个进程也看到同一个key。
shmat:将共享内存段连接到进程地址空间(建立页表映射关系)。成功返回一个指针,指向共享内存第一个字节;失败返回 (void*) -1。

参数:
shmid: 共享内存的标识符。
shmaddr: 指定连接地址,如果设置为nullptr,则让操作系统指定连接到合适的地址上。
shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY。shmflg 等于 SHM_RDONLY 时,表示连接操作用来只读共享内存。
shmdt:将共享内存段与当前进程脱离。成功返回0,失败返回-1。

参数:
shmaddr: 由shmat函数所返回的指针。
这里我们需要注意:将共享内存和当前进程脱离不等于删除共享内存。
shmctl:用语控制共享内存。

参数:
shmid: 由shmget函数返回的共享内存标识符。
cmd: 将要采取的动作。(有三个可以选择)
buf: 为指向一个保存着共享内存的模式状态和访问权限的数据结构。不关心共享内存的内核数据结构时,buf可以设置为nullptr。
这里我们需要注意的是:当进程运行结束时,进程创建的共享内存还会存在,这是因为system V IPC资源的生命周期是随其内核的。其内核可以通过代码删除(
shmctl函数),也可以通过ipcrm -m shmid指令手动删除。使用ipcs -m指令可以查看系统中已经创建好的共享内存。

3. 共享内存实现通信
makefile
.PHONY:all
all: shmclient shmservershmclient:client.ccg++ -o $@ $^ -std=c++11
shmserver:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f shmclient shmserver
comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP_#include <iostream>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;#define PATHNAME "."
#define PROJID 0x6666const int gsize = 4096;//获取key
key_t getKey()
{key_t k = ftok(PATHNAME, PROJID);if(k == -1){cerr << "error: " << errno << " : " << strerror(errno) << endl;exit(1);}return k;
}//转十六进制函数
string toHex(int x)
{char buffer[64];snprintf(buffer, sizeof buffer, "0X%x", x);return buffer;
}//共享内存公共函数
static int createShmHelper(key_t k, size_t size, int flag)
{int shmid = shmget(k, size, flag);if(shmid == -1){cerr << "error: " << errno << " : " << strerror(errno) << endl;exit(2);}return shmid;
}//创建共享内存
int createShm(key_t k, size_t size)
{umask(0);return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}//获取共享内存
int getShm(key_t k, size_t size)
{return createShmHelper(k, size, IPC_CREAT);
}//关联进程
char* attachShm(int shmid)
{char* start = (char*)shmat(shmid, nullptr, 0);return start;
}//去关联进程
void detachShm(char* start)
{int n = shmdt(start);assert(n != -1);(void)n;
}//释放共享内存
void delShm(int shmid)
{int n = shmctl(shmid, IPC_RMID, nullptr);assert(n != -1);(void)n;
}#define SERVER 1
#define CLIENT 0class Init
{
public:Init(int t):_type(t){key_t key = getKey();if(_type == SERVER)_shmid = createShm(key, gsize);else_shmid = getShm(key, gsize);_start = attachShm(_shmid); }char* getChar(){return _start;}~Init(){detachShm(_start);if(_type == SERVER) delShm(_shmid);}
private:char* _start;int _type; // server or clientint _shmid;
};#endif
服务端:server.cc
#include "comm.hpp"int main()
{Init init(SERVER);char* start = init.getChar();int n = 0;while(n <= 35){cout <<"client -> server# "<< start << endl;sleep(1);n++;}// // 1. 创建key// key_t k = getKey();// cout << "server:" << toHex(k) << endl;// // 2. 创建共享内存// int shmid = createShm(k, gsize);// cout << "shmid:" << shmid << endl;// sleep(8);// // 3. 将自己和共享内存关联起来// char* start = attachShm(shmid);// sleep(20);// // 4. 将自己和共享内存去关联// detachShm(start);// sleep(3);// 5. 删除共享内存// delShm(shmid);return 0;
}
客户端:client
#include "comm.hpp"int main()
{Init init(CLIENT);char *start = init.getChar();char c = 'A';while(c <= 'Z'){start[c - 'A'] = c;c++;start[c - 'A'] = '\0';sleep(1);}// // 1. 获取key// key_t k = getKey();// cout << "client:" << toHex(k) << endl;// // 2. 获取共享内存// int shmid = getShm(k, gsize);// cout << "shmid:" << shmid << endl;// // 3. 将自己和共享内存关联起来// char* start = attachShm(shmid);// sleep(15);// // 4. 将自己和共享内存去关联// detachShm(start);return 0;
}

4. 共享内存的特点
优点:
- 只要通信双方使用共享内存,一方直接向共享内存中写入数据,另一方就可以马上看到对方写入的数据。共享内存是所有进程间通信(IPC)中速度最快的!因为其不需要过多的拷贝(不需要将数据给操作系统)
下面我们来比对一下共享内存和管道:
管道通信的拷贝:
C++输入设备把数据拷贝到cin或者stdin文件缓存区不考虑,这里总共进行了4次拷贝。

共享内存通信的拷贝:
直接从输入到共享内存,从共享内存到输出。

缺点:
以共享内存的方式进行进程间通信缺乏访问控制,会带来同步问题!比如:写端还没将全部数据写入,读端就已经开始读取了,这将会带来巨大的问题!
二、system V消息队列(了解)
消息队列 是OS提供的内核级队列,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
- IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
常用系统调用:ftok,msgget(创建消息队列),msgctl(控制消息队列),msgsnd(向消息队列发送数据),msgrcv(从消息队列中读取数据)等。

三、system V信号量(信号量)
下面,我们先来引出几个概念:
"公共资源:" 被多个进程同时访问的资源,访问没有保护的公共资源:数据不一致问题。要让不同的进程看到同一份资源是为了通信,通信是为了让进程间实现协同,而进程之间具有独立性,所以为了解决独立性问题要让进程看到同一份资源,但是会导致数据不一致的问题。
"互斥": 任何一个时刻,都只允许一个执行流在进行共享资源的访问。各进程间竞争使用这些资源,竞争的这种关系为进程的互斥。
"临界资源": 任何一个时刻,都只允许一个执行流在进行访问的共享资源,叫做临界资源。
"临界区": 临界资源是需要通过代码访问的,凡是访问临界资源的代码,叫做临界区。
"原子性": 要么不做、要么做完,只有两种确定状态的属性,叫做原子性。
任何一个执行流,想访问临界资源中的一个子资源时,不能直接访问。得先申请信号量,信号量/信号灯 本质是一个计数器,描述资源数量的计数器。 信号量是对临界资源的预定机制。
- 申请信号量
只要申请信号量成功,临界资源内部一定给你预留了你想要的资源,申请信号量本质是对临界资源的一种预定机制,让信号计数器减减,信号量计数器为0时,进程申请信号量无法成功。只能阻塞等待其他进程退出,才能申请信号量,访问临界资源。 - 访问临界资源——进程执行自己的临界区代码
- 释放信号量——信号量计数器加加


我们可以发现,共享内存、消息队列、信号量接口相似度非常高,获取与删除,都是system V标准的进程间通信。
OS如何管理:先描述,在组织,对相关资源的内核数据结构做管理,对于共享内存、消息队列、信号量的第一个成员都是ipc_perm:
struct ipc_perm {key_t __key; /* Key supplied to shmget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq; /* Sequence number */
};
虽然内部的属性差别很大,但是维护它们的数据结构的第一个成员都是ipc_perm类型的成员变量,都可以通过key来标识唯一性。这样设计的好处:在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。((struct shmid_ds*)perms[0],强转,此时就可以访问其他剩下的属性)

相关文章:
【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量
文章目录 一、system V共享内存1. 共享内存的原理2. 共享内存相关函数3. 共享内存实现通信4. 共享内存的特点 二、system V消息队列(了解)三、system V信号量(信号量) 一、system V共享内存 1. 共享内存的原理 共享内存是一种在…...
CentOS实现html转pdf
CentOS使用实现html转PDF,需安装以下软件: yum install wkhtmltopdf # 转换工具,将HTML文件或网页转换为PDFyum install xorg-x11-server-Xvfb # 虚拟的X服务器,在无图形界面环境下运行图形应用程yum install wqy-zenhei-fonts #…...
【C++】基于多设计模式下的同步异步日志系统
✍作者:阿润021 📖专栏:C 文章目录 一、项目介绍二、项目实现准备工作1.日志系统技术实现策略2.相关技术知识补充2.1 不定参函数设计2.2 设计模式 三、日志项目框架设计1.模块划分2.各模块关系图 四、详细代码实现1.实用工具类设计2.日志等级…...
防火墙监控工具
防火墙监控是跟踪在高效防火墙性能中起着关键作用的重要防火墙指标,防火墙监控通常应包括: 防火墙日志监控防火墙规则监控防火墙配置监控防火墙警报监控 防火墙监控服务的一个重要方面是它应该是主动的。主动识别内部和外部安全威胁有助于在早期阶段识…...
组合模式——树形结构的处理
1、简介 1.1、概述 树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等。如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题。组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形…...
从实体按键看 Android 车载的自定义事件机制
作者:TechMerger 在汽车数字化、智能化变革的进程中,越来越多的车机设计或部分、或全部地舍弃了实体按键,进而把车主操作的入口转移到了车机 UI 以及语音助手。 但统一、高效的零层级 UI 颇为困难,语音的准确率、覆盖率亦不够完善…...
nosql之redis集群
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、redis集群1.单节点redis服务器带来的问题2.集群redis3.集群的优势4.redis集群的实现方法5.redis群集的三种模式5.1 主从复制5.2 哨兵5.3 集群 二、Redis 主从复…...
SpringBoot 项目使用 Redis 对用户 IP 进行接口限流
一、思路 使用接口限流的主要目的在于提高系统的稳定性,防止接口被恶意打击(短时间内大量请求)。 比如要求某接口在1分钟内请求次数不超过1000次,那么应该如何设计代码呢? 下面讲两种思路,如果想看代码可…...
SLA探活工具EaseProbe
工具介绍 EaseProbe可以做三种工作:探测、通知和报告。 项目地址:https://github.com/megaease/easeprobe 1、安装 [rootlocalhost ]# yum -y install unzip go [rootlocalhost ]# unzip easeprobe-main.zip [rootlocalhost ]# cd easeprobe-main [r…...
[Java] 观察者模式简述
模式定义:定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,他的所有依赖者都会收到通知并且更新 依照这个图,简单的写一个代码 package Section1.listener;import java.ut…...
linux驱动定时器实现按键按下打印字符
#include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h>struct device_node *dev; unsigned int irqno; //中断处理函数 irqreturn_t myirq_handler(int irq,void *…...
反转链表(JS)
反转链表 题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出:[2,1]示例 3&…...
[PyTorch][chapter 45][RNN_2]
目录: RNN 问题 RNN 时序链问题 RNN 词组预测的例子 RNN简洁实现 一 RNN 问题 RNN 主要有两个问题,梯度弥散和梯度爆炸 1.1 损失函数 梯度 其中: 则 1.1 梯度爆炸(Gradient Exploding) 上面矩阵进行连乘后…...
基于canvas画布的实用类Fabric.js的使用
目录 前言 一、Fabric.js简介 二、开始 1、引入Fabric.js 2、在main.js中使用 3、初始化画布 三、方法 四、事件 1、常用事件 2、事件绑定 3、事件解绑 五、canvas常用属性 六、对象属性 1、基本属性 2、扩展属性 七、图层层级操作 八、复制和粘贴 1、复制 2…...
基于SpringBoot+Vue驾校理论课模拟考试系统源码(自动化部署)
DrivingTestSimulation Unity3D Project, subject two, simulated driving test 【更新信息】 更新时间-2021-1-17 解决了方向盘不同机型转动轴心偏离 更新时间-2021-2-18 加入了手刹系统 待更新-2021-6-19(工作太忙少有时间更新,先指出问题…...
SpringBoot使用Redis对用户IP进行接口限流
使用接口限流的主要目的在于提高系统的稳定性,防止接口被恶意打击(短时间内大量请求)。 一、创建限流注解 引入redis依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId&g…...
MeterSphere学习篇
从开发环境部署开始 metersphere-1.20.4 源码下载地址: https://gitee.com/fit2cloud-feizhiyun/MeterSphere/tree/v1.20/ MeterSphere GitHub 相关插件程序下载 相关准备 安装mysql 配置IDEA...
大数据技术之Clickhouse---入门篇---数据类型、表引擎
星光下的赶路人star的个人主页 今天没有开始的事,明天绝对不会完成 文章目录 1、数据类型1.1 整型1.2 浮点型1.3 布尔型1.4 Decimal型1.5 字符串1.6 枚举类型1.7 时间类型1.8 数组 2、表引擎2.1 表引擎的使用2.2 TinyLog2.3 Memory2.4 MergeTree2.4.1 Partition by分…...
【微服务架构设计】微服务不是魔术:处理超时
微服务很重要。它们可以为我们的架构和团队带来一些相当大的胜利,但微服务也有很多成本。随着微服务、无服务器和其他分布式系统架构在行业中变得更加普遍,我们将它们的问题和解决它们的策略内化是至关重要的。在本文中,我们将研究网络边界可…...
天下风云出我辈,AI准独角兽实在智能获评“十大数字经济风云企业
时值盛夏,各地全力拼经济的氛围同样热火朝天。在浙江省经济强区余杭区这片创业热土上,人工智能助力数字经济建设正焕发出蓬勃生机。 7月28日,经专家评审、公开投票,由中共杭州市余杭区委组织部(区委两新工委ÿ…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
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.登…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
