Linux:进程间通信之共享内存

news2024/12/25 13:59:10

我们无论使用命名管道还是匿名管道,都是在文件层面上实现的通信,实际上还有基于系统层面的system v标准的进程间通信方式。

因为操作系统不相信用户,所以用户使用的时候只能通过调用的方式

进程间通信的本质:先让不同的进程看到同一份资源。

system v提供的主流方式有三个:

1.共享内存 2.消息队列(有些落伍)3.信号量

前两个以传送数据为目的,第三个以实现进程间同步后者互斥为目的。

共享内存

共享内存其实和我们之前在文件层面上进行通信的原理差不多,只不过是在操作系统层面上操作

通过系统调用,创造出一片新的内存空间,然后让两个进程都挂接到这个新的内存空间上,也就是把我们新开辟的内存空间,通过页表的映射在两个进程上,两个进程就可以拿到同一份地址空间

此时我们就让不同的进程看到了同一份资源,这种通信方案称之为共享内存

怎么创建一个共享内存:

使用shmget创建

key是自己自定义的

size通常是4kb的整数倍

shmflg有两种类型:

  • 如果单独使用IPC_CREATE或者shmflg为0:如果不存在共享内存,则创建一个共享内存,如果创建的共享内存已经存在,直接返回当前已经存在的共享内存。(基本不会空手而归)

  • IPC_CREATE | IPC_EXCL:如果不存在共享内存,则创建;如果已经有了共享内存,则返回出错。意义在于如果我调用成功,得到的一定是一个最新的,没有被别人使用过的共享内存!

  • 但是IPC_EXCL单独使用是没有意义的

使用ftok来共享

两个参数:自己设定的内存的路径,自己设定的id

增加两个概念:

        key:是共享内存对于系统外部的标识符,通常是整数或字符串,也就是我们使用shemget创建共享内存时候的那个key

        什么是ID:内核分配给文件的特有标识符,通常是整数 

        什么叫key的算法:生成id的算法

所以只要我们在生成共享内存的时候,只要我们内部的数据是一样的,并且生成id的key算法是一样的,那么这两个进程就可以共享同一块共享内存

key_t是一个特殊的类型,用于表示系统中的共享内存、消息队列和信号量等 IPC(进程间通信)机制中的键值,就像ssize_t一样

来写一个:

comm.h:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6666
#define SIZE 4097

shared.c:

#include"comm.h"
int main(){
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("key");
        return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    printf("key:%u,shmid:%d\n",key,shmid);

    return 0;
}

结果:

因为共享内存文件已经创建成功了,所以第二次会返回File exists

ipcs -m是一个命令,用于显示系统中所有的共享内存段的信息

不加-m选项就是显示所有共享内存,消息队列,信号量

我们会发现:我们的进程结束后共享内存文件并没有随着进程的销毁而销毁,还能查到;如果是文件的话,当和这个文件的所有进程都被关闭后,那么文件也被关闭了。system V 的IPC资源,生命周期是随内核的!这个IPC只能通过程序员显示的释放(利用系统调用命令)或者是OS重启 !(内核不死或者程序员不让它死,他就死不了)

你看我退出重连就没有了:

于是就产生了一个问题:我们拿C语言创建一个新指针或者申请一块新内存的时候,就需要free()释放,那么用shmget创建共享内存的时候,进程退出就不会造成内存泄漏吗?你也没管你的共享内存啊!

指针为什么需要free()?因为指针的内存是用malloc、colloc、realloc等动态内存函数分配的,函数分配的内存得由程序员自己释放;而共享内存是由操作系统管理的。操作系统会在进程退出时清理相关资源。但是,如果你的进程是异常退出(例如通过调用exit() 函数),那么仍然需要手动来确保资源的释放和清理。

删除共享内存的命令:

ipcrm -m shmid

我这里的shmid是1,key是0x66014933

shmid和key有什么关系?

我们来试试用key删除共享内存:

删不掉,为什么?

key是用户创建,内核使用的标识符,是OS层面进行标识唯一性的,不能用来管理共享内存的。

shmid是生成共享内存返回给用户使用的id,用来在用户层进行共享内存的管理。比如:删除它,关联/去关联。

因为命令行就是用户级的,所以使用的是shmid

使用shmctl来操作共享内存

三个参数:shmid就是shmid,cmd是一个选项,将要采取的动作(有三个可取值),shmid_ds:就是描述共享内存的数据结构,只不过它是用户层的数据结构,是内核上描述共享内存的子集。

shmid_ds的本质上就是一个结构体,用户使用共享内存的时候,通常就通过shmctl来和这个结构体交互

struct shmid_ds的内容:

返回值:成功返回0;失败返回-1 

我们现在如果要删除这个共享内存,cmd置为IPC_RMID,shmid_ds置为NULL

我们在程序里试一下:

shared.c

#include"comm.h"
int main(){
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("key");
        return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL);
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    printf("key:%u,shmid:%d\n",key,shmid);
    sleep(5);
    shmctl(shmid,IPC_RMID,NULL);
    printf("key:0x%X,shmid->%d->shm delete succes!\n",key,shmid);
    sleep(5);//此时已经删掉了

    return 0;
}

你会发现欸?我们创建着创建着,这个shmid居然还是递增的欸!这让我们想到什么?数组啊!

内核再组织IPC资源的时候是通过数组组织的。

目前我们创建的共享内存的perms是0,代表权限为0,意思就是谁都不能读谁都不能写 ;如果我们想创建一个有权限被读取的共享文件,就可以在创建的时候加一个0666,这样perms就变成了666。说明我们可以给共享内存设置权限,并且共享内存的权限管理依赖于文件系统,也就是一切皆文件。

#include"comm.h"
int main(){
    umask(0);
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("key");
       return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//增加权限
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    printf("key:%u,shmid:%d\n",key,shmid);
    sleep(5);
    //shmctl(shmid,IPC_RMID,NULL);//不删除,删除的话查询权限可能为0
    printf("key:0x%X,shmid->%d->shm delete succes!\n",key,shmid);
    sleep(5);//此时已经删掉了

    return 0;
}

shmat让进程和共享内存产生关系

功能:讲内核级的共享内存链接到地址空间

参数shmid: 共享内存标识,shmaddr:指定连接的地址,shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的起始地址(这个地址是虚拟地址。ps:只要用户用的地址都是虚拟地址);失败返回-1。这个返回值如同函数malloc的返回值。

如果shmaddr为NULL的时候,os会自动选择一个地址

shmaddr不为NULL时且shmflg没有SHM_RND标记,则以shmaddr为链接地址

shmaddr不为NULL且shmflg设置了SHM_RND标记,则则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)

shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

shmdt去除关联关系

功能:将共享内存段和当前进程脱离

参数:shmaddr:shmat返回的指针

返回值:成功返回0,失败返回-1

使共享内存和进程脱离关系不等于删除共享内存段,起不到释放共享内存的作用

#include"comm.h"
int main(){
    umask(0);
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("key");
       return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    printf("key:%u,shmid:%d\n",key,shmid);
    sleep(5);
    char *mat=shmat(shmid,NULL,0);
    if(mat==(char*)-1){
        perror("shmat");
    }
    printf("attaches shm success\n");
    sleep(5);
    shmdt(mat);
    printf("detaches shm success\n");
    shmctl(shmid,IPC_RMID,NULL);
    printf("key:0x%X,shmid->%d->shm delete succes!\n",key,shmid);
    sleep(5);//此时已经删掉了

    return 0;
}

注意对mat的判断:因为shmat的返回值可能是指针,也可能是-1,所以不能随便解引用,应该转换类型

这样就实现了创建-挂接-去挂-删除了

来写一个利用共享内存通信的实例

写一个客户端和服务端的通信:

comm.h:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>        
#include <sys/stat.h>
#include <fcntl.h>
#define MY_FIFO "./fifo"
#include<sys/shm.h>
#define PATH_NAME "./"
#define PROJ_ID 0X6666
#define SIZE 4097

server.c

#include"comm.h"
int main(){
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("ftok");
        return 1;
    }
    int shmid=shmget(key,SIZE,IPC_CREAT|0666|IPC_EXCL);
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    printf("key:%u,shmid:%d",key,shmid);
    printf("attaches shm success \n");  
    char *shm=(char *)shmat(shmid,NULL,0);
    if(shm==(char *)-1){
        perror("shmat");
        return 1;
    }
    //开始通信
    while(1){
        sleep(1);
        printf("%s\n",shm);
    }
    shmctl(shmid,IPC_RMID,NULL);
    printf("delete");

}

client.c:

#include"comm.h"
int main(){
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0){
        perror("ftok");
        return 1;
    }
    printf("%u\n",key);
    int shmid=shmget(key,SIZE,IPC_CREAT);
    //和server的形成规则相同,代表是同一块共享内存
    if(shmid<0){
        perror("shmget");
        return 1;
    }
    char *shm =shmat(shmid, NULL, 0);   
    if (shm == (char *) -1) {
        perror("shmat");
        return 1;
    }


    printf("client process attaches success!\n");   
    char c='A';
    while(c<='Z'){
        shm[c-'A']=c;
        c++;
        shm[c-'A']=0;//向shm[0]写入A,再把shm[1]写为0,再写为B
        sleep(2);
    }    
    shmdt(shm);
    printf("client process detaches success\n");
    //client去挂接,server负责删除


}

先运行server.c,在运行client.c,此时我们看到client所写的消息就都被server读到了

一开始一直出现:

一开始一直出现这样的情况,因为我做了错饭:IPC_EXCL这个标志与IPC_CREAT 一起使用,表示如果共享内存段已存在,则shmget失败,并返回 -1,同时设置 errno 为 EEXIST,我的客户端不允许在已经存在的共享内存段上执行操作。

服务端已经创建了,客户端再这么写就会出现上述情况

共享内存的使用有没有类似管道的read这样的接口呢?

没有,共享内存一旦建立并映射到进程的地址空间后,就可以直接实现共享了;像malloc的空间一样,不需要系统调用接口

使用系统接口的本质是因为管道是把数据从进程拷贝到内核文件里,然后再由内核文件拷贝到另外一个进程的空间里;read或write本质是将数据从内核拷贝到用户,或者从用户拷贝到内核

我们可以看见server.exe启动时,client.exe还没启动,server.exe一直在刷新:

说明在client还没写入时,server已经在读取了!

因为共享内存是所有进程间通信最快的,省略了若干次数据拷贝的问题,管道就比他慢

但是共享内存不提供任何同步或互斥机制,就需要我们自己保证他的安全:

例如我向管道里写入写满的时候,我就写不进去了;没有数据的时候,就读不了了;

但是共享内存是同时进行的,你向共享内存写入了一个“我讨厌 上学”,而读取端读到了“我讨厌”就获取数据了,谁知道你是讨厌什么呢?造成数据不一致,所以共享内存在多进程通信的时候是不太安全的。

共享内存的三个特点:

生命周期随内核。

共享内存不提供任何同步或者互斥机制。

共享内存是所有进程间通信中速度最快的。

共享内存的size

size大小建议为4kb的整数被,也就是4096的整数倍

共享内存在内核中申请的基本单位是页,这个页叫做内存页,这个内存页是4KB。

如果我申请了4097就会向上取整,获得两页的大小

但是我们看见的又是4097

如果我向操作系统要了4096个字节,它给了我250个字节,那么就是操作系统的问题;有时候我要的少,操作系统给多了也会有问题:比如你设置了不能超过10bits,但是操作系统给了你20bits就又出错了;所以操作系统会按两页的大小给我申请,但是我用4097,就是4097

哈哈,励志轩的车又被推上去了

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

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

相关文章

C++中list类的使用及模拟实现

目录 1.C中list的底层结构 2.C中list容器各个接口函数的使用 3.迭代器的分类 3.1从功能上进行分类 3.2从性质上进行分类 4.list的结构 5.list的模拟实现 5.1默认成员函数(Member functions) 5.1.1构造函数(constructor) 5.1.1.1默认构造函数 5.1.1.2 initializer …

OSError: [WinError 126] 找不到指定的模块。 Error loading \torch\lib\fbgemm.dll“

遇到问题&#xff1a; 在使用torch 、 或者任何设计到torch的库中&#xff0c;只要导入torch就会报错 解决方案 https://blog.csdn.net/Changxing_J/article/details/140489278 https://blog.csdn.net/weixin_43591849/article/details/140715890&#xff08;最终这个解决&…

初识Linux · 进程终止

目录 前言&#xff1a; 进程终止在干什么 进程终止的3种情况 进程如何终止 前言&#xff1a; 由上文的地址空间的学习&#xff0c;我们已经知道了进程不是单纯的等于PCB 自己的代码和数据&#xff0c;进程实际上是等于PCB mm_struct(地址空间) 页表 自己的代码和数据。…

LLM 构建Data Multi-Agents 赋能数据分析平台的实践之⑥:NL2SQL技术探讨

一、概述 NL2SQL&#xff08;Natural Language to SQL&#xff09;是一种将自然语言转换为结构化查询语言的技术。它可以帮助用户通过使用自然语言来与数据库进行交互&#xff0c;而无需了解复杂的SQL语法。 NL2SQL技术的背景&#xff1a; 随着人工智能的发展&#xff0c;越…

prometheus + alertmanager + PrometheusAlert实现告警

prometheus 收集监控数据 alertmanager 制定告警路由 PrometheusAlert 连通告警webhook 一、prometheus配置 https://prometheus.io/download/ 1.1、prometheus安装 包的下载直接wget就行,放在data目录下,解压后在prometheus目录下创建config和rule目录 配置了热重启&#…

聊一聊 C#中有趣的 SourceGenerator生成器

一&#xff1a;背景 1. 讲故事 前些天在看 AOT的时候关注了下 源生成器&#xff0c;挺有意思的一个东西&#xff0c;今天写一篇文章简单的分享下。 二&#xff1a;源生成器探究之旅 1. 源生成器是什么 简单来说&#xff0c;源生成器是Roslyn编译器给程序员开的一道口子&am…

vxe-grid给单元格加上触发事件

效果&#xff1a;输入框的双击事件(其他事件可以由此替换) 代码 // gridTableOptions是每列的配置项 <vxe-grid v-bind"gridTableOptions" :data"goodsList" ref"xTable">// edit_spbh 是对应的样式名&#xff0c;是写在gridTableOption…

如何通过日志快速定位TTS的缓存放音文件(mod_cti基于FreeSWITCH)

文章目录 前言联系我们分析过程1. 测试话术&#xff0c;记录日志2. 关键词搜索 前言 顶顶通呼叫中心中间件在运行话术时&#xff0c;如果有通过TTS合成的语音&#xff0c;会被freeswitch缓存在目录中&#xff1a;/ddt/fs/storage/http_file_cache。 我们可以分析freeswitch日志…

学习Webpack中图片-JS-Vue-plugin

目录 图片文件资源模块类型 JS文件babel命令行使用babel-loaderbabel-preset Vue文件vue-loadervue/compiler-sfc pluginCleanWebpackPluginHtmlWebpackPluginDefinePlugin 图片文件 需要先在项目中使用图片&#xff0c;比较常见的使用图片的方式是两种&#xff1a; img元素&…

LeetCode 918. 环形子数组的最大和

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n…

基于STM32的智能室内空气质量监控系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 传感器数据采集与处理空气质量分析与报警显示与数据记录功能应用场景结论 1. 引言 智能室内空气质量监控系统用于实时监测环境中的空气质量&#xff0c;通过检测空气中的CO2、PM2.5、温…

软件测试学习笔记丨Pytest 学习指南

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32336 基本介绍 pytest框架是一个成熟&#xff0c;全面的测试框架&#xff0c;具有非常丰富的第三方插件&#xff0c;并且可以自定义扩展 比如&#xff1a;pytest-selenium , pytest-html ,…

Git常用方法——详解

一、下载安装git git官网&#xff1a; Git - Downloads (git-scm.com) 下载安装Git&#xff08;超详细超简单&#xff09;_git下载-CSDN博客 二、克隆下载至本地 1、复制HTTPS链接 在gitee或者gitLab或者gitHub上复制HTTPS链接 2、打开Open Git Bash here 在本地想要新建文…

小程序原生-列表渲染

1. 列表渲染的基础用法 <!--渲染数组列表--> <view wx:for"{{numList}}" wx:key"*this" > 序号&#xff1a;{{index}} - 元素&#xff1a;{{item}}</view> <!--渲染对象属性--> <view wx:for"{{userInfo}}" wx:key&q…

怎么给视频加片头片尾和字幕

在这个视觉内容爆炸的时代&#xff0c;一段精心制作的视频不仅能吸引眼球&#xff0c;更能传达深刻的情感与信息。而一个引人入胜的片头、一个温馨感人的片尾&#xff0c;以及恰到好处的字幕&#xff0c;无疑是提升视频质感的关键。那么新人要怎么给视频加片头片尾和字幕效果呢…

2024年9月收评

金1是从2005年12月开始&#xff0c;到现在2024年5月&#xff0c;还差7个月整整20年。一共11轮。 这20年里&#xff0c;真正形成单边趋势&#xff0c;能较好获利或者说至少不亏损的一共有以下几次&#xff0c; 第1轮&#xff0c;第2轮&#xff0c;第7轮&#xff0c;第8轮&…

《程序猿之Redis缓存实战 · 集合类型》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

基于微信小程序的商品展示+ssm论文ppt源码调试讲解

2 系统开发环境 2.1微信开发者工具 微信开发者工具现在已经被小程序开发团队开发运行&#xff0c;目前微信开发者工具任然在不断的完善中&#xff0c;在开发小程序时经常要不断的更新。可以使用微信扫码登陆开发者工具&#xff0c;开发者工具将使用这个微信帐号的信息进行小程…

为VRoidStudio制作的vrm格式模型制作blendshape

零、效果展示 bs视频演示 一、准备相关插件 1、VRoidStudio&#xff08;免费&#xff09; 下载网址&#xff1a;https://vroid.com/en/studio 2、UniVRM&#xff08;免费&#xff09; 下载网址&#xff1a;https://github.com/vrm-c/UniVRM/releases 注意&#xff1a;unity…

Qt --- 常用控件的介绍---Widget属性介绍

一、控件概述 编程&#xff0c;讲究的是站在巨人的肩膀上&#xff0c;而不是从头发明轮子。一个图形化界面上的内容&#xff0c;不需要咱们全都从零区实现&#xff0c;Qt中已经提供了很多内置的控件了&#xff08;按钮&#xff0c;文本框&#xff0c;单选按钮&#xff0c;复选…