Linux——进程间通信(System V共享内存)

news2024/12/25 9:02:53

目录

共享内存示意图(理解共享内存的关键)

shmget函数

第一个参数

ftok函数

使用ftok打印key值

第二个参数

第三个参数

 返回值

打印shmget

再谈key值

举例理解key值

共享内存的过程

创建共享内存

关联共享内存

去除关联

​编辑

使用共享内存通信

删除共享内存

完整共享内存通信代码

comm.hpp

server.cc

client.cc

makefile


共享内存示意图(理解共享内存的关键)

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

需要通信时:

OS在物理内存上申请一块共享内存空间,让两个进程看到这同一份空间(也就是使两个进程看到同一块资源)。然后将创建好的内存分别通过页表映射进两个进程的地址空间,也称为挂接。

不通信的时候:

1、取消进程与内存的映射关系,也就是去除关联。

2、释放共享内存空间。

System V 共享内存是专门设计出来进程IPC(进程间通信)的。

shmget函数

首先通过man手册来看一下shmget的基本信息。

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

第一个参数

首先我们要明白,key能唯一标识我们创建的共享内存!

ftok函数

我们使用ftok函数来决定key的值。

key_t ftok(char *pathname,char proj_id);

pathname就是我们设置的一个路径,proj_id是任意设置的(作用也是为了生成能唯一标识的key)。

ftok可以通过这两个参数来生成一个key值,当我们创建key失败的时候该函数会返回-1。

使用ftok打印key值

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 // 随便设置的


// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k < 0)
    {
        // cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2

        // 使用strerror来打印错误码描述
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

#endif

shm_client.cc

#include"comm.hpp"


int main()
{
    key_t k = getKey();
    printf("0x%x\n", k);
    return 0;
}


shm_server.cc




#include"comm.hpp"


int main()
{
    key_t k = getKey();
    printf("0x%x\n", k);
    return 0;
}

makefile 

.PHONY:all
all : shm_client shm_server

shm_client : shm_client.cc
g++ - o $@ $ ^ -std = c++11
shm_server:shm_server.cc
g++ - o $@ $ ^ -std = c++11

.PHONY:clean
clean :
rm - f shm_client shm_server

第二个参数

size:也就是共享内存的大小。

第三个参数

有两种设置方式:

1、IPC_CREAT

作用:如果不存在,创建,如果存在,就获取一个新的共享内存。

2、IPC_EXCL | IPC_CREAT
如果不存在,就创建共享内存,如果存在,就出错返回。
用户层面:如果创建成功,一定是一个新的shm(共享内存)。

转定义我们能看到他们底层就用一个二进制位来标定的

 返回值

成功会返回一个共享内存标识符,失败就返回-1,未来对该共享内存做操作就可以使用该标识符。

打印shmget

附有详细注释

 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 // 随便设置的
#define MAX_SIZE 4096// 设置的内存空间大小

// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k < 0)
    {
        // cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2

        // 使用strerror来打印错误码描述
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
    int shmid = shmget(k, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
}

// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作  ————  通过传入不同的flags值
int getShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT);
}

// 用于shm_server.cc来进行创建共享内存操作  ————  通过传入不同的flags值
int creatShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT | IPC_EXCL);
}

#endif

shm_client.cc

#include"comm.hpp"


int main()
{
    // 使用comm.hpp中的getkey函数来获取k值
    key_t k = getKey();
    printf("key: 0x%x\n", k);


    int shmid=getShm(k);

    printf("shmid: %d\n",shmid);

    return 0;
}

shm_server.cc

#include "comm.hpp"

int main()
{

    // 使用comm.hpp中的getkey函数来获取k值
    key_t k = getKey();
    printf("key: 0x%x\n", k);


    int shmid=creatShm(k);

    printf("shmid: %d\n",shmid);

    return 0;
}

makefile 

.PHONY:all
all : shm_client shm_server

shm_client : shm_client.cc
g++ - o $@ $ ^ -std = c++11
shm_server:shm_server.cc
g++ - o $@ $ ^ -std = c++11

.PHONY:clean
clean :
rm - f shm_client shm_server

再谈key值

举例理解key值

我们在使用malloc(1024)在堆空间开辟空间的时候,只需要在free(p)中传入一个地址就可以准确的对我们开辟的空间进行释放,并不用传入我们需要开辟空间的大小。

那么OS是如何知道我们开辟了多大的空间呢?

其实,我们向OS要1024的空间,OS并非就给我们只开辟1024的空间。OS为了管理我们,还要给我们额外开辟一些空间!!!

因此,我们的共享内存也要被管理起来。

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

我们申请共享内存的时候也会申请一个数据结构对象,操作系统需要将共享内存管理起来,本质管理的就是共享对应的特定数据结构对象形成的数据结构进行操作的。有点儿类似我们进程中的PCB。

创建共享内存的时候,如何保证共享内存在OS中是唯一的?

key就能保证!

只要另一个进程也看到同一个key就能进行通信!

key在哪儿?

在共享内存的相关属性中。
key是要通过shmget,设置进入共享内存属性中的!用来表示该共享内存,在内核中是唯一的!

shmid vs  key
fd    vs  inode

一个是内核用的id,一个是用户用的id。


至于为什么内核和用户不同用一个id,就类似与我们的身份证号与名字一样。

为什么身份证号已经可以唯一的标识我们了,我们还要用姓名进行标识?

这是一种解耦的体现。

共享内存的过程

共享内存从创建到使用需要经过几个过程。

创建共享内存 --> 关联共享内存 --> 去关联 --> 使用共享内存(通信) --> 删除共享内存

创建共享内存

使用shmget函数进行创建,服务端用来创建,客户端只需要获取我们创建出来的共享内存。



// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k < 0)
    {
        // cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2

        // 使用strerror来打印错误码描述
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}
// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
    int shmid = shmget(k, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
}

// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作  ————  通过传入不同的flags值
int getShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT);
}

// 用于shm_server.cc来进行创建共享内存操作  ————  通过传入不同的flags值
int creatShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}

关联共享内存

现在只做了共享内存的创建和删除,还没有让共享内存与对应的内存关联起来!!!!(是通过页表关联的)

// 关联共享内存
void *attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if ((long long)mem == -1L)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

shmid函数

 第一个参数

是我们创建共享内存时返回的共享内存地址。

第二个参数

shmaddr:是我们指定一个虚拟地址,让我们的共享内存去映射到该虚拟地址处

第三个参数

shmflg:读写权限问题 

关联成功返回值:对应共享内存空间(进程地址空间里面的)的起始地址。类似我们之前使用的malloc,返回值是我们开辟空间的起始地址。把共享内存贴到对应的进程地址空间。

去除关联

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

 返回值 

成功就是0,失败就是-1.

使用共享内存通信

// server.cc
// 使用
    while(true)
    {
        printf("client say : %s\n",start);
        sleep(1);
    }

// client.cc
// 使用
    const char *message = "hello server, 我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    // char buffer[1024];
    while (true)
    {
        sleep(1);
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
        // snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
        // memcpy(start,buffer,strlen(buffer)+1);
    }

 客户端向共享内存中发送数据,服务端接受后并打印出来!

删除共享内存

// 删除指定共享内存
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
    }
}

 

 

我们使用ipcs -m 可以看到我们当前创建的共享内存

ipcrm -m +我们需要删除的shmid就可以删除对应的共享内存

也可以使用shmctl函数进行删除,shmctl本意就是控制共享内存,在这里我们只需要使用删除操作即可!

根据操作不同返回值不同,当我们使用删除操作时,操作失败返回-1

完整共享内存通信代码

comm.hpp

#ifndef _COMM_HPP_
#define _COMM_HPP_

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0x66  // 随便设置的
#define MAX_SIZE 4096 // 设置的内存空间大小

// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k < 0)
    {
        // cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2

        // 使用strerror来打印错误码描述
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
    int shmid = shmget(k, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
}

// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作  ————  通过传入不同的flags值
int getShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT);
}

// 用于shm_server.cc来进行创建共享内存操作  ————  通过传入不同的flags值
int creatShm(key_t k)
{

    return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}

// 关联共享内存
void *attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if ((long long)mem == -1L)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

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

// 删除指定共享内存
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << errno << " : " << strerror(errno) << std::endl;
    }
}

#endif

server.cc

#include "comm.hpp"
// #include<unistd.h>
int main()
{

    // 使用comm.hpp中的getkey函数来获取k值
    key_t k = getKey();
    printf("key: 0x%x\n", k);

    int shmid = creatShm(k);

    printf("shmid: %d\n", shmid);

    // 五秒后共享内存将通过页表与对应进程地址空间进行关联
    sleep(5);

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


    // 使用
    while(true)
    {
        printf("client say : %s\n",start);
        sleep(1);
    }



    // std::cout<<"正在使用"<<std::endl;

    // 去关联
    detachShm(start);


    sleep(5);

    // 五秒后共享内存将被删除
    delShm(shmid);

    return 0;
}

client.cc

#include "comm.hpp"

int main()
{
    // 使用comm.hpp中的getkey函数来获取k值
    key_t k = getKey();
    printf("key: 0x%x\n", k);

    int shmid = getShm(k);

    printf("shmid: %d\n", shmid);

    sleep(5);

    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;
    // char buffer[1024];
    while (true)
    {
        sleep(1);
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
        // snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
        // memcpy(start,buffer,strlen(buffer)+1);
    }

    // 去关联
    detachShm(start);

    sleep(10);

    // done

    return 0;
}

makefile

.PHONY:all
all:shm_client shm_server

shm_client:shm_client.cc
	g++ -o $@ $^ -std=c++11
shm_server:shm_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shm_client shm_server

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

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

相关文章

关于财会〔2020〕6 号文件解读,本地化部署+区块链或成为新趋势?

2020年3月&#xff0c;财政部、国家档案局发布了《关于规范电子会计凭证报销入账归档的通知》财会〔2020〕6 号&#xff08;以下简称“通知”&#xff09;&#xff0c;对于电子会计凭证的范围、法律效力、适用条件都做出了进一步规范。这可以看作是财会档案管理由纸质化迈向全面…

每日一博 - 闲聊“突发流量”的应急之道

文章目录 概述思路 概述 面对“突发流量”的情况,我会采取以下应急措施: 扩容现有资源。这是最直接和最常用的方法。可以通过增加CPU、内存、节点等来扩容。典型案例是双11等大促期间,阿里会大规模扩容幕布等系统以应对流量激增。横向扩展,增加更多服务器或节点。通过增加服务…

【Unity-UGUI控件全面解析】| Scrollbar 滚动条组件详解

🎬【Unity-UGUI控件全面解析】| Scrollbar 滚动条组件详解一、组件介绍二、组件属性面板三、代码操作组件四、组件常用方法示例4.1 监听开关事件4.2 充当 进度条/血条 使用💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSD…

单片机之 理论概述

一 简介 51单片机&#xff0c;STM32单片机 二 最小系统板的构成 2.1 复位电路 复位&#xff1a;指使系统回到初始状态&#xff0c;重新开始执行程序。不同MCU的复位电平可能不同&#xff0c;比如51单片机为高电平复位&#xff0c;STM32为低电平复位。为防止系统正常执行过程…

7. 类的封装

一、类的封装 封装的含义&#xff1a; 所有属性都是私有的&#xff0c;外部不能直接访问提供与属性相关的成员函数&#xff0c;间接访问属性 模板代码&#xff1a; #include <iostream> using namespace std;class Clock { private:int hour, minute, second; public…

Linux之Jupyter NoteBook安装

一、Jupyter NoteBook简介 Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。Jupyter Notebook 的本质是一个 Web 应用程序&#xff0c;便于创建和共享程序文档&#xff0c;支持实时代码&#xf…

10个可直接访问的AI工具

10个不同功能的AI工具 1、ChatGPT镜像&#xff1a;知汇 2、AI画图工具&#xff1a;Midjourney 3、AI图片背景移除工具&#xff1a;AI Background Remover – Remove Background From Image 4、AI图片无损放大工具&#xff1a;https://www.upscayl.org/ 5、AI老照片上色修复工具…

1459页54万字电力行业数字化转型监管云平台 解决方案(WORD)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除。 目录 1 总体要求 1.1 系统框架及结构 1.1.1 总体架构 1.1.2 主配网一体化架构 1.1.3 数据流架构 1.1.4 数据流架构 1.1.5 功能结构 1.1.6 硬件结构 1.2 总…

事务传播行为 @Transactional

文章目录 前言一、事务是什么&#xff1f;二、使用步骤开始验证1.验证REQUIRED2.验证 REQUIRES_NEW3.同一个类中的两个方法 总结 前言 事务传播行为: 指的是在项目中开启多个事务后,他们之间的影响关系; 一、事务是什么&#xff1f; 逻辑上是一组操作&#xff0c;要么执行&am…

ASEMI代理ADUM3223ARZ-RL7原装ADI车规级ADUM3223ARZ-RL7

编辑&#xff1a;ll ASEMI代理ADUM3223ARZ-RL7原装ADI车规级ADUM3223ARZ-RL7 型号&#xff1a;ADUM3223ARZ-RL7 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-16 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;16 工作温度:-40C~125…

form表单与模板引擎

文章目录 一、form表单的基本使用1、什么是表单2、表单的组成部分3、 <form>标签的属性4、表单的同步提交及缺点&#xff08;1&#xff09; 什么是表单的同步提交&#xff08;2&#xff09; 表单同步提交的缺点&#xff08;3&#xff09; 如何解决表单同步提交的缺点 二、…

Java中的位运算

文章目录 Java中支持的位运算位运算规则逻辑运算与运算&#xff08;&&#xff09;或运算&#xff08;|&#xff09;异或运算&#xff08;^&#xff09;取反运算&#xff08;~&#xff09; 位移操作左移&#xff08;<<&#xff09;右移&#xff08;>>&#xff0…

十五周算法训练营——BFS

今天是十五周算法训练营的第六周&#xff0c;主要讲BFS专题。&#xff08;欢迎加入十五周算法训练营&#xff0c;与小伙伴一起卷算法&#xff09; 「BFS的核心思想是把一些问题抽象成图&#xff0c;从一个点开始&#xff0c;向四周开始扩散。一般来说&#xff0c;写BFS算法都是…

3-Raven2百个靶机渗透(精写-思路为主)

特别注明&#xff1a;本文章只用于学习交流&#xff0c;不可用来从事违法犯罪活动&#xff0c;如使用者用来从事违法犯罪行为&#xff0c;一切与作者无关。 文章目录 前言一、信息收集二、ssh爆破尝试三、根据框架exp和cve拿shell四、对mysql的进一步渗透&#xff0c;mysql UD…

Python - 通过 pyInstaller 打包成可执行文件

一、场景 通常来说&#xff0c;我们开发的 python 脚本一般都会用到一些第三方的包&#xff0c;并且需要对应版本的 python 解释器。因此在 python 脚本在不同的主机上运行的时候相对来说不是很方便&#xff0c;为此可以通过 pyInstaller 将脚本和解释器打包成可执行文件&…

数字中国创新大赛·信创赛道优秀作品推荐 | 国产工业实时操作系统(Intewell)

产品介绍和功能体系 Intewell工业实时操作系统源于有30多年发展历史的“道”操作系统&#xff0c;是一款微内核实时操作系统&#xff08;RTOS&#xff09;&#xff0c;具有良好的可扩展性、友好的用户开发环境和丰富的开发调试工具&#xff0c;提供POSIX接口。Intewell工业实时…

2023年5月产品经理认证NPDP线上班,我要报名学习

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

程序设计的三种结构-C中实现其的6条语句

什么是程序设计结构? C语言设计出来就是为了解决现实中存在的问题&#xff0c;但是现实中问题大多较为复杂&#xff0c;如何描述问题也成为了一个重要的问题。将这些众多的问题核心找出来&#xff0c;我们发现只需要使用三种基本的结构包括&#xff1a;顺序结构&#xff0c;分…

FS4068四节锂电池充电管理控制芯片

FS4068 是一款工作于 2.7V 到 6.5V 的 PFM 升压型四节锂电池充电控制集成电路。 FS4068采用恒流和恒压模式对电池进行充电管理&#xff0c;内部集成有基准电压源&#xff0c; 电感电流检测单元&#xff0c;电池电压检测电路和外置场效应晶体管驱动电路等&#xff0c; 具有外部元…

面向开发者的 ChatGPT 提示工程

LLM 正在逐步改变人们的生活&#xff0c;而对于开发者&#xff0c;如何基于 LLM 提供的 API 接口快速、便捷地开发一些具备更强能力、集成 LLM 的应用&#xff0c;来便捷地实现一些更新颖、更实用的能力&#xff0c;是一个急需学习的重要能力。由巨佬吴恩达老师与 OpenAI 合作推…