进程间通信(二)/共享内存

2023-11-06

⭐前言:在前面的博文中分析了什么的进程间通信和进程间通信的方式之一:管道(匿名管道和命名管道)。接下来分析第二种方式:共享内存。

要实现进程间通信,其前提是让不同进程之间看到同一份资源。所谓共享内存,那就是不同进程之间,可以看到内存中同一块资源,这就是共享内存的概念。

共享内存原理

用户通过操作系统提供的系统调用,让操作系统帮助用户去申请一块空间,跟C语言中malloc函数、C++的new的意思差不多。创建好后,将创建好的内存映射到进程地址空间中,然后返回这个地址的起始地址给用户。最后,当结束通信后,就会取消进程和内存的映射关系去掉,然后释放这段内存空间!

而这段内存,就称为共享内存!进程与内存关联的行为称为挂接。取消进程与内存的映射关系,称为去关联。释放这段内存,叫做释放共享内存。

理解共享内存的开辟

①用户申请开辟共享内存空间的系统接口,是专门为了进程间通信而设计出来的,可以让不同进程同时跟其建立关联。跟malloc,new等等的函数不一样,它们虽然也可以在物理内存上开辟空间,但是只能用于本身进程。

②共享内存是一种通信方式,意味着所有想通信的进程都可以使用它。

③既然共享内存是一种通信方式,因此在OS中,一定存在多个共享内存!

实例代码

共享内存函数

按照上图的步骤:第一步,创建共享内存。以下是创建共享内存的两个函数。

①shmget函数

功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);

头文件:#include<sys/ipc.h>  #include<sys/shm.h>
参数:
        key : 这个共享内存段名字。
        size : 共享内存大小
        shmflg : 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样

其中重要的两个:

IPC_CREAT:如果不存在,创建之。如果存在,获取之。

IPC_EXCL:无法单独使用。需要与IPC_CREAT结合使用,

IPC_CREAT | IPC_EXCL:如果不存在,创建之。如果存在,出错并返回。如果创建成功,那么一定是一个新的共享内存。

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 - 1

shmget函数中的参数key,它能够标定唯一性!因为需要保证一个进程去申请共享内存,另外的进程去获取这个共享内存,它们的共享内存是同一个共享内存!而获取key是通过ftok函数来获取的。

②ftok函数

功能:将一个路径明和一个项目标识符转化成一个IPC的key

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

头文件:#include<sys/ipc.h> #include<sys/types.h>

参数:

        pathname:传进来的字符串

        proj_id:项目标识符

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

只要不同进程在调用ftok的时候,参数一模一样,获取相同的key,再去调用shmget函数,通过同一个key,就能访问同一个共享内存。

补充说明:

共享内存=物理内存块+共享内存的相关属性

上面谈到,OS中一定存在多个共享内存,而OS必须要对这些用户申请开辟的空间进行管理!即先描述再组织,因此,OS会对开辟的共享内存创建一个数据结构,一个共享内存一个数据结构,然后通过链表链接起来,统一管理。于是,在谈到申请开辟一块共享内存,就需要想到:共享内存 = 物理内存块 + 共享内存的相关属性!

key值被包含在了共享内存的属性中。

共享内存的相关属性被包含在共享内存的数据结构中,而其中的key值也包含在了里面。即key值是在shmget函数创建出来后被设置进入共享内存的属性当中,用来表示该共享内存,并表示该共享内存在内核中的唯一性!

shmid和key的关系区分

shmget函数返回值,假设命名为shmid。那么shmid与key的关系就如同在文件IO中的文件描述符fd和inode的关系一样,inode是一个文件一个inode,表示文件的唯一性,key是一个共享内存一个,表示的是共享内存的唯一性,它们都是底层访问目标的工具。但是上层是不用key或inode的,而是使用shmid和fd这样一个特定的整数来访问。一句话来说,一个是用户的,一个是系统的,两个互不干扰,这是它的好处。

查看共享内存指令

ipcs -m

ipc资源的特征

共享内存的生命周期是随操作系统的,不是随进程的,即使进程终止了,但没有去释放这段共享内存,那么它就会一直存在。

删除共享内存

ipcrm -m shmid

按照上图所示:以下是删除共享内存的函数。

③shmctl函数

功能:用于控制共享内存,即删除共享内存,设置共享内存属性等等
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

头文件:#include<sys/ipc.h> #include<sys/shm.h>
参数:
        shmid:由shmget返回的共享内存标识码。
        cmd:将要采取的动作(有三个可取值)

动作:

①IPC_STAT:获取共享内存属性

②IPC_SET:设置共享内存属性

③IPC_RMID:删除共享内存
        buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

按照上图所示,以下是将共享内存映射到进程地址空间的函数。

④shmat函数

功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid, const void *shmaddr, int shmflg);

头文件:#include<sys/shm.h>   #include<sys/types.h>
参数:
        shmid: 共享内存标识,即想和哪个共享内存关联起来
        shmaddr:指定连接的地址。就是想把这个共享内存映射到哪个进程地址空间中,给出这个进程地址。
        shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY


返回值:成功返回一个指针,指向共享内存;失败返回-1

使用完后,不直接删除共享内存,而是先去关联。以下是去关联的函数。

⑤shmdt函数

功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);

头文件:#include<sys/shm.h>   #include<sys/types.h>
参数:shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

示例代码代码如下:

代码思路:创建一段共享内存,创建两个没有亲属关系的进程,client进程负责写入,server进程负责读取。

头文件comm.hpp:

#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0X66

//设置共享内存大小:建议为4KB的整数倍
//因为系统分配共享内存是以4KB为单位的!
#define MAX_SIZE 4096

//获取key
key_t getKey()
{
    //通过ftok函数获取key
    key_t k = ftok(PATHNAME,PROJ_ID);//获得同一个key
    if(k < 0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(1);
    }
    return k;
}
//创建共享内存
int getShmHelper(key_t k,int flags)
{
    //通过shmget函数创建共享内存。
    //第一个参数是key,第二个参数是共享内存的大小。第三个参数是权限标志
    int shmid = shmget(k,MAX_SIZE,flags);//创建共享内存
    if(shmid<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}
//通过封装函数给用户去使用,只需传入key值即可。
//获取共享内存,不一定要新的,因为不用调用它的进程去创建新的
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT);
}
//创建共享内存,使用IPC_CREAT | IPC_EXCL,确定创建的共享内存一定是新的。需要给权限0600
int createShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);
}

//进程地址空间与共享内存相联
void* attachShm(int shmid)
{
    //通过shmat函数将共享内存段连接到进程地址空间
    //传入shmid和指定连接的进程地址的地址,但是这个一般不填,系统会自动去填
    //第三个参数是权限标志,是对内存只读还是读写。
    //在Linux系统中,一般是64位。我们这里需要将shmat函数返回的指针判断是否关联成功
    //强行转化为longlong
    void *men = shmat(shmid,nullptr,0);
    if((long long)men==-1L)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(3);
    }
    return men;//返回起始地址

}

void detachShm(void* start)
{
    //通过shmdt函数去关联
    if(shmdt(start)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

void delShm(int shmid)
{
    //通过shmctl函数删除共享内存
    //第一个参数是函数是需要对哪个共享内存操作,那个共享内存
    //第二个参数是需要进行什么样的操作
    //第三个参数一般给nullptr
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}

负责写入的进程程序代码client.cc:

#include"comm.hpp"
#include<unistd.h>

int main()
{
    //第一步:创建key,创建共享内存
    key_t k = getKey();//获取key
    printf("key: 0x%x\n",k);//查看key值
    int shmid = getShm(k);//创建共享内存
    printf("shmid:%d\n",shmid);//查看shmid

    //第二步:关联内存和进程地址空间
    char* start = (char*)attachShm(shmid);
    printf("attach success,address start: %p\n",start);//查看起始地址

    //开始使用
    //写下需要往共享内存段写入的数据
    const char* message = "hello server,我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    while(true)
    {
        sleep(5);
        //写入到共享内存段,将共享内存段当字符串,不需要额外char buffer[];
        snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt++);
    }

    //去关联
    detachShm(start);

    //这个工程项目不需要删除共享内存



    return 0;
}

负责读取的进程的程序代码server.cc

#include"comm.hpp"
#include<unistd.h>

int main()
{
    key_t k = getKey();//获取key值
    printf("key: 0x%x\n",k);//查看key值
    int shmid = createShm(k);//创建共享内存,必须是新的
    printf("shmid: %d\n",shmid);//查看共享内存

    //关联
    char* start = (char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    //使用
    while(true)
    {
        //读取共享内存中的数据
        printf("client say: %s\n",start);
        //获取共享内存中的属性数据(部分)
        struct shmid_ds ds;
        shmctl(shmid,IPC_STAT,&ds);
        printf("获取属性: size: %d, pid: %d, myself: %d, key: 0x%x",\
                ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);
        sleep(1);
    }

    //去关联
    detachShm(start);

    //删除共享内存
    delShm(shmid);

    return 0;
}

 结果如下:在第一个五秒时,共享内存中没有任何数据。第二个五秒,消息编号为1。第三个五秒,消息编号为2......

 对于从内核数据结构中获取共享内存的属性,发现没有直接显示key值。但实际上key值是在这个内核数据结构中里面的另外一个结构体里面。

共享内存的优缺点

优点:所有使用共享内存的进程通信,速度是最快的!能大大减少数据拷贝的次数!并且生命周期是随系统的!那么,如果我们考虑到同样一份代码,分别使用管道和共享内存的话,并且考虑键盘输入和显示器输出,那么管道有几次拷贝?共享内存有几次拷贝?

 如图,管道的话,需要创建buffer来获取数据,然后通过管道进行通信。而共享内存不需要,因为共享内存可以作为字符串空间,直接写入和读取数据。因此,根据上图所示,管道是6次拷贝,共享内存是4次拷贝。当然,代码不同,拷贝的次数也不会同。

缺点:共享内存没有同步和互斥!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

进程间通信(二)/共享内存 的相关文章

  • 抑制 makefile 中命令调用的回显?

    我为一个作业编写了一个程序 该程序应该将其输出打印到标准输出 分配规范需要创建一个 Makefile 当调用它时make run gt outputFile应该运行该程序并将输出写入一个文件 该文件的 SHA1 指纹与规范中给出的指纹相同
  • 跟踪 Linux 程序中活跃使用的内存

    我想跟踪各种程序在特定状态下接触了多少内存 例如 假设我有一个图形程序 最小化时 它可能会使用更少的内存 因为它不会重新绘制窗口 这需要读取图像和字体并执行大量库函数 这些对象仍然可以在内存中访问 但实际上并没有被使用 类似的工具top它们
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • 如何禁用 GNOME 桌面屏幕锁定? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何阻止 GNOME 桌面在几分钟空闲时间后锁定屏幕 我已经尝试过官方手册了在红帽 https access redhat com doc
  • 我可以从命令行打印 html 文件(带有图像、css)吗?

    我想从脚本中打印带有图像的样式化 html 页面 谁能建议一个开源解决方案 我使用的是 Linux Ubuntu 8 04 但也对其他操作系统的解决方案感兴趣 你可以给html2ps http user it uu se jan html2
  • sendfile64 只复制约2GB

    我需要使用 sendfile64 复制大约 16GB 的文件 到目前为止我所取得的成就是 include
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • Linux 内核标识符中前导和尾随下划线的含义是什么?

    我不断遇到一些小约定 比如 KERNEL Are the 在这种情况下 是内核开发人员使用的命名约定 还是以这种方式命名宏的语法特定原因 整个代码中有很多这样的例子 例如 某些函数和变量以 甚至 这有什么具体原因吗 它似乎被广泛使用 我只需
  • CentOS:无法安装 Chromium 浏览器

    我正在尝试在 centOS 6 i 中安装 chromium 以 root 用户身份运行以下命令 cd etc yum repos d wget http repos fedorapeople org repos spot chromium
  • Linux 可执行文件与 OS X“兼容”吗?

    如果您在基于 Linux 的平台上用 C 语言编译一个程序 然后将其移植以使用 MacOS 库 它会工作吗 来自编译器的核心机器代码在 Mac 和 Linux 上兼容吗 我问这个问题的原因是因为两者都是 基于 UNIX 的 所以我认为这是真
  • 在 Linux 上使用多处理时,TKinter 窗口不会出现

    我想生成另一个进程来异步显示错误消息 同时应用程序的其余部分继续 我正在使用multiprocessingPython 2 6 中的模块来创建进程 我试图用以下命令显示窗口TKinter 这段代码在Windows上运行良好 但在Linux上
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • C语言中如何通过内存地址映射函数名和行号?

    如何用 GCC 中的内存地址映射回函数名称和行号 即假设一个 C 语言原型 void func Get the address of caller maybe this could be avoided MemoryAddress get
  • 在我的 index.php 中加载 CSS 和 JS 等资源时出现错误 403

    我使用的是 Linux Elementary OS 并在 opt 中安装了 lampp My CSS and JS won t load When I inspect my page through browser The console
  • ftrace:仅打印trace_printk()的输出

    是否可以只转储trace printk 输出于trace文件 我的意思是过滤掉函数跟踪器 或任何其他跟踪器 中的所有函数 一般来说 您可以在选项目录中关闭选项 sys kernel debug tracing options Use ls显
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do

随机推荐

  • 【React】19课 react组件使用redux小案例

    小案例效果入下 我们来看一下文件的目录结构 redux文件内的index js文件代码 const add Num 增加一条数据 const remove Num 删除一条数据 state 数据原先的状态 action 需要来进行改造的内容
  • using namespace std啥意思?命名空间?

    using 是C 中的关键字 命名空间作用域参考 两种形式 using 命名空间名 标识符名 将指定的标识符暴露在当前的作用域内 使得在当前作用域可以直接引用该标识符 using namespace 命名空间名 将指定命名空间内的所有标识符
  • 默纳克调试说明书_默纳克_NICE3000调试说明书(修改版)

    4 2 4 密码设置 为了更有效地进行参数保护 NICE3000电梯一体化控制器提供了密码保护 下面示例将密码改为12345的过程 粗体表示闪烁位 图4 9 密码的设定过程 设置了用户密码 即用户密码FP 00的参数不为0 会先进入用户密码
  • Selenium被检测为爬虫,怎么屏蔽和绕过

    01 Selenium 操作被屏蔽 使用selenium自动化网页时 有一定的概率会被目标网站识别 一旦被检测到 目标网站会拦截该客户端做出的网页操作 比如淘宝和大众点评的登录页 当手工打开浏览器 输入用户名和密码时 是能正常进入首页的 但
  • DNS查询服务器的基本流程

    DNS查询服务器的基本流程 假定域名为m xyz com的主机想知道另一台主机 域名为y abc com 的IP地址 例如 主机m xyz com打算发邮件给y abc com 此时 必须知道主机y abc com的IP地址 主机m xyz
  • 因果推理(九):双重差分(Difference-in-Differences)

    1 预备知识 回顾一下第二章的 unconfoundedness 假设 无混杂假设等价于 T T T到 Y Y Y之间没有畅通无阻的后门路径 在这种情况下 关联就是因果 那么可以用下面的式子计算ATE
  • 【编译原理】第1章 引论

    1 引论 目录 一 基本概念 二 编译过程 三 PL 0编译程序 题目练习 拓展 高级程序语言 参数传递 一 基本概念 源语言程序 源语言编写的程序 源语言一般指的是编写源程序所用的语言 它必须翻译成机器语言才能在计算机中使用 目标语言程序
  • linux下显卡驱动掉了,出现The system is running in low-graphics mode时解决方法

    Ctrl alt F1 cd etc X11 sudo cp xorg conf failsafe xorg conf sudo reboot 重启会发现linux很卡 原因是显卡驱动有问题 Ctrl alt F1 sudo service
  • Android 10如何隐藏应用图标

    隐藏应用图标这个功能应该用的很少 毕竟用到这个功能的应用可想而之 手动滑稽 隐藏图标的实现方式我想在网上查到很多了有在AndroidManifest实现的也用通过如下代码实现的 启动组件 param componentName 组件名 pr
  • 2.处理器与设备间数据交换方式

    处理器与外设之间传输数据的控制方式通常有3种 查询方式 中断方式和直接内存存取 DMA 方式 21 查询方式 设备驱动程序通过设备的I O端口空间 以及存储器空间完成数据的交换 例如 网卡一般将自己的内部寄存器映射为设备的I O端口 而显示
  • ASPXGridView使用总结

    转载 一 ASPXGridView外观显示 属性 Caption 列的标题 KeyFieldName 数据库字段 SEOFriendly 是否启用搜索引擎优化 Summary 指定分页汇总信息的格式 Setting节点的ShowFilter
  • 如何将小程序放到公众号菜单?

    第一步 公众号关联小程序 注 小程序要放到公众号菜单需要先把小程序与公众号关联 如果已经关联了可以直接下一步操作 1 到微信公众平台 登录小程序账号 进入设置 gt 开发设置 即可得到小程序AppID 将其复制下来 获取小程序AppID 2
  • chinaUnix中的linux源代码学习

    原文地址 http bbs chinaunix net thread 1930079 1 1 html 大家好 内核源码版对本版块的精华帖进行了分类汇总 所有的精华帖分为十大类 各个分类的精华帖相关信息分布在该贴2 11楼 每个分类各占1楼
  • AXI总线学习小结

    1 AXI总线结构 AXI总线由5个通道构成 通道名称 通道功能 数据流向 read address 读地址通道 主机 gt 从机 read data 读数据通道 包括数据通道和读响应通道 从机 gt 主机 write address 写地
  • SQL中的连接

    一 表连接 SQL提供了多种类型的连接方式 它们之间的区别在于 从相互交叠的不同数据集合中选择用于连接的行时所采用的方法不同 连接类型 定义 内连接 只连接匹配的行 左外连接 包含左边表的全部行 不管右边的表中是否存在与它们匹配 的行 以及
  • 个人网站搭建 01——Linux 安装宝塔面板

    宝塔面板概念 官网 https www bt cn new index html 安全高效的服务器运维面板 使用宝塔前 手工输入命令安装各类软件 操作起来费时费力并且容易出错 而且需要记住很多 Linux 的命令 非常复杂 使用宝塔后 2
  • python cv实现二值化区域检测

    功能 检测图像中最大的矩形区域 代码实现 import cv2 import numpy as np imgpath test1 jpeg img cv2 imread imgpath gray cv2 cvtColor img cv2 C
  • maven创建自定义web工程模板

    一 先搭建好一个项目模板 这里推荐两种方式 更推荐第一种 第一种 maven创建web工程 使用模板方式 二 第二种 maven创建web工程不用模板 手动创建 一 注意每个文件夹下都放一个文件占位 否则创建模板时会认为是空目录不进行创建
  • 5分钟了解AI算法 之 隐式马尔可夫模型(Hidden Markov Model)

    一 隐式马尔可夫模型 简介 Hidden Markov Model 在之前的文章中已经介绍了马尔可夫链 马尔可夫模型与马尔可夫链的区别在于 隐马尔科夫模型多了一条不可见的时序状态 通过对该模型各参数的推导即可解决当前AI领域比较常见的三大基
  • 进程间通信(二)/共享内存

    前言 在前面的博文中分析了什么的进程间通信和进程间通信的方式之一 管道 匿名管道和命名管道 接下来分析第二种方式 共享内存 要实现进程间通信 其前提是让不同进程之间看到同一份资源 所谓共享内存 那就是不同进程之间 可以看到内存中同一块资源