进程间通信 —— 共享内存
目录
1.共享内存实现通信的原理
2.如何使用共享内存实现通信
共享内存通信接口介绍
shmget
shmat
shmdt
shmctl
使用示例
key和shmid
3.共享内存通信的优缺点
缺点:不提供任何同步机制,可能会造成数据混乱。
优点:共享内存是进程间通信最快的方式。
1.共享内存实现通信的原理
“共享内存实现进程间通信”,我们从名字就可以看出,共享内存其实就是通过提供一块共享的内存来实现进程间通信。
共享内存在计算机系统中可能不止一个,而操作系统作为计算机系统中软硬件资源的管理者,肯定要管理好共享内存,就要为共享内存创建对应的数据结构,进程只要能够找到该数据结构对象的起始地址就能找到某个特定的共享内存来进行通信。
Linux2.6内核源代码中共享内存的数据结构如下:
struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void *shm_unused2; /* ditto - used by DIPC */void *shm_unused3; /* unused */
};
共享内存进行通信的具体做法如下:
- 由操作系统在内存开辟一块区域,也就是提供进程间通信的场所。
- 构建进程的进程地址空间和共享内存的映射关系。保证不同的进程能够通过自己的进程地址空间来找到这块内存空间。
- 当通信结束的时候应该移除进程地址空间和共享内存的映射关系,减少系统资源的使用。
- 删除共享内存,避免造成资源泄漏的问题。
其原理图如下:

2.如何使用共享内存实现通信
为了使用共享内存进行进程间通信,操作系统给我们提供了一些系统调用接口,这些系统调用接口有不同的版本,我们主要学习 system V 版本 的共享内存接口,这些接口分别是 shmget、shmat、shmdt、shmctl。
共享内存通信接口介绍
shmget
功能:用于创建或获取一个共享内存段。
头文件:<sys/ipc.h> 、<sys/shm.h>
函数原型:int shmget(key_t key, size_t size, int shmflg)
参数:
- key_t key:这是共享内存段的键值,用于唯一标识共享内存段。可以通过
ftok函数生成,也可以直接使用IPC_PRIVATE创建一个新的共享内存段。如果key是IPC_PRIVATE,系统会创建一个新的共享内存段,且该段只能由当前进程及其子进程使用。- size_t size:表示共享内存段的大小,以字节为单位;如果是创建新的共享内存段,必须指定大小;如果是获取已存在的共享内存段,可以设置为 0。
- int shmflg:这是一个int类型的标志位,用于控制共享内存段的创建和访问权限;常用的创建标志位有这么两个,分别是 IPC_CREAT 和 IPC_EXEL,访问权限通过八进制数字来控制,两者通常使用按位或的方式来传参。
说明一下 IPC_CREAT 和 IPC_EXEL:
- IPC_CREAT:表示如果共享内存段不存在,则创建,如果共享内存已经存在,则获取。
- IPC_EXEL:单独使用时没意义,不能单独使用,需要和 IPC_CREAT 一起使用,表示共享内存不存在则创建,共享内存已经存在,则出错返回(一起使用时,需要使用 按位或 来操作)。
返回值:
- 成功时:返回共享内存段的标识符(shmid),用于后续操作(如
shmat、shmctl)。- 失败时:返回-1,并设置错误码来表示错误类型。
补充ftok函数:
前面说到了shmget的第一个参数可以通过ftok来获取,下面介绍一下ftok函数。
功能:ftok函数用于将一个文件路径和一个整数标识符(
proj_id)转换为一个唯一的 IPC 键值(key_t 类型的值),这个键值可以用于创建或访问共享内存。头文件:<sys/ipc.h>
函数原型:key_t ftok(const char *pathname, int proj_id)
参数:
- const char* pathname:文件路径,通常是一个已存在的文件(这个参数由用户来指定);ftok会使用该文件的 inode 号 和 设备号 来生成键值,文件必须存在且可访问,否则 ftok 会失败。
- int proj_id:一个整数标识符(通常是一个字符,取值范围是
0到255),用于在同一个文件路径下生成不同的键值。返回值:
- 成功:返回生成的IPC键值。
- 失败:返回-1,并设置错误码指示错误类型。
shmat
功能:用于将共享内存段映射到当前进程的地址空间。
头文件: <sys/types.h> 、<sys/shm.h>
函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg)
参数:
- int shmid:共享内存段的标识符,由
shmget函数返回,用于指定要映射的共享内存段。- const void* shmaddr:指定共享内存段附加到当前进程地址空间的位置,通常设置为
NULL,由系统自动选择合适的地址。(如果指定了地址,需要确保地址对齐和可用性,通常不建议手动指定)- int shmflg:标志位,用于控制共享内存段的映射行为。常用的标志位有
SHM_RDONLY和0。SHM_RDONLY: 表示以只读方式映射共享内存段。0: 以读写方式映射共享内存段(默认行为)。返回值:
- 成功时:返回共享内存段在进程地址空间中的起始地址(
void *类型),我们可以通过这个地址直接访问共享内存。- 失败时:返回
(void *)-1,并设置errno以指示错误类型。
shmdt
功能:用于将共享内存段从当前进程的地址空间中分离。
头文件:<sys/types.h>、<sys/shm.h>
函数原型:int shmdt(const void *shmaddr)
参数:
- const void* shmaddr:共享内存段在进程地址空间中的起始地址(由
shmat函数返回),用于指定要分离的共享内存段。返回值:
- 成功:返回0;
- 失败:返回-1,并设置错误码指示错误类型。
shmctl
在实际编程的时候,我们通常使用这个函数来释放共享内存。
需要注意:共享内存的生命周期是随内核的。也就是说,如果我们不释放使用完之后的共享内存,共享内存就会一直存在系统中,占用资源导致资源泄漏等问题;这一点基于文件的通信方式不同,文件的生命周期是随进程的,进程退出,文件自动就释放了。
功能:用于控制共享内存段的行为。它可以执行多种操作,例如获取共享内存段的信息、设置权限、删除共享内存段 等……
头文件:<sys/ipc.h>、<sys/shm.h>
函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf)
参数:
- int shmid:共享内存段的标识符(由
shmget函数返回),用于指定要操作的共享内存段。- int cmd:表示控制命令,用于指定要执行的操作。常用的选项有三个,分别是:IPC_STAT(获取共享内存段的状态信息,存储到
buf指向的结构体中)
IPC_SET(设置共享内存段的权限和属性,如: UID、GID、模式等……)
IPC_RMID(标记共享内存段为删除状态,当最后一个进程分离该共享内存段时,它会被删除)- struct shmid_ds* buf:这是一个指向
shmid_ds结构体 的指针(struct shmid_ds 就是共享内存的类型),用于存储或设置共享内存段的信息,当cmd被设置为 IPC_STAT 或IPC_SET 时,则需要提供该参数,否则可以设置为NULL。返回值:
- 成功:返回0;
- 失败:返回-1,并设置错误码指示错误类型。
使用示例
我们编写两个程序:
- writer.c:往共享内存中一次写入一个字符。
- reader.c:将共享内存中的数据全部读出。
writer.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define SHM_SIZE 1024 // 共享内存段大小int main() {// 生成键值key_t key = ftok("./", 65);if (key == -1) {perror("ftok failed");exit(1);}// 创建共享内存段int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");exit(1);}// 将共享内存段附加到当前进程的地址空间char *shmaddr = (char *)shmat(shmid, NULL, 0);if (shmaddr == (char *)-1) {perror("shmat failed");exit(1);}// 向共享内存写入数据char ch = 'a';char* addr = shmaddr;for(int i = 0; i < 26; ++i){*addr = ch+i;printf("我写入了一个字符:%c\n",*addr);addr++;sleep(1);}// 分离共享内存段if (shmdt(shmaddr) == -1) {perror("shmdt failed");exit(1);}// 删除共享内存段if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl failed");exit(1);}return 0;
}
reader.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define SHM_SIZE 1024 // 共享内存段大小int main() {// 生成键值key_t key = ftok("./", 65);if (key == -1) {perror("ftok failed");exit(1);}// 获取共享内存段int shmid = shmget(key, SHM_SIZE, 0666);if (shmid == -1) {perror("shmget failed");exit(1);}// 将共享内存段附加到当前进程的地址空间char *shmaddr = (char *)shmat(shmid, NULL, 0);if (shmaddr == (char *)-1) {perror("shmat failed");exit(1);}// 读取共享内存中的数据while(1){printf("共享内存中的数据:%s\n", shmaddr);sleep(1);}// 分离共享内存段if (shmdt(shmaddr) == -1) {perror("shmdt failed");exit(1);}return 0;
}
运行结果:

- 从运行结果来看,两个进程之间成功通信了。
key和shmid
通过使用,你可能有这么一个疑问。key是共享内存的唯一标识,shmid也是共享内存的唯一标识,为什么要提供两个呢?
我们可以这么来理解:计算机系统中可能存在多个共享内存,操作系统肯定要管理多个共享内存,就需要能够唯一的确定一个共享内存,这一点是通过key来区分的;同时,创建出的共享内存需要被用户使用,如果直接将key提供给用户,不就代表用户可以访问操作系统内核的数据了吗?这一点是万万不可的,操作系统为了管理好整个计算机的软硬件资源,不能让用户直接访问其内核的数据,于是,操作系统提供给用户一个shmid,让用户通过shmid间接操作特定的共享内存。
也就是说,key是操作系统在内核中区分不同共享内存的全局唯一标识符;而shmid是内核分配给进程的,让用户在代码层面区分不同的共享内存的。
我们可以通过 ipcs -m 命令来查看系统中的共享内存:

共享内存使用完之后要记得释放,不然就会造成资源泄漏,我们除了使用代码删除共享内存,还可以使用 ipcrm -m shmid 命令来删除指定的共享内存:

3.共享内存通信的优缺点
缺点:不提供任何同步机制,可能会造成数据混乱。
先说缺点,管道是提供同步机制的,而共享内存并不提供任何进程同步的机制,写方不会管读方,读方也不会管写方,从我们前面的代码运行效果就可以看出:

读端刚开始读的时候,读到了三个abc,而没有读到a、ab这两个数据,说明写端一直在写,并没有管读端。
优点:共享内存是进程间通信最快的方式。
主要有以下几点原因:
直接访问内存减少拷贝:共享内存允许多个进程直接访问同一块物理内存区域,数据不需要在进程之间复制,而是直接在共享内存中读写,读写效率高。其他 IPC 机制(如管道、消息队列、Socket)通常需要在用户空间和内核空间之间复制数据,这会增加额外的开销。
减少进程上下文切换:其他 IPC 机制(如管道、消息队列)通常需要调用系统调用(如 read、write),这会触发用户态和内核态之间的上下文切换,增加开销;共享内存的读写操作完全在用户空间完成,不需要内核的介入。
不需要内核缓冲区:其他 IPC 机制(如管道、消息队列)需要内核维护缓冲区,增加了内存和 CPU 的开销。共享内存不需要内核维护额外的缓冲区,数据直接存储在共享内存区域中。
相关文章:
进程间通信 —— 共享内存
目录 1.共享内存实现通信的原理 2.如何使用共享内存实现通信 共享内存通信接口介绍 shmget shmat shmdt shmctl 使用示例 key和shmid 3.共享内存通信的优缺点 缺点:不提供任何同步机制,可能会造成数据混乱。 优点:共享内存是进程…...
本地搭建dify结合ollama+deepseek方法详解
1.安装ollama,安装deepseek-r1:8b模型 2.安装dify社区版 访问Dify GitHub项目地址 git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d docker compose ps 查重以下实例是否启动成功: 更新Dif…...
冒泡排序算法优化
一 概述 冒泡排序是一种简单的交换排序算法,其核心思想是通过相邻元素比较和交换将最大元素逐步移动到数组末尾。 二、基础冒泡排序 void bubbleSort(int arr[], int n) { for (int i = 0; i < n-1; i++) { for (int j = 0; j < n-i-1; j++) { if…...
Linux系统软件管理
systemctl 控制软件启动和关闭 Linux系统很多软件支持使用systemctl命令控制:启动,停止,开启自启。 能被systemctl管理的软件,一般被称为:服务。 语法:systemctl start|stop|status|enable|disable 服务名…...
C# 使用 Newtonsoft.Json 序列化和反序列化对象实例
Newtonsoft.Json(也被称为 Json.NET)是一个广泛使用的用于在 C# 中进行 JSON 序列化和反序列化的开源库。下面将详细介绍如何使用它来序列化和反序列化对象。 1. 安装 Newtonsoft.Json 如果你使用的是 Visual Studio,可以通过 NuGet 包管理…...
在Linux桌面上创建Idea启动快捷方式
1、在桌面新建idea.desktop vim idea.desktop [Desktop Entry] EncodingUTF-8 NameIntelliJ IDEA CommentIntelliJ IDEA Exec/home/software/idea-2021/bin/idea.sh Icon/home/software/idea-2021/bin/idea.svg Terminalfalse TypeApplication CategoriesApplication;Developm…...
从0开始的操作系统手搓教程19:构建我们的内存管理——第二步:内存子系统进化,获取页!
目录 讨论页表的分析和索引的完成 完成一个宽泛的页获取 从指定的内存池中分配若干页 获取准备用来提供给客户端方向的虚拟地址起始位置 根据内存池的选择,完成对物理内存的获取 关联我们的物理内存和虚拟内存 编写尝试 运行的截图 现在,我们将会…...
数学软件Matlab下载|支持Win+Mac网盘资源分享
如大家所了解的,Matlab与Maple、Mathematica并称为三大数学软件。Matlab应用广泛,常被用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人,控制系统等领域。 Matlab将数值分析、矩阵计算、科学…...
Basler acA1920-40gc
软件 下载Basler软件 | Basler AG 说明书 ace acA1920-40gc | GigE相机 | Basler | Basler AG 支持PTP同步 在使用 Basler acA1920-40gc 相机和 Polyn View 软件时,确认 PTP(Precision Time Protocol)同步是否成功,可以通过…...
FASIONAD:自适应反馈的类人自动驾驶中快速和慢速思维融合系统
24年11月来自清华、早稻田大学、明尼苏达大学、多伦多大学、厦门大学马来西亚分校、电子科大(成都)、智平方科技和河南润泰数字科技的论文“FASIONAD : FAst and Slow FusION Thinking Systems for Human-Like Autonomous Driving with Adaptive Feedbac…...
sql server 复制从备份初始化数据
参考 : 从备份初始化订阅(事务) - SQL Server | Microsoft Learn sql server 复制默认是用快照初始化数据的,也支持从备份初始化数据,参考如上...
java八股文之框架
1.Spring框架中的Bean是否线程安全的 Spring框架中的Bean默认是单例的,不是线程安全的。因为一般在Spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的…...
R语言+AI提示词:贝叶斯广义线性混合效应模型GLMM生物学Meta分析
全文链接:https://tecdat.cn/?p40797 本文旨在帮助0基础或只有简单编程基础的研究学者,通过 AI 的提示词工程,使用 R 语言完成元分析,包括数据处理、模型构建、评估以及结果解读等步骤(点击文末“阅读原文”获取完整代…...
2020年蓝桥杯Java B组第二场题目+部分个人解析
#A:门牌制作 624 解一: public static void main(String[] args) {int count0;for(int i1;i<2020;i) {int ni;while(n>0) {if(n%102) {count;}n/10;}}System.out.println(count);} 解二: public static void main(String[] args) {…...
【Azure 架构师学习笔记】- Azure Databricks (13) -- 搭建Medallion Architecture part 1
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (12) – Medallion Architecture简介 前言 上文已经介绍了关于Medallion的知识,本文开始用ADB 来实现, 但是基于内容较…...
2025年2月21日优雅草内测分发站全新升级-测试运营-优雅草内测分发站新用户提供免费100下载点-2月28日正式运营并且提供私有化部署版本
2025年2月21日优雅草内测分发站全新升级-测试运营-优雅草内测分发站新用户提供免费100下载点-2月28日正式运营并且提供私有化部署版本 说明 优雅草内测分发站新用户提供免费100下载点,优雅草分运营站和demo测试站 运营站:www.youyacao.cn 提供免费100…...
通过 PromptTemplate 生成干净的 SQL 查询语句并执行SQL查询语句
问题描述 在使用 LangChain 和 Llama 模型生成 SQL 查询时,遇到了 sqlite3.OperationalError 错误。错误信息如下: OperationalError: (sqlite3.OperationalError) near "sql SELECT Name FROM MediaType LIMIT 5; ": syntax error [SQL: …...
本地部署Embedding模型API服务的实战教程
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…...
IP段转CIDR:原理Java实现
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
[STM32]从零开始的STM32 DEBUG问题讲解及解决办法
一、前言 最近也是重装了一次keil,想着也是重装了,也是去官网下载了一个5.41的最新版,在安装和配置编译器和别的版本keil都没太大的区别,但是在调试时,遇到问题了,在我Debug的System Viewer窗口中没有GPIO&…...
MySQL当中的Lock
1. 总览锁的类型 锁的类型: 锁类型 符号/缩写 描述 全局锁 FTWRL 锁定整个数据库(FLUSH TABLES WITH READ LOCK),用于全库备份。 表级锁 - 表锁 S/X LOCK TABLES ... READ(共享锁)或 WRITE&#…...
electron-builder打包时github包下载失败【解决办法】
各位朋友们,在使用electron开发时,选择了electron-builder作为编译打包工具时,是否经常遇到无法从github上下载依赖包问题,如下报错: Get "https://github.com/electron/electron/releases/download/v6.1.12/ele…...
【免费】YOLO[笑容]目标检测全过程(yolo环境配置+labelimg数据集标注+目标检测训练测试)
一、yolo环境配置 这篇帖子是我试过的,非常全,很详细【cudaanacondapytorchyolo(ultralytics)】 yolo环境配置 二、labelimg数据集标注 可以参考下面的帖子,不过可能会出现闪退的问题,安装我的流程来吧 2.1 labelimg安装 label…...
服务器禁止操作汇总(Server Prohibits 0peration Summary)
服务器禁止操作汇总 一、禁忌操作TOP10 1. 直接断电关机 💥 血泪案例:某物流公司运维拔电源强制关机,导致数据库事务中断,20万订单状态丢失。 📌 技术解析: • 直接断电可能引发: ✅ 文件系统…...
UE5 Slate类的基础创建
创建一个slate类的基础代码 #pragma onceclass SCustomDetailPlane : public SCompoundWidget {SLATE_BEGIN_ARGS(SCustomDetailPlane){}SLATE_END_ARGS()public:SCustomDetailPlane();~SCustomDetailPlane();void Construct(const FArguments& InArgs);};***************…...
springboot2.7.18升级springboot3.3.0遇到的坑
druid的警告,警告如下: 运行警告2025-02-28T09:20:31.28508:00 WARN 18800 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidSpringAopConfiguration of type [com.a…...
服务器IPMI用户名、密码批量检查
背景 大规模服务器部署的时候,少不了较多的网管和监测平台,这些平台会去监控服务器的性能、硬件等指标参数,为了便于管理和控制,则需要给服务器IPMI带外管理添加较多的用户,这就需要对较多的服务器检查所对应的IPMI用…...
JAVA面试_进阶部分_netty面试题
1.BIO、NIO 和 AIO 的区别? BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。 伪异步 IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。 NIO&#x…...
小红书湖仓架构的跃迁之路
作者:李鹏霖(丁典),小红书-研发工程师,StarRocks Contributor & Apache Impala Committer 本文整理自小红书工程师在 StarRocks 年度峰会上的分享,介绍了小红书自助分析平台中,StarRocks 与 Iceberg 结合后&#x…...
C++-第十七章:包装器
目录 第一节:std::function 第二节:std::bind 2-1.基本介绍 2-2.调整顺序(不常用) 2-3.调整个数 2-4.std::bind与std::function 下期预告: C中有3种可调用对象:函数指针、仿函数对象、lambda函数,经过包装器包装后屏…...
