【Linux】进程间通信——system V共享内存

news2024/11/26 20:50:09

目录

 写在前面的话

System V共享内存原理

System V共享内存的建立

代码实现System V共享内存

创建共享内存shmget()

ftok()

删除共享内存shmctl()

挂接共享内存shmat()

取消挂接共享内存shmdt()

整体通信流程的实现


 写在前面的话

         上一章我们讲了进程间通信的第一种方式 --- 管道,这一章我们将继续讲解进程间的通信的第二种方式 --- system V共享内存

        在讲解之前,还是先建议去把进程间通信管道的方式和原理搞明白,这样理解起system V共享内存来会简单许多。

System V共享内存原理

        共享内存是一种进程间通信(IPC)的机制,允许多个进程共享同一块内存区域,以便它们可以直接读取和写入其中的数据,从而实现高效的数据共享和通信。

         在共享内存中,当进程task_struct创建或连接到共享内存段时,操作系统会为每个进程分配一个虚拟地址空间mm_srtuct,将这个虚拟地址通过页表 映射到相同的物理内存区域。这样,多个进程就可以通过自己的虚拟地址访问相同的物理内存,实现对同一块内存的共享访问。通过虚拟地址到物理地址的映射,多个进程可以看到同一个共享内存.

        那么如何释放共享内存呢?

也很简单,只需要将每个进程 和 共享内存建立的映射去掉,然后释放掉共享内存即可.

System V共享内存的建立

        假设有很多进程都在用共享内存,这样内存中也会出现大量的 共享内存块,所以OS要把这些共享内存块管理起来,方式:先描述再组织,这样要把共享内存属性抽象成数据结构,然后利用一些方式将这些数据结构组织起来.所以:

       1. 共享内存 = 共享内存块 + 共享内存块对应的内核数据结构

       2.共享内存块一定不属于任何一个进程,而是属于操作系统.

建立共享内存的大体流程如下:

  1. 创建共享内存段:使用 shmget() 系统调用来请求创建一个共享内存段。该调用需要指定共享内存的大小、权限和标志等参数,并返回一个唯一的共享内存标识符。

  2. 连接到共享内存段:使用 shmat() 系统调用将当前进程附加到共享内存段。这个调用将返回共享内存段的地址,并将该地址映射到当前进程的虚拟地址空间。

  3. 访问共享内存:连接到共享内存的进程可以通过在其地址上执行内存操作,直接读取和写入共享内存段中的数据。进程可以使用指针、数组或结构体等方式在共享内存段中存储和访问数据。

  4. 分离共享内存:当进程完成对共享内存的访问后,使用 shmdt() 系统调用将其与共享内存段分离。分离后,进程将无法再访问共享内存段,但共享内存段仍然存在。

  5. 删除共享内存段:当不再需要共享内存段时,可以使用 shmctl() 系统调用删除它。这个调用需要指定共享内存标识符和特定的控制操作,比如传递 IPC_RMID 参数表示删除共享内存段.

        具体的使用方法及原理,我们在下面马上讲解。

代码实现System V共享内存

        按我们上面所说的,第一步是利用shmget()建立共享内存段,我们来看一下它的用法.

创建共享内存shmget()

        这个函数作用是创建并获取一个共享内存。

        先说第二个参数,是size,代表的是要创建的共享内存有多大

        再来说第三个参数shmflg,代表我们要设置的选项,共有两个选项:

        1.IPC_CREATE:创建共享内存,如果底层已经存在,则获取并返回;如果不存在,则创建共享内存然后再返回,

        2.IPC_EXCL:单独使用它没有意义,一般和IPC_CREATE合起来使用,见下:

        3.IPC_CREATE | IPC_EXCL:如果底层不存在,则创建共享内存并返回;如果底层存在,则出错返回。言外之意,如果返回成功,那么一定是一个全新的内存块

        最后再来说第一个参数 key.

        我们利用共享内存通信时会有一个问题,要通信的对方进程,怎么保证对方看到的共享内存就是我创建的呢?毕竟有很多共享内存.

        这个我们就需要通过key,key数据是多少不重要,只要保证在系统里唯一即可! 两个通信端A 和 B,只要使用同一个key,便可以找到同一块共享内存。因为key是唯一的,即这个共享内存块也是唯一的!

        相当于是给每个共享内存块编了个号,这个号码是唯一的,所以只要拿到了编号,就能找到相同的共享内存块。

        那么这个唯一的key值如何得到呢?这里需要用到ftok()函数

ftok()

我们先来看一下函数的使用:

  1. pathname:一个字符串,用于标识一个文件的路径名。通常会选择一个已经存在的文件,因为 ftok() 函数将使用该文件的inode编号和 proj_id 参数通过算法来生成键值key。

  2. proj_id:一个整数,作为用于生成键的项目标识号。该参数通常取一个非负整数。


然后我们来看一下返回值:

         看到如果成功的话,生成的键值被返回,否则-1被返回。

这些说清楚了,我们来用一下吧:

首先四个文件,comm.hpp,里面包含了必要的头文件及宏定义,这个便不再展示了;

        Log.hpp是日志文件,上一章我们写了,这里不写也可以,直接cout输出也行。

        shmClient.cc中我们写入以下代码:

#include "comm.hpp"
#include "Log.hpp"
int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug) << " client key : " << k << endl;

    return 0;
}

        shmServer中拷贝一份代码,然后把输出语句中的client改成server,然后我们编译运行来及看一下效果:

         可以发现生成了一样的key值,这样便可以找到同一块共享内存块了.


这样shmget的第一个参数也讲完了,接下来说一下返回值.

    返回值是如果建立成功,则返回这段共享内存的标识符,否则返回-1并且错误码被设置。

删除共享内存shmctl()

        当我们创建好共享内存后,最后还需要删除它,因为共享内存的生命周期是随内核的!

        不关闭的话,只要操作系统一直在运行,那么它就一直存在,占用空间资源。所以必须需要删除.

        有两种方法删除:手动指令删除、代码删除

指令删除:

        我们首先创建了一个共享内存,然后该进程执行完毕,进程退出。                

         我们在终端输入

ipcs -m

        来查看当前共享内存的使用情况

         可以发现确实没有释放掉。然后我们可以利用

ipcrm -m shmid

        来删除对应的共享内存,此时我输入这条指令后便没有了这块内存了。

这里提到了perms,这个是权限的意思,我们可以在shmget的第三个选项 加上权限,如下:

    int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);

此时perms就变成了666. 

代码删除

        每次手动删除太麻烦了,我们直接卸载程序里到时候自动帮我们删除不更方便吗

        所以我们需要用到一个函数shmctl()

 

  1. shmid:共享内存段的标识符(ID),即通过调用 shmget() 函数创建共享内存时返回的 shmid

  2. cmd:控制命令,用于指定要执行的操作类型。可以使用以下命令之一:

    • IPC_STAT:获取共享内存段的状态信息,将结果存储在 buf 参数指向的 struct shmid_ds 结构体中。
    • IPC_SET:设置共享内存段的状态信息,使用 buf 参数中提供的值。
    • IPC_RMID删除共享内存段,将其标记为删除状态,并在释放最后一个进程的附加段之后销毁。
  3. buf:一个指向 struct shmid_ds 结构体的指针,用于传递或接收共享内存段的状态信息。

     这是struct shmid_ds结构体指针的内容:

struct shmid_ds {
    struct ipc_perm shm_perm;   // 共享内存的权限信息
    size_t shm_segsz;           // 共享内存的大小
    time_t shm_atime;           // 上一次连接共享内存的时间
    time_t shm_dtime;           // 上一次与共享内存断开连接的时间
    time_t shm_ctime;           // 上一次修改共享内存的时间
    pid_t shm_cpid;             // 创建共享内存的进程ID
    pid_t shm_lpid;             // 最后一个操作共享内存的进程ID
    unsigned short shm_nattch;  // 当前连接到共享内存的进程数量
    // 其他字段...
};

其中第二个参数cmd我们目前只使用第3个IPC_RMID删除共享内存的选项。

第三个参数buf我们暂且不使用,直接传入nullptr。

    int n = shmctl(shmid,IPC_RMID,nullptr);

挂接共享内存shmat()

        我们创建好了共享内存,当然需要将进程的地址空间与其挂接上,然后 才能使用,这里使用到了函数shmat().

 

shmat() 函数接受三个参数:

  1. shmid:共享内存段的标识符(ID),即通过调用 shmget() 函数创建共享内存时返回的 shmid。表示你想挂接哪一个共享内存。

  2. shmaddr:共享内存段连接到进程地址空间的首地址。通常将其设置为 NULL,指示系统选择适当的地址。如果想要指定特定的地址,可以传递一个非空的地址值。但不建议这样使用。

  3. shmflg:标志参数,用于指定连接共享内存的选项。常用的选项有:

    • SHM_RDONLY:以只读方式连接共享内存,不允许写入。
    • SHM_RND:将 shmaddr 参数忽略,系统选择一个地址以进行连接。

              其他选项参考 shmat() 函数的文档以获得更多详细信息。


        它的返回值是void*,是一个指向共享内存段的指针,即连接到进程地址空间的首地址。我们需要将结果强转为我们需要的类型,一般为char*,和malloc的使用类似。

所以可以如下这样使用:

    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);

shmaddr便是连接的进程地址空间的首地址。

取消挂接共享内存shmdt()

        删除共享内存时,无论有多少个进程与共享内存连接,都会被直接清理掉,这种方式不太好,所以在删除共享内存前,可以先取消挂接。当挂接数为0时,再释放共享内存。

        同样先来看一下用法:

         这个参数正好是我们刚才shmat返回的进程地址空间的首地址shmaddr,表示将共享内存从调用进程的地址空间中分离,使得该进程无法再访问该共享内存

        再来看一下返回值:

        如果取消挂接成功,则返回0,失败则返回-1.

所以我们可以直接这么使用:

    //3.将指定的共享内存,挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);

    //这里就是通信的逻辑

    //4.将指定的共享内存,从自己的进程地址空间取消关联
    int n = shmdt(shmaddr);
    assert(n != -1);

整体通信流程的实现

        认识了上面的接口,加上我们在共享内存的建立中所说的步骤,我们便可以制作一个完整的利用System V共享内存通信的流程了。        

        0. 我们要利用ftok()生成共享内存唯一标识key.

        1. 然后我们利用key调用shmget()函数创建共享内存

        2. 接着我们需要利用shmat()挂接上共享内存

        3. 然后实现通信的流程

        4. 利用shmdt()取消挂接

        5. 利用shmctl()删除共享内存(一般是server,谁创建的谁删除)

这里一共四个文件,分别为Log.hpp(日志信息),comm.hpp(共用的头文件),shmServer.cc, shmClient.cc 

Log.hpp(日志信息)

#pragma once
#include <iostream>
#include <ctime>
#include<string>
using namespace std;

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3

string msg[] = {
    "Debug ",
    "Notice",
    "Warning",
    "Error"
};

ostream& Log(string message,int level)
{
    cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;

    return cout;
}

comm.hpp(共用的头文件)

#pragma once 
#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
using namespace std;

#define PATH_NAME "/home/hyx"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //最好是页(PAGE:4096)的整数倍,假设是4097,OS也会申请4096*2的空间,剩下的4095空间相当于浪费了 


shmServer.cc

#include "comm.hpp"
#include "Log.hpp"
string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof(buffer),"0x%x",k);
    return buffer;
}
int main()
{
    //1.创建公共的key值
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug) << " server key : " << TransToHex(k) << endl;

    //2.创建共享内存 --- 建议创建一个全新的共享内存 
    int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //3.将指定的共享内存,挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
    Log("attach shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //这里就是通信的逻辑
    //将共享内存当做一个大字符串
    // char buffer[SHM_SIZE];
    //结论1:只要双方使用shm,一方直接向共享内存中写入数据。另一方就可以立马看到
    //       共享内存是所有进程IPC,速度最快的! 因为不需要过多的拷贝!表现为不需要将数据拷贝给操作系统
    for(;;)
    {
        printf("%s\n",shmaddr);
        if(strcmp(shmaddr,"quit") == 0) break;
        sleep(1);
    }

    //4.将指定的共享内存,从自己的进程地址空间取消关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //5..删除共享内存
    n = shmctl(shmid,IPC_RMID,nullptr);
    assert(n != -1);
    Log("delete shm done",Debug) << " shmid: " << shmid << endl;

    return 0;
}

shmClient.cc 

#include "comm.hpp"
#include "Log.hpp"
int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    if(k < 0)
    {
        Log("create key done", Error) << "client key : " << k << endl;
        exit(1);
    }
    Log("create key done",Debug) << " client key : " << k << endl;

    //获取共享内存
    int shmid = shmget(k,SHM_SIZE,IPC_CREAT);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << "client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Debug) << "client key : " << k << endl;
    // sleep(10);

    
    char* shmaddr = (char*)shmat(shmid,nullptr,0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << "client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Debug) << "client key : " << k << endl;
    // sleep(10);

    //使用
    //client将共享内存看做一个char类型的buffer
    char a = 'a';
    for(;a <= 'e'; a++)
    {
        //我们是每一次都向shmaddr[共享内存起始地址]写入
        snprintf(shmaddr,SHM_SIZE-1,\
        "hello,server, my pid is: %d, inc : %c\n",getpid(),a);
        sleep(2);
    }
    strcpy(shmaddr,"quit");

    //去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success",Debug) << "client key : " << k << endl;
    // sleep(10);
    //client 不需要删除共享内存,server负责这些,和client没关系。
    return 0;
}

然后我们编译运行,分两个窗口观察:

 client

 可以看到,双方都实现了通信。

到这里System V共享内存就介绍完毕了,如果有不懂或疑问的地方,欢迎评论区或私信哦~

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

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

相关文章

kubeadml 安装 k8s

目录 一&#xff1a;kubeadml 安装 k8s 1、网络环境 2、 环境准备 3、 所有节点安装docker 4、所有节点安装kubeadm&#xff0c;kubelet和kubectl ​5、部署K8S集群 6、测试 二&#xff1a; 部署 Dashboard 一&#xff1a;kubeadml 安装 k8s 1、网络环境 master&am…

VBA遍历Wrod所有表格每个单元格,单元格未尾两个回车替换

一、遍历 word中遍历所有表格的每个单元格。因为在单元格时会常出错。浪费了不少时间。 Sub a()Dim doc As Document, tb As Table, ce As cellDim rng As Range, p As ParagraphSet doc ActiveDocumentFor Each tb In doc.TablesFor Each ce In tb.Range.Cells 关键处就是这里…

作为程序猿,怎么维护自己的电脑?

我的电脑是联想拯救者R7000 内存&#xff1a;16G CPU &#xff1a;AMD R5 外存&#xff1a;500G。买这台电脑是个意外。情况是这样的&#xff0c;面试去了一家外包公司&#xff0c;入职当天&#xff0c; HR问我&#xff1a;你的电脑呢&#xff1f; 我&#xff1a;没有发给我啊 …

十一、ESP32加快240x240显示二维码

1. 效果 非常快速的显示出二维码 2. 原理 2.1 之前的方式(慢)

MySQL日期常见的函数

-- 获取当天日期 -- 2023-06-20 select curdate();-- 获取当天年月日时分秒 select now();-- 日期运算 -- 2024-06-20 17:04:17 select date_add(now(),interval 1 year);-- 日期比较 -- 0 select datediff(now(),now());-- 日期MySQL对于日期类型数据如何查询 -- 获取指定日期…

vcode开发go

配置环境变量 go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct 创建文件夹 mkdir hello cd hello go mod init hello 安装所有"all" matched packages go mod tidy

Linux文件属性与权限管理(可读、可写、可执行)

Linux把所有文件和设备都当作文件来管理&#xff0c;这些文件都在根目录下&#xff0c;同时Linux中的文件名区分大小写。 一、文件属性 使用ls -l命令查看文件详情&#xff1a; 1、每行代表一个文件&#xff0c;每行的第一个字符代表文件类型&#xff0c;linux文件类型包括&am…

Python编程从入门到实践练习第五章:if语句和条件测试

目录 一、条件测试1.1 检测多个条件&#xff08;and / or&#xff09;1.2 检测特定值是否包含在列表中1.3 if语句结构 二、if语句处理列表2.1 判断列表是否为空2.2 练习题代码输出 一、条件测试 1.1 检测多个条件&#xff08;and / or&#xff09; 所用关键词 and : 两个条件…

C++ 多态 虚函数表

文章目录 简易抽象理解多态多态的具体实现虚函数的定义虚函数的重写重定义&#xff08;隐藏&#xff09;、重载 、重写&#xff08;覆盖&#xff09;区别C11 override 和 final 关键字抽象类的定义接口继承和实现继承多态的原理&#xff1a;虚函数表单继承和多继承关系的虚函数…

Nginx可视化Nginx-gui

Github&#xff1a;GitHub - onlyGuo/nginx-gui: Nginx GUI Manager 运行方式支持docker、window 下载后压缩&#xff0c;直接运行startup.bat 默认账号密码&#xff1a;admin/admin

Flutter iOS 集成使用 flutter boost

在 Flutter项目中集成完 flutter boost&#xff0c;并且已经使用了 flutter boost进行了路由管理&#xff0c;这时如果需要和iOS混合开发&#xff0c;这时就要到 原生端进行集成。 注意&#xff1a;之前建的项目必须是 Flutter module项目&#xff0c;并且原生项目和flutter m…

CorelDRAW2023矢量制图支持Windows和macOS系统安装使用

CorelDRAW Graphics Suite - 矢量制图专为企业打造的矢量设计作图软件CorelDRAW Graphics Suite套装&#xff0c;支持Windows和macOS系统安装使用。使用 CorelDRAW Graphics Suite 2023&#xff0c;打破创意障碍&#xff0c;以自己的风格进行自由创作。与全球数百万仰赖 CorelD…

睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程

简介&#xff1a; 睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程 支持分享海报 支持暗黑模式 包含了音频数据 最近很火的助眠小程序&#xff0c;前端vue&#xff0c;可以打包H5&#xff0c;APP&#xff0c;小程序 后台可以设置流量主广告&#xff0c;非常不错的源码 代码完…

Linux操作系统知识点总结(二)

总结&#xff08;一&#xff09;链接Linux操作系统知识点总结&#xff08;一&#xff09;&#xff08;附VMware、CentOS以及finalshell的安装教程&#xff09;_你好&#xff0c;明天&#xff0c;&#xff0c;的博客-CSDN博客 43. 由于虚拟机的 Linux系统的IP地址是通过DHCP服务…

前端CSS文字阴影text-shadow记录

前端CSS文字阴影text-shadow记录 一、文字阴影 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Doc…

【基于IDEA + Spark 3.4.1 + sbt 1.9.3 + Spark MLlib 构建逻辑回归鸢尾花分类预测模型】

逻辑回归进行鸢尾花分类的案例 背景说明&#xff1a; 基于IDEA Spark 3.4.1 sbt 1.9.3 Spark MLlib 构建逻辑回归鸢尾花分类预测模型&#xff0c;这是一个分类模型案例&#xff0c;通过该案例&#xff0c;可以快速了解Spark MLlib分类预测模型的使用方法。 依赖 ThisBui…

“算法详解”系列第3卷贪心算法和动态规划出版

“算法详解”系列图书共有4卷&#xff0c;目前1到3卷已经出版。最新出版的是第3卷—贪心算法和动态规划。 算法详解 卷3 贪心算法和动态规划 “算法详解”系列图书共有4卷&#xff0c;本书是第3卷—贪心算法和动态规划。其中贪心算法主要包括调度、最小生成树、集群、哈夫曼编…

vue+vite线上环境地址和开发环境配置方式

vuevite线上环境地址和开发环境配置方式 第一种(放飞自我写法) 说明:后端已解决跨域的情况下配置线上部署访问地址和开发时候地址 java解决跨域代码: Configuration public class WebMvcConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegistr…

程序员编写文档的 10 个技巧

编写好的文档在软件开发领域具有重大意义。文档是概述特定问题陈述、方法、功能、工作流程、架构、挑战和开发过程的书面数据或指令。文档可以让你全面了解解决方案的功能、安装和配置。 文档不仅是为其他人编写的&#xff0c;也是为自己编写的。它让我们自己知道我们以前做过什…