Linux进程间通信——共享内存

Linux进程间通信——共享内存

  • 1、创建/打开共享内存
    • 1.1 shmget
    • 1.2 ftok
  • 2、关联和接触关联
    • 2.1 shmat
    • 2.2 shmdt
  • 3、删除共享内存
    • 3.1 shmctl
  • 3.2 相关shell命令
  • 3.3 共享内存状态
  • 4、进程间通信
  • 5、shm和mmap的区别

原文链接

共享内存不同于内存映射区,它不属于任何进程,并且不受进程生命周期的影响。通过调用Linux提供的系统函数就可得到这块共享内存。使用之前需要让进程和共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联, 解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中共享内存的效率是最高的。

共享内存操作默认不阻塞,如果多个进程同时读写共享内存,可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步,比如:信号量,共享内存内部没有提供这种机制。

1、创建/打开共享内存

1.1 shmget

在使用共享内存之前必须要先做一些准备工作,如果共享内存不存在就需要先创建出来,如果已经存在了就需要先打开这块共享内存。不管是创建还是打开共享内存使用的函数是同一个,函数原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 参数:
    • key: 类型 key_t 是个整形数, 通过这个key可以创建或者打开一块共享内存,该参数的值一定要大于0
    • size: 创建共享内存的时候, 指定共享内存的大小,如果是打开一块存在的共享内存, size是没有意义的
    • shmflg:创建共享内存的时候指定的属性
      • IPC_CREAT: 创建新的共享内存,如果创建共享内存, 需要指定对共享内存的操作权限,比如:IPC_CREAT | 0664
      • IPC_EXCL: 检测共享内存是否已经存在了,必须和 IPC_CREAT一起使用
  • 返回值:共享内存创建或者打开成功返回标识共享内存的唯一的ID,失败返回-1

场景1:创建一块大小为4k的共享内存

shmget(100, 4096, IPC_CREAT|0664);

场景2:创建一块大小为4k的共享内存, 并且检测是否存在

// 	如果共享内存已经存在, 共享内存创建失败, 返回-1, 可以perror() 打印错误信息
shmget(100, 4096, IPC_CREAT|0664|IPC_EXCL);

场景3:打开一块已经存在的共享内存

// 函数参数虽然指定了大小和IPC_CREAT, 但是都不起作用, 因为共享内存已经存在, 只能打开, 参数4096也没有意义
shmget(100, 4096, IPC_CREAT|0664);
shmget(100, 0, 0);

场景4:打开一块共享内存, 如果不存在就创建

shmget(100, 4096, IPC_CREAT|0664);

1.2 ftok

shmget() 函数的第一个参数是一个大于0的正整数,如果不想自己指定可以通过 ftok()函数直接生成这个key值。该函数的函数原型如下:

// ftok函数原型
#include <sys/types.h>
#include <sys/ipc.h>// 将两个参数作为种子, 生成一个 key_t 类型的数值
key_t ftok(const char *pathname, int proj_id);
  • 参数:

    • pathname: 当前操作系统中一个存在的路径

    • proj_id: 这个参数只用到了int中的一个字节, 传参的时候要将其作为 char 进行操作,取值范围: 1-255

  • 返回值:函数调用成功返回一个可用于创建、打开共享内存的key值,调用失败返回-1

使用举例:

// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);

2、关联和接触关联

2.1 shmat

创建/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址,通过得到的内存地址进行数据的读写操作,关联函数的原型如下:

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数:
    • shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
    • shmaddr: 共享内存的起始地址, 用户不知道, 需要让内核指定, 写NULL
    • shmflg: 和共享内存关联的对共享内存的操作权限
      • SHM_RDONLY: 读权限, 只能读共享内存中的数据
      • 0: 读写权限,可以读写共享内存数据
  • 返回值:关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1

2.2 shmdt

当进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。

int shmdt(const void *shmaddr);
  • 参数:shmat() 函数的返回值, 共享内存的起始地址

  • 返回值:关联解除成功返回0,失败返回-1

3、删除共享内存

3.1 shmctl

shmctl() 函数是一个多功能函数,可以设置、获取共享内存的状态也可以将共享内存标记为删除状态。当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部和共享内存解除关联,共享内存才会被删除。因为通过shmctl()函数只是能够标记删除共享内存,所以在程序中多次调用该操作是没有关系的。

// 共享内存控制函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);// 参数 struct shmid_ds 结构体原型          
struct shmid_ds {struct ipc_perm shm_perm;    /* Ownership and permissions */size_t          shm_segsz;   /* Size of segment (bytes) */time_t          shm_atime;   /* Last attach time */time_t          shm_dtime;   /* Last detach time */time_t          shm_ctime;   /* Last change time */pid_t           shm_cpid;    /* PID of creator */pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */// 引用计数, 多少个进程和共享内存进行了关联shmatt_t        shm_nattch;  /* 记录了有多少个进程和当前共享内存进行了管联 */...
};
  • 参数:
    • shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
    • cmd: 要做的操作
      • IPC_STAT: 得到当前共享内存的状态
      • IPC_SET: 设置共享内存的状态
      • IPC_RMID: 标记共享内存要被删除了
    • buf:
      • cmd==IPC_STAT, 作为传出参数, 会得到共享内存的相关属性信息
      • cmd==IPC_SET, 作为传入参, 将用户的自定义属性设置到共享内存中
      • cmd==IPC_RMID, buf就没意义了, 这时候buf指定为NULL即可
  • 返回值:函数调用成功返回值大于等于0,调用失败返回-1

3.2 相关shell命令

使用ipcs 添加参数-m可以查看系统中共享内存的详细信息

$ ipcs -m------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态      
0x00000000 425984     oracle     600        524288     2          目标       
0x00000000 327681     oracle     600        524288     2          目标       
0x00000000 458754     oracle     600        524288     2          目标 	

使用 ipcrm 命令可以标记删除某块共享内存

# key == shmget的第一个参数
$ ipcrm -M shmkey  # id == shmget的返回值
$ ipcrm -m shmid	

3.3 共享内存状态

// 参数 struct shmid_ds 结构体原型          
struct shmid_ds {struct ipc_perm shm_perm;    /* Ownership and permissions */size_t          shm_segsz;   /* Size of segment (bytes) */time_t          shm_atime;   /* Last attach time */time_t          shm_dtime;   /* Last detach time */time_t          shm_ctime;   /* Last change time */pid_t           shm_cpid;    /* PID of creator */pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */// 引用计数, 多少个进程和共享内存进行了关联shmatt_t        shm_nattch;  /* 记录了有多少个进程和当前共享内存进行了管联 */...
};

通过shmctl()我们可以得知,共享内存的信息是存储到一个叫做struct shmid_ds的结构体中,其中有一个非常重要的成员叫做shm_nattch,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为0之后共享内存才会被真正的被删除掉。

当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的key从一个正整数变为0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。下图演示了共享内存的状态变化:

在这里插入图片描述

4、进程间通信

使用共享内存实现进程间通信的操作流程如下:

1. 调用linux的系统API创建一块共享内存- 这块内存不属于任何进程, 默认进程不能对其进行操作2. 准备好进程A, 和进程B, 这两个进程需要和创建的共享内存进行关联- 关联操作: 调用linux的 api- 关联成功之后, 得到了这块共享内存的起始地址3. 在进程A或者进程B中对共享内存进行读写操作- 读内存: printf();- 写内存: memcpy();4. 通信完成, 可以让进程A和B和共享内存解除关联- 解除成功, 进程A和B不能再操作共享内存了- 共享内存不受进程生命周期的影响的5. 共享内存不在使用之后, 将其删除- 调用linux的api函数, 删除之后这块内存被内核回收了

写共享内存的进程代码:

#include <stdio.h>
#include <sys/shm.h>
#include <string.h>int main()
{// 1. 创建共享内存, 大小为4kint shmid = shmget(1000, 4096, IPC_CREAT|0664);if(shmid == -1){perror("shmget error");return -1;}// 2. 当前进程和共享内存关联void* ptr = shmat(shmid, NULL, 0);if(ptr == (void *) -1){perror("shmat error");return -1;}// 3. 写共享内存const char* p = "hello, world, 共享内存真香...";memcpy(ptr, p, strlen(p)+1);// 阻塞程序printf("按任意键继续, 删除共享内存\n");getchar();shmdt(ptr);// 删除共享内存shmctl(shmid, IPC_RMID, NULL);printf("共享内存已经被删除...\n");return 0;
}

读共享内存的进程代码:

#include <stdio.h>
#include <sys/shm.h>
#include <string.h>int main()
{// 1. 创建共享内存, 大小为4k,其实这里是关联!!!int shmid = shmget(1000, 0, 0);if(shmid == -1){perror("shmget error");return -1;}// 2. 当前进程和共享内存关联void* ptr = shmat(shmid, NULL, 0);if(ptr == (void *) -1){perror("shmat error");return -1;}// 3. 读共享内存printf("共享内存数据: %s\n", (char*)ptr);// 阻塞程序printf("按任意键继续, 删除共享内存\n");getchar();shmdt(ptr);// 删除共享内存shmctl(shmid, IPC_RMID, NULL);printf("共享内存已经被删除...\n");return 0;
}

两个进程之间就是依靠key,实现同一片共享内存的关联,这里key = 1000

5、shm和mmap的区别

共享内存内存映射区都可以实现进程间通信,下面来分析一下二者的区别:

  • 实现进程间通信的方式

    • shm: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
    • 内存映射区: 位于每个进程的虚拟地址空间中, 并且需要关联同一个磁盘文件才能实现进程间数据通信
  • 效率:

    • shm: 直接对内存操作,效率高
    • 内存映射区: 需要内存和文件之间的数据同步,效率低
  • 生命周期

    • 内存映射区:进程退出, 内存映射区也就没有了
    • shm:进程退出对共享内存没有影响,调用相关函数/命令/ 关机才能删除共享内存
  • 数据的完整性 -> 突发状态下数据能不能被保存下来(比如: 突然断电)

    • 内存映射区:可以完整的保存数据, 内存映射区数据会同步到磁盘文件
    • shm:数据存储在物理内存中, 断电之后系统关闭, 内存数据也就丢失了

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

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

相关文章

Android,JNI开发和NDK之间的联系

Android&#xff0c;JNI开发和NDK。 1.jni和ndk jni是在jdk中就有出现的 在我们jdk路径中 D:\java\jdk11\include 这就是jdk中的jni Android开发环境中的ndk也有jni&#xff0c; D:\Android\sdk\ndk\20.0.5594570\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\in…

实体、协议、服务和服务访问点

目录 一、概念 二、相邻两层之间的关系 三、面向连接服务的特点 四、无连接服务的特点 五、著名的协议举例 一、概念 实体&#xff08;entity&#xff09;表示任何可发送或接收信息的硬件或软件进程。同机器上同一层的实体叫做对等实体&#xff08;peer entity&#xff0…

学嵌入式,已经会用stm32做各种小东西了,下一步是什么

学嵌入式&#xff0c;已经会用stm32做各种小东西了&#xff0c;下一步是什么&#xff0c;研究stm32的内部吗&#xff1f; 针对题主这种类型的&#xff0c;首先我想提出几个技术问题。 1&#xff0c;除了那几个常用的外设&#xff0c;stm32上集成的众多外设是否都有实际的使用经…

基于OpenCV+YOLOv5实现车辆跟踪与计数(附源码)

导 读 本文主要介绍基于OpenCVYOLOv5实现车辆跟踪与计数的应用&#xff0c;并给出源码。 资源下载 基础代码和视频下载地址&#xff1a; https://github.com/freedomwebtech/win11vehiclecount main.py代码:​​​​​​​ import cv2import torchimport numpy as npfrom tr…

【Docker】python flask 项目如何打包成 Docker images镜像 上传至阿里云ACR私有(共有)镜像仓库 集成Drone CI

一、Python环境编译 1、处理好venv环境 要生成正常的 requirements.txt 文件&#xff0c;我们就需要先将虚拟环境处理好 创建虚拟环境&#xff08;可选&#xff09;&#xff1a; 在项目目录中&#xff0c;你可以选择使用虚拟环境&#xff0c;这样你的项目依赖将被隔离在一个…

【题目】栈和队列专题

文章目录 专题一&#xff1a;栈系列1. 中缀表达式转后缀表达式&#xff08;逆波兰式&#xff09;2. 有效的括号3. 用栈实现队列4. 最小栈 专题一&#xff1a;栈系列 1. 中缀表达式转后缀表达式&#xff08;逆波兰式&#xff09; 算法原理 2. 有效的括号 题目链接 算法原理 代…

【无标题】广东便携式逆变器的澳洲安全 AS/NZS 4763

便携式逆变器的澳洲安全 AS/NZS 4763 便携式逆变器申请澳大利亚和新西兰SAA认证的时候&#xff0c;需要按照澳洲*用标准AS/NZS 4763: 2011进行测试。立讯检测安规实验室有澳洲AS/NZS 4763: 2011资质授权&#xff0c;为国内多家便携式逆变器客户成功申请澳洲SAA证书 便携式户外…

Java生成word[doc格式转docx]

引入依赖 <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.32</version></dependency> doc…

Tap虚拟网卡 (草稿)

1 概述 Tap设备通常用于虚拟化场景下&#xff0c;参考如下场景&#xff1a; 图中标注了关键函数&#xff0c;以及数据流向。 tun有两个数据接口&#xff0c; file&#xff0c;给用户态使用&#xff1b;socket&#xff0c;给内核态使用&#xff0c;例如vhost 2 异步处理 图…

redis Redis::geoAdd 无效,phpstudy 如何升级redis版本

redis 查看当前版本命令 INFO SERVERwindows 版redis 进入下载 geoadd 功能在3.2之后才有的&#xff0c;但是phpstudy提供的最新的版本也是在3.0&#xff0c;所以需要升级下 所以想出一个 挂狗头&#xff0c;卖羊肉的方法&#xff0c;下载windows 的程序&#xff0c;直接替…

2023.11.27【读书笔记】|医疗科技创新流程(前言)

目录 注重价值关键要素如何解决价值问题&#xff1f;注重三个关键点价值探索价值预测价值定位 中国视角背景挑战战术 洞察过程发现需求发现需求筛选 发明概念产生概念选择 发挥战略发展商业计划 注重价值 在美国&#xff0c;医疗费用的增长率已经多年超过GDP增长率&#xff1b…

玩转大数据5:构建可扩展的大数据架构

1. 引言 随着数字化时代的到来&#xff0c;大数据已经成为企业、组织和个人关注的焦点。大数据架构作为大数据应用的核心组成部分&#xff0c;对于企业的数字化转型和信息化建设至关重要。我们将探讨大数据架构的基本要素和原则&#xff0c;以及Java在大数据架构中的角色&…

AndroidStudio - 新版本 Logcat 使用详解

最近这俩天正好有时间给自己做一下减法&#xff0c;忘记是去年还是今年&#xff0c;在升级 AndroidStudio 后使用 Logcat查看日志的方式也发生了一些变化&#xff0c;虽然一直在使用&#xff0c;但每当看到之前还未关闭 Logcat 命令行工具额昂也&#xff0c;就感觉可能还存在知…

电压驻波比

电压驻波比 关于IF端口的电压驻波比 一个信号变频后&#xff0c;从中频端口输出&#xff0c;它的输出跟输入是互异的。这个电压柱波比反映了它输出的能量有多少可以真正的输送到后端连接的器件或者设备。

uniapp实现文件预览过程

H5实现预览 <template><iframe :src"_url" style"width:100vw; height: 100vh;" frameborder"0"></iframe> </template> <script lang"ts"> export default {data() {return {_url: ,}},onLoad(option…

vue实现数字千分位格式化 如6,383,993,037,937.463

1.封装文件&#xff1a;numberToCurrency.js /**实现数字千分位格式化 如6,383,993,037,937.463 */ export function numberToCurrencyNo(value) {if (!value) return 0// 获取整数部分const intPart Math.trunc(value)// 整数部分处理&#xff0c;增加,const intPartFormat …

C语言数组(上)

# 数组的概念 数组是一组相同类型元素的集合。数组中存放的是一个或多个数据&#xff0c;但是数组中的元素个数不能为零&#xff1b;数组中存放的所有元素&#xff08;数据&#xff09;的类型必须是相同的。 数组分为一维数组和多维数组&#xff0c;多维数组一般比较多见的是二…

全球与中国仿制药市场:增长趋势、竞争格局与前景展望

仿制药是指在剂型、功效、给药方法、品质、性能特征、用途等方面与原厂药相似并已获得原厂药上市许可的药品。仿制药的价格低于品牌药。糖尿病、癌症和心血管疾病等慢性疾病的快速成长推动了仿制药市场的成长。此外&#xff0c;仿制药的实惠价格以及最新产品的批准和推出也有助…

Redis实战篇笔记(最终篇)

Redis实战篇笔记&#xff08;七&#xff09; 文章目录 Redis实战篇笔记&#xff08;七&#xff09;前言达人探店发布和查看探店笔记点赞点赞排行榜 好友关注关注和取关共同关注关注推送关注推荐的实现 总结 前言 本系列文章是Redis实战篇笔记的最后一篇&#xff0c;那么到这里…

ElasticSearch知识体系详解

1.介绍 ElasticSearch是基于Lucene的开源搜索及分析引擎&#xff0c;使用Java语言开发的搜索引擎库类&#xff0c;并作为Apache许可条款下的开放源码发布&#xff0c;是当前流行的企业级搜索引擎。 它可以被下面这样准确的形容&#xff1a; 一个分布式的实时文档存储&#xf…