进程间通信 —— 共享内存

目录

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 */
};

共享内存进行通信的具体做法如下:

  1. 由操作系统在内存开辟一块区域,也就是提供进程间通信的场所。
  2. 构建进程的进程地址空间和共享内存的映射关系。保证不同的进程能够通过自己的进程地址空间来找到这块内存空间。
  3. 当通信结束的时候应该移除进程地址空间和共享内存的映射关系,减少系统资源的使用。
  4. 删除共享内存,避免造成资源泄漏的问题。

其原理图如下:

 

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),用于后续操作(如 shmatshmctl)。
  • 失败时:返回-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_STATIPC_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 机制(如管道、消息队列)通常需要调用系统调用(如 readwrite),这会触发用户态和内核态之间的上下文切换,增加开销;共享内存的读写操作完全在用户空间完成,不需要内核的介入。

不需要内核缓冲区:其他 IPC 机制(如管道、消息队列)需要内核维护缓冲区,增加了内存和 CPU 的开销。共享内存不需要内核维护额外的缓冲区,数据直接存储在共享内存区域中。

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

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

相关文章

3.【基于深度学习YOLOV11的车辆类型检测系统】

文章目录 研究背景主要工作内容一、系统核心功能介绍及效果演示演示&#xff1a;软件主要功能&#xff1a;检测界面各大板块说明&#xff1a;检测区域&#xff1a;结果显示&#xff1a;主要功能说明:&#xff08;1&#xff09;图片检测说明&#xff08;2&#xff09;图片批量检…

汽车悬架系统技术演进:从被动到全主动的革新之路(主动悬架类型对比)

在汽车工业的百年发展史中&#xff0c;悬架系统始终是平衡车辆性能与舒适性的关键战场。随着消费者对驾乘体验要求的不断提升&#xff0c;传统被动悬架已难以满足中高端车型的需求&#xff0c;而半主动与全主动悬架技术的崛起&#xff0c;正在重塑行业格局。本文将深入解析三大…

跨平台文件互传工具

一款高效便捷的文件互传工具&#xff0c;支持在线快速传输各种文件格式&#xff0c;无需注册&#xff0c;直接分享文件。适用于个人和团队间的文件共享&#xff0c;跨平台支持&#xff0c;轻松解决文件传输问题。免费的文件传输服务&#xff0c;让你的工作更高效。 gotool

算法题(81):询问学号

审题&#xff1a; 需要我们根据给出的n值确定录入数据个数&#xff0c;然后根据给出的数据存储学号。再根据m值确定需要输出的学号个数&#xff0c;然后根据数组内容输出学号 思路: 我们可以利用数组进行数据顺序存储&#xff0c;以及随机读取完成本题 由于学号最大为1e9&#…

(转)Java多态`

Base是父类&#xff0c;Sub是子类。 Base b new Sub(); b.out(); b持有的是子类Sub的实例&#xff0c;调用的是子类的方法。

JavaWeb后端基础(3)

原打算把Mysql操作数据库的一些知识写进去&#xff0c;但是感觉没必要&#xff0c;要是现在会的都是简单的增删改查&#xff0c;所以&#xff0c;这一篇&#xff0c;我直接从java操作数据库开始写&#xff0c;所以这一篇大致就是记一下JDBC、MyBatis、以及SpringBoot的配置文件…

Selenium自动化测试秘籍:解锁常用函数实战指南

目录 1.元素的定位 2.操作测试对象 2.1.点击/提交对象 2.2.模拟按键输入 2.3.清除文本内容 2.4.获取文本信息&#xff1a; 特殊情况&#xff1a;元素属性值 获取当前页面标题和URL方法&#xff1a; 3. 窗口 3.1.切换窗口&#xff1a; 3.2.窗口大小的设置 4.屏幕截图…

Linux操作系统5-进程信号1(信号基础)

上篇文章&#xff1a;Linux操作系统4-进程间通信5&#xff08;共享内存实现两个进程通信&#xff09;-CSDN博客 本篇Gitee仓库&#xff1a;myLerningCode/l25 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com) 本篇重点&#xff1a;信号的概念 一. 信号基…

【博资考4】网安学院-硕转博考试内容

【博资考4】硕转博考试内容 - 网络安全与基础理论 写在最前面一. **21年硕转博面试内容回顾**网络、逆向、操作系统、攻防、漏洞1. **网络安全常见攻击方式及其防范措施**1.1 **DDoS攻击&#xff08;分布式拒绝服务&#xff09;**1.2 **SQL注入攻击**1.3 **XSS攻击&#xff08;…

考研/保研复试英语问答题库(华工建院)

华南理工大学建筑学院保研/考研 英语复试题库&#xff0c;由华工保研er和学硕笔试第一同学一起整理&#xff0c;覆盖面广&#xff0c;助力考研/保研上岸&#xff01;需要&#x1f447;载可到文章末尾见小&#x1f360;。 以下是主要内容&#xff1a; Part0 复试英语的方法论 Pa…

TCP基本入门-简单认识一下什么是TCP

部分内容来源&#xff1a;小林Coding TCP的特点 1.面向连接 一定是“一对一”才能连接&#xff0c;不能像 UDP 协议可以一个主机同时向多个主机发送消息&#xff0c;也就是一对多是无法做到的 2.可靠的 无论的网络链路中出现了怎样的链路变化&#xff0c;TCP 都可以保证一个…

使用Uni-app实现语音视频聊天(Android、iOS)

使用Uni-app开发手机端APP已经变得很普遍&#xff0c;同一套代码就可以打包成Android App 和 iOS App&#xff0c;相比原生开发&#xff0c;可以节省客观的人力成本。那么如何使用Uni-app来开发视频聊天软件或视频会议软件了&#xff1f;本文将详细介绍在Uni-app中&#xff0c;…

mac电脑中使用无线诊断.app查看连接的Wi-Fi带宽

问题 需要检查连接到的Wi-Fi的AP硬件支持的带宽。 步骤 1.按住 Option 键&#xff0c;然后点击屏幕顶部的Wi-Fi图标&#xff1b;2.从下拉菜单中选择 “打开无线诊断”&#xff08;Open Wireless Diagnostics&#xff09;&#xff1b;3.你可能会看到一个提示窗口&#xff0c;…

Flutter - StatefulWidget (有状态的 Widget) 和 生命周期

StatefulWidget /*** 需求&#xff1a;* 两个按钮&#xff0c;一个计数器* 这里要用到 StatefulWidget,因为 StatelessWidget 通常用来展示固定不变的数据*/ main() > runApp(MyApp());class MyApp extends StatelessWidget {overrideWidget build(BuildContext context) {…

(八)趣学设计模式 之 装饰器模式!

目录 一、 啥是装饰器模式&#xff1f;二、 为什么要用装饰器模式&#xff1f;三、 装饰器模式的实现方式四、 装饰器模式的优缺点五、 装饰器模式的应用场景六、 装饰器模式 vs 代理模式七、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢…

如何使用 Ollama 的 API 来生成文本

如何使用 Ollama 的 API 来生成文本 简介 生成文本 生成文本的示例 加载模型 卸载模型 简介 Ollama 提供了一个 RESTful API&#xff0c;允许开发者通过 HTTP 请求与 Ollama 服务进行交互。这个 API 覆盖了所有 Ollama 的核心功能&#xff0c;包括模型管理、运行和监控。本…

Matlab地图绘制教程第2期—水陆填充图

上一期分享了海岸线图的绘制方法&#xff1a; 本着由浅入深的理念&#xff0c;本期再来分享一下水陆填充图的绘制方法。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;Matlab地图绘制教程系列&#xff0c;旨在降低大家使用Matlab进行地图类科研绘图的门槛&#xff0c;…

8.Dashboard的导入导出

分享自己的Dashboard 1. 在Dashboard settings中选择 JSON Model 2. 导入 后续请参考第三篇导入光放Dashboard&#xff0c;相近

计算机毕设-基于springboot的融合多源高校画像数据与协同过滤算法的高考择校推荐系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

SpringBoot 整合mongoDB并自定义连接池,实现多数据源配置

要想在同一个springboot项目中使用多个数据源&#xff0c;最主要是每个数据源都有自己的mongoTemplate和MongoDbFactory。mongoTemplate和MongoDbFactory是负责对数据源进行交互的并管理链接的。 spring提供了一个注解EnableMongoRepositories 用来注释在某些路径下的MongoRepo…