【详解 进程通信】之 System V 共享内存

news2025/2/26 22:58:25

  • 简介
  • 大致操作步骤介绍
  • 结果展示
  • key值的获取
  • 创建 | 获取共享内存
  • 使用共享内存段
  • 解除共享内存段
  • 销毁共享内存段(重点)
    • 命令行方式销毁共享内存段
    • 系统调用销毁共享内存
  • 使用共享内存进行进程间通信

简介

共享内存可以让多个进程共享同一块内存,也就是说这块内存能映射到多个进程的页表中供不同的进程读写。由于用户对内存的操作是非常快的,因此共享内存是其中一种速度最快的IPC方法,一个进程一旦更新了共享内存,那么这个变更会立即对共享同一个内存段的其他进程可见

大致操作步骤介绍

第一步 :
调用 ftok ( )函数获取一个接近唯一的 key 值供shmget函数使用

第二步:
调用 shmget ( ) 函数创建或获取一个已有的共享内存,并返回该内存标识符

第三步:
调用 shmat ( ) 函数将指定标识符的共享内存映射到该进程的页表中,并返回指针供进程使用 ,换句话说就是将指定的共享内存与进程关联起来

第四步:
调用 shmdt ( ) 函数 解除对应共享内存与该进程间的关联

第五步:
调用shmctl ( ) 函数 将指定表示符的共享内存删除

结果展示

启动两个程序(也就是两个进程),由其中一个进程读取共享内存中的内容,另一个进程写入。实现进程间数据传输
请添加图片描述

key值的获取

为啥key还需要获取?

1:由于获取共享内存时,shmget () 会根据 key 值转换成对应的 IPC 标识符(本文就暂时把他称为共享内存表示符)。

2:也就意味着shmget () 函数只要我们传相同的key值就能获得相同的共享内存标识符,所以key值和共享内存标识符是一一对应的

3:共享内存标识符又和共享内存块一一对应,所以当不同进程使用相同的标识符将对应共享内存块映射到进程时,就会让不同进程访问同一个空间,达到数据交互的目的-

4 : 但是当想创建一个新的共享内存块供进程使用时,就需要一个接近唯一的key值,这时就可以使用 ftok () 函数帮助我们获取随机数,让我们无需指定key值并无需思考key值是否重复

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

参数解释
pathname : 可以是文件或目录的路径 ,但该路径是必须存在的

proj_id : 虽然是一个 int 值但 ftok () 函数只会取其第八位的值 , 所以 proj_id 的有效参数为 1 ~ 255 ,注意 proj_id 不要设为0 ,在1 ~ 255中选择即

返回值:成功返回一个key_t类型的值,失败返回-1

实际使用演示

ftok(., 30);

如上:pathname是 “.” ,也就是当前路径 ,proj_id 是 30

原理:
在Linux上,ftok() 返回的key是一个32位的值,它通过取proj参数的最低8个有效位,以及 pathname所引用的文件的i-node号的最低16个有效位组合经一定算法后形成。

创建 | 获取共享内存

shmget()系统调用会创建一个新的共享内存段或获取一个已经存在的共享内存段标识符

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

注意:
shmget()新创建的内存段中的内容会被初始化为0 , 只要 shmget ()中的key 参数相同就会获取到同 一个共享内存段标识符

作用 :
创建
一个新的共享内存段或获取一个已经存在的共享内存段标识符

返回值
成功返回获取到的内存段的描述符,失败返回-1

参数解释:
key : 想创建新的共享内存段应使用 ftok() 获取 key 值,想获取已有的共享内存段就使用开辟内存时所传递的key值

size: 是一个正整数,表示所申请共享内存段的字节数。如果shmget () 是用来获取一个已有的的共享内存段标识符,则size参数不起作用,但必须小于或等于已有共享内存段的实际大小。

shmfly:可以是 IPC_CREAT 或者是 IPC_CREAT | IPC_EXCL

IPC_CREAT : 如果该key值的共享内存段已存在,则直接获取其内存段标识符并返回,但如果不存在与该key值相同的内存段则创建一个新的共享内存段,并返回新内存段的标识符。

IPC_CREAT | IPC_EXCL :注意 IPC_EXCL 不能单独使用,需或上 IPC_CREAT 使用。表示但如果不存在与该key值相同的内存段则创建一个新的共享内存段,并返回新内存段的标识符。如果该key值的共享内存段已存在,则直接返回错误

注意:-
IPC_CREAT参数不挑内存,有了直接拿标识符,没有才创建
IPC_CREAT | IPC_EXCL参数很挑,一定要自己新创建的共享内存段的标识符,也就意味着这个参数能保证程序获取的内存段是最新的,不可能有其他人使用

使用共享内存段

上述 shmget() 系统调用只是获取到了共享共存段的标识符,但要使标识符对应的内存段还需将内存段映射进进程的虚拟地址空间中

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

作用:将标识符对应的内存段映射进进程的虚拟地址空间中(也可以理解为关联共享内存段)

返回值:成功返回共享内存段的地址,失败返回-1

参数解释:

shmid : 要关联的目标共享内存段的描述符

shmaddr : 建议设置为NULL,这样共享内存会自动被映射到一个合适的虚拟地址空间。(不建议传递非NULL,传递的是其他指针时,系统会根据这个指针及其地址边界,内存对齐等分配地址。)

shmflg : 如果参数为 0 ,则进程对共享内存段有读写权限

如果指定为:SHM_RDONLY 则只要只读权限

解除共享内存段

调用 shmdt()来分离共享内存段,执行了该步骤后进程将无法再引用这块共享内存。

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数解释:

shmaddr :为使用 shmat()获取到的指针

作用:取消 shmaddr 指向的共享内存与进程的映射(取消关联)

销毁共享内存段(重点)

这步是必须要做的,申请了就必须释放,多个进程一起使用的共享内存也只需销毁一次即可。共享内存的生命不会随进程结束而结束,只要没接收到销毁命令就会一直存在,且不报错

销毁共享内存段的方式有两种:
1 : 命令行使用指令
2 :程序中使用系统调用

命令行方式销毁共享内存段

首先写一个不规范的代码给大家见见共享内存段

#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>

//                  假设这是客户端写对应指令
int main()
{
    key_t key = ftok( "." , 33);//获取key值
    umask(0);												//0666是创建文件的权限
    int id = shmget(key , 1314 , IPC_CREAT | IPC_EXCL | 0666);//获取一个新的共享内存段,
    
    char* str = (char*)shmat(id , NULL , 0);//将id(描述符)对应的共享内存段映射到进程的虚拟地址空间
    
    shmdt(str);//取消 str 指向的共享内存与进程的映射(取消关联)
    
    //并未销毁共享内存段,直接返回了
    return 0 ; 
}

可以看到上述代码,申请共享内存段后,关联了进程,后面又取消关联了。但是没有销毁共享内存段

可想而知程序运行结束后,这个共享内存段就被遗留了下来

但是别慌,可以通过命令来查找他并且也可以用命令销毁他

命令:ipcs -m
作用:显示当前系统中运行的共享内存
使用如下:
在这里插入图片描述
可以看这个就是我们刚刚创建的共享内存,有兴趣的小伙伴可以再程序中将共享内存标识符打印出来确定一下,我这里片面认为就是这个描述符为17的共享内存块了,块大小1314也符合咱的开辟大小

命令:ipcrm -m shmid
作用: 销毁 shmid(内存描述符)对应的内存段

实际使用:
在这里插入图片描述
可以看到执行后,指定内存就被销毁了。但这样未免太麻烦了,所以可以在程序中使用系统调用来销毁共享内存段,命令方式只是用于不时之需

系统调用销毁共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

作用:这个函数除了销毁共享内存,还有其他用处。但现在只了解删除功能即可
返回值:成功返回0 , 失败返回 -1

参数解释:
shmid : 共享内存段描述符
cmd : 传 IPC_RMID 即为删除功能
buf : 这里咱不用传NULL即可

测试代码:

#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>

//                  假设这是客户端写对应指令
int main()
{
    key_t key = ftok( "." , 33);//获取key值
    umask(0);
    int id = shmget(key , 1314 , IPC_CREAT | IPC_EXCL|0666);//获取一个新的共享内存段
    printf("创建了一个描述符为 %d 的共享内存段\n" , id);
    char* str = (char*)shmat(id , NULL , 0);//将id(描述符)对应的共享内存段映射到进程的虚拟地址空间
    
    shmdt(str);//取消 str 指向的共享内存与进程的映射(取消关联)

    shmctl(id , IPC_RMID , NULL);
    return 0 ; 
}

结果如下,内存创建后也被销毁了,无需在命令行手动销毁
在这里插入图片描述

使用共享内存进行进程间通信

共享内存通信原理

ftok传参一致的程序(进程)就会回到相同key值,shmget使用相同key值就会获得同一个内存段描述符,不同进程使用相同描述符就能映射到同一个共享内存段,就达到了通信的前提 ———— 让不同进程看到同一份资源

下面是俩进程间实现通信的代码

Client.cpp

#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
//                  假设这是客户端写对应指令
int main()
{
    key_t key = ftok( "." , 33);//获取key值
    umask(0);
    int id = shmget(key , 1314 , IPC_CREAT | IPC_EXCL|0666);//获取一个新的共享内存段

    char* str = (char*)shmat(id , NULL , 0);//将id(描述符)对应的共享内存段映射到进程的虚拟地址空间
   
    sleep(5);//延时一下方便其他进程读取
    for(int i = 0 ; i < 26 ; i++)//向共享内存循环写入数据
    {
        str[i] = 'A' + i;//写入数据
        str[i+1] = '\0';//可不写因为刚开辟默认为0

        sleep(1);//防止写入太快,需测试看效果
    }

    shmdt(str);//取消 str 指向的共享内存与进程的映射(取消关联)

    shmctl(id , IPC_RMID , NULL);
    return 0 ; 
}

Server.cpp

 #include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
//                  假设这是服务端读对应指令
int main()
{
    key_t key = ftok( "." , 33);//获取key值
    umask(0);
    int id = shmget(key , 1314 , IPC_CREAT );//获取一个共享内存段

    char* str = (char*)shmat(id , NULL , 0);//将id(描述符)对应的共享内存段映射到进程的虚拟地址空间
   
    for(int i = 0 ; i < 35 ; i++)//读取内存中数据
    {
        std::cout << str << std::endl;
        sleep(1);//防止读入太快,需测试看效果
    }

    shmdt(str);//取消 str 指向的共享内存与进程的映射(取消关联)

    shmctl(id , IPC_RMID , NULL);
    return 0 ; 
}

运行如下:
请添加图片描述
代码写的比较简单,有兴趣的小伙伴可以这些调用封装成类,然后直接调用就能返回关联好的指针,直接使用即可,销毁调用放在析构函数中即可实现自动销毁,进程结束共享内存段也会自动销毁。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何实现Chatgpt写文章(附chatgpt3.5免费接口)

申明&#xff1a;本次只是说一下实现思路&#xff0c;官方的接口以及如何实现方式&#xff0c;本文没有提及&#xff0c;这次只是一个思路&#xff0c;若想代替人工完成质量还差的很远&#xff0c;请审核大大放行 今天再次优化了代码&#xff0c;修复了一些bug&#xff0c;考虑…

单片机程序是如何运行起来

多年前在学习计算机原理的时候曾经问过老师一个问题&#xff0c;就是我们编写的程序是怎么在计算机中运行起来的&#xff0c;希望有个完整的思路&#xff0c;现在通过网络收集和整理了这个问题&#xff0c;相当于对这个问题又做了一个认识&#xff0c;有了新的体会。以stm32单片…

Go 语言切片是如何扩容的?

原文链接&#xff1a; Go 语言切片是如何扩容的&#xff1f; 在 Go 语言中&#xff0c;有一个很常用的数据结构&#xff0c;那就是切片&#xff08;Slice&#xff09;。 切片是一个拥有相同类型元素的可变长度的序列&#xff0c;它是基于数组类型做的一层封装。它非常灵活&am…

VCS4 debug with DVE

1、重点讲解&#xff1a; 在verilog源代码中嵌入VCD 系统函数&#xff0c;重点如testbench文件中。VCD文件是VCS产生的仿真波形文件&#xff0c;未经压缩&#xff0c;占用空间较大。VCD是压缩后的波形文件。 编译、仿真以生成VCD文件。 在后处理模式中使用激活DVElog对产生的…

【Javaee】SpringMVC_day01

文章目录1&#xff0c;SpringMVC简介1.1 SpringMVC概述2&#xff0c;SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目&#xff0c;并导入对应的jar包步骤2:创建控制器类步骤3:创建配置类步骤4:创建Tomcat的Servlet容器配置类步骤5:配置Tomcat环境步骤6:启动运行…

JS字符串对象

、 JS字符串对象 1.1 内置对象简介 在 JavaScript 中&#xff0c;对象是非常重要的知识点。对象可以分为两种:一种是“自定义对象”外一种是“内置对象”。自定义对象&#xff0c;指的是需要我们自己定义的对象&#xff0c;和“自定义函数”是一些道理;内置对象&#xff0c;…

力扣刷题笔记26——最小的k个数/快速排序学习/快排与冒泡的时间复杂度

最小的k个数/快速排序学习/快排与冒泡的时间复杂度问题我的代码示例代码快速排序代码问题 来自力扣&#xff1a; 输入整数数组 arr &#xff0c;找出其中最小的 k 个数。例如&#xff0c;输入4、5、1、6、2、7、3、8这8个数字&#xff0c;则最小的4个数字是1、2、3、4。示例 …

1672_MIT 6.828 xv6中如何通过构建环境让系统中增加一个可执行调用文件

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 前面已经分析了如何实现一个系统调用&#xff0c;这个过程的梳理也已经整理成了一份学习笔记。这一次看一下&#xff0c;如何让OS的系统中增加这样的一个可执行的文…

10.网络爬虫—MongoDB详讲与实战

网络爬虫—MongoDB详讲与实战MongoDBMongoDB安装创建数据目录1.数据库操作2.集合操作3.文档操作4.索引操作5.聚合操作6.备份与恢复MongoDB增删改查mongodb集合的增删改查数据插入到表数据的查看删除数据更新数据PyMongo连接数据库第二步 选择需要使用的数据库和集合PyMongo增删…

公司分布式锁加锁错误原因

目录一、问题二、问题复现三、为什么产生这个错误四、解决方案一、问题 第一次设置锁成功, 但是返回false, 后续在循环获取的时候, 因为已经设置成功, 调用setIfAbsent不会返回true, 导致等锁3s失败 private boolean lockWait(String key, long wait, long expire) { long tot…

【CV】Latent diffusion model 扩散模型体验

note 文章目录note一、diffusion模型1.1 Stable Diffusion简介1.2 和GAN对比的优势二、Latent diffusion model原理2.1 潜在空间(Lantent Space)2.2 自动编码器和U-Net2.3 文本编码器三、代码实践3.1 模型权重checkpoints3.2 Stable Diffusion v1模型推理3.3 安装Stable Diffus…

再见了,我的C!

本人的第一篇博客发布于1月份&#xff0c;现在已经4月份了&#xff0c;历时3个月&#xff0c;&#xff0c;已经将C语言涵盖的大多数C语言知识点系统性的整理了出来&#xff0c;在这个期间自己收获了很多&#xff0c;这是C语言的最后一篇文章&#xff0c;接下来我们来回顾一下我…

13.Java面向对象----嵌套类

Java面向对象—嵌套类、内部类、匿名类 一、static静态 在《Java编程思想》有这样一段话&#xff1a;   “static方法就是没有this的方法。在static方法内部不能调用非静态方法&#xff0c;反过来是可以的。而且可以在没有创建任何对象的前提下&#xff0c;仅仅通过类本身来…

计及需求侧响应日前、日内两阶段鲁棒备用优化【IEEE6节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

3.3 二维随机变量条件分布

学习目标&#xff1a; 要学习二维随机变量的条件分布&#xff0c;我可能会采取以下步骤&#xff1a; 复习边缘分布和联合分布&#xff1a;首先需要了解二维随机变量的边缘分布和联合分布的概念以及相应的公式。 复习条件概率&#xff1a;学习条件概率的定义和计算公式&#x…

Java使用elasticjob实现定时任务(v2.1.5)

elastic是一个定时任务库 https://shardingsphere.apache.org/elasticjob/index_zh.html 项目结构 ​依赖 <dependency><groupId>com.dangdang</groupId><artifactId>elastic-job-lite-core</artifactId><version>2.1.5</version>&…

【linux】——进程和计划任务管理

文章目录1.进程 VS 线程1.1 程序和进程的关系1.2 线程1.3 进程和线程的关系2.查看进程2.1 查看进程信息ps2.2 查看进程信息top2.3 查看进程信息pgrep2.4 查看进程树pstree3.控制进程3.1 进程的启动方式3.2 进程的前后台调度3.3 终止进程的运行kill3.4 终止进程的运行pkill4.计划…

【华为OD机试】1039 - 迷宫问题

文章目录一、题目&#x1f538;题目描述&#x1f538;输入输出&#x1f538;样例1&#x1f538;样例2二、代码参考作者&#xff1a;KJ.JK&#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &am…

CASA(Carnegie-Ames-Stanford Approach)模型应用

植被作为陆地生态系统的重要组成部分对于生态环境功能的维持具有关键作用。植被净初级生产力&#xff08;Net Primary Productivity, NPP&#xff09;是指单位面积上绿色植被在单位时间内由光合作用生产的有机质总量扣除自养呼吸的剩余部分。植被NPP是表征陆地生态系统功能及可…

全网最详细,Jmeter性能测试-性能基础详解,控制器不同选择(四)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 逻辑控制器 提前说…