[Linux打怪升级之路]-system V共享内存

news2024/12/24 8:43:27

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

本期学习目标:认识什么是 system V共享内存,认识共享内存的接口函数,学会运用共享内存

目录

一、共享内存的基本原理

1、什么是共享内存

2、共享内存和管道的对比

二、共享内存的系统接口 

1、shmget函数(创建共享内存)

 2、shmat函数(关联)

3、shmdt函数(去关联) 

4、shmctl函数(控制)

​三、用共享内存进行通信

1、comm.hpp

2、server.cpp 

 3、client.cpp

4、现象


一、共享内存的基本原理

1、什么是共享内存

共享内存是一种进程间通信机制,它允许两个或多个进程共享同一块物理内存空间,从而实现数据共享。在共享内存中,进程可以通过读写共享内存的方式来相互通信,而不必进行复杂的管道、消息队列等进程间通信操作。

那我们知道了共享内存,其实就是OS操作系统管理的一块共享物理内存,那共享内存是怎么样实现进程间通信的呢?

下面我们看图来理解

首先我们让操作系统在物理内存中,创建一块共享内存。然后在将创建的内存通过页表映射到进程地址空间,这样 A和B进程就通过共享内存建立起来联系,A进程如果想和B进程通信,只要让A进程往物理内存中写数据,在让B进程读数据就可以了。最后我们不在想让AB进程进行通信,我们只要去关联就可以了,AB进程的关联无非是共享的物理内存,所以我们只要取消二者的映射关系,在释放内存即可。

那这种方式和我们前面将的管道将有什么优缺点吗?

客官别走,且听我细细道来。

2、共享内存和管道的对比

共享内存和管道是两种不同的进程间通信机制,它们各有优势和适用场景。下面是它们的对比:

  1. 数据传输方式:

    • 共享内存:进程直接访问同一块物理内存,数据在内存中共享。
    • 管道:通过缓冲区进行数据传输,数据在缓冲区中依次流动。
  2. 通信效率:

    • 共享内存:由于直接访问内存,读写效率高,适用于大量数据交换的场景。
    • 管道:数据需要经过内核空间,相对较慢,适用于小量数据传输或者不频繁的通信。
  3. 数据同步和通信方式:

    • 共享内存:需要考虑并发控制和同步问题,通信方式更为灵活,可以使用类似锁、信号量等机制进行同步。
    • 管道:基于先进先出的原则,数据流动是单向的,不需要显式的同步操作。
  4. 可扩展性:

    • 共享内存:多个进程可以同时访问共享内存,适用于多个生产者和消费者的情况。
    • 管道:一般情况下,只支持一个生产者和一个消费者,不适用于多个进程之间的数据交换。

综上所述,共享内存适用于大量数据交换、需要高效率和灵活同步的场景,而管道适用于小量数据传输或者不频繁的通信,并且只涉及一个生产者和一个消费者

二、共享内存的系统接口 

1、shmget函数(创建共享内存)

功能:用来创建共享内存

原型: int shmget(key_t key, size_t size, int shmflg);

参数

           key:这个共享内存段名字

           size:共享内存大小

           shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样

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

key是什么 

 key是共享内存段的名字,我们要注意他是多少我们不关系,我们关心的是他的作用是能够进行唯一的标识。

虽然我们不关系他是多少,但是我们要传什么key给shmegt,key_t 是一个什么类型。

这里我们先看一个获取k的函数ftok()

。那key_t是个什么类型?

key_t是一个在Unix/Linux系统中用于表示IPC(进程间通信)键的类型。它被定义为一个整数类型(通常是int,用于唯一标识共享内存、消息队列和信号量等进程间通信机制。

 举例子获取k:

#define PATHNAME "."
#define PROJ_ID 0x66

key_t getKey()
{
    key_t k = ftok(PATHNAME,PROJ_ID);
    if(k < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }

    return k;
}

其中的ftok() 函数使用由给定路径名命名的文档的标识(必须引用现有的、可访问的文档)和最不重要的 8 位proj_id(必须为非零)生成一个key_t类型的 System V IPC 密钥

   shmflg是什么

   shmflg是标志位,用于指定操作共享内存的方式这里我们举例二种最常见的标志位:

IPC_CREAT:如果共享内存不存在就创建,如果存在就获取他。

IPC_EXCL:无法单独使用,IPC_CREAT|IPC_EXCL如果不存在创建共享内存,如果存在就出错返回。

shmget的用法:

#define MAX_SIZE 4096
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);
    }
    return shmid;
}

 2、shmat函数(关联)

功能:将共享内存段连接到进程地址空间

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

参数: shmid: 共享内存标识 shmaddr:指定连接的地址 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

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

这里我们要注意的是返回指针可以认为是共享内存的起始地址

 shmflg:

  1. SHM_RND:将 size 参数舍入到系统页面大小的倍数。这样做可以保证共享内存段的大小是系统页面大小的整数倍,以提高性能。

  2. SHM_RDONLY:以只读方式打开共享内存段。这意味着进程只能读取共享内存中的数据,不能进行写操作。这个标志通常用于让多个进程共享只读数据的情况

用法:

//返回指是共享内存的开始地址
void* attachShm(int shmid)
{
    void* start = shmat(shmid,nullptr,0);
    if((long long)start == -1L)
    {
        std::cerr << errno << ":" << strerror(errno) <<std::endl;
        exit(3);
    }
    return start;
}

3、shmdt函数(去关联) 

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

原型: int shmdt(const void *shmaddr);

参数 :shmaddr: 由shmat所返回的指针

返回值:成功返回0;失败返回-1 注意:将共享内存段与当前进程脱离不等于删除共享内存段

用法:

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

4、shmctl函数(控制)

功能:用于控制共享内存

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

参数:shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值) buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

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

这里我们重点关注:参数cmd

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值

IPC_SET:在进程有足够权限的前提下,把共享内存的当前值设置为shmid_ds数据结构中给出的值

IPC_RMID:删除共享内存

  struct shmid_ds *buf参数保存着共享内存的模式状态和访问权限的数据结构,里面存放这共享内存的共享参数

 三、用共享内存进行通信

下面我们将用共享内存实现server和client的通信

1、comm.hpp

我们定义一个comm.hpp的头文件定义server和client共同使用的函数:

#ifndef _COMM_HPP
#define _COMM_HPP

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


#define PATHNAME "."
#define PROJ_ID 0x66

#define MAX_SIZE 4096

//获取密钥k
key_t getKey()
{
    key_t k = ftok(PATHNAME,PROJ_ID);
    if(k < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }

    return k;
}

//调用shmget创建共享内存
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);
    }
    return shmid;
}

//获取shmid标识码
int getShm(key_t k)
{
    return getShmHelper(k,IPC_CREAT);
}

//调用getShmHelper
int createShm(key_t K)
{
    return getShmHelper(K,IPC_CREAT | IPC_EXCL | 0600);
}

//返回指是共享内存的开始地址
void* attachShm(int shmid)
{
    void* start = shmat(shmid,nullptr,0);
    if((long long)start == -1L)
    {
        std::cerr << errno << ":" << strerror(errno) <<std::endl;
        exit(3);
    }
    return start;
}

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

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

#endif

2、server.cpp 

这里我们让server进程,打印k和shmid值给我们看一下,并进行创建共享内存,并且进行和client进行通信

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

int main()
{
     key_t k = getKey();
    //看看k
    printf("key: 0x%x\n",k);

    //创建共享内存
    int shmid = createShm(k);
    printf("shmid: %d\n",shmid);

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

    const char* message = "hello server,我是clinet正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    while(true)
    {
        sleep(5);
        snprintf(start,MAX_SIZE,"%s[pid:%d][信息标号:%d]",message,id,cnt);

    }

    detachShm(start);

    return 0;
}

 3、client.cpp

这里我们让client和server进行通信,server负责接受就可以了

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

int main()
{
    key_t k = getKey(); 
    printf("key: 0x%x\n", k);
    int shmid = getShm(k);
    printf("shmid: %d\n", shmid);

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

    const char* message = "hello server,我是clinet正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    while(true)
    {
        sleep(5);
        snprintf(start,MAX_SIZE,"%s[pid:%d][信息标号:%d]",message,id,cnt);

    }
    //去关联
    detachShm(start);

    return 0;
}

4、现象

运行shm_server

运行shm_client 

 但是当我们第二次在运行./shm_server时候却不行了

这是为什么呢? 

我们通过ipcs -m命令查看一下:

ipcs -m

ipcs -m 是一个Unix/Linux系统中的命令,用于列出当前系统中的共享内存段信息。它可以显示已经创建的共享内存段的详细信息,包括标识符、权限、大小、进程ID等。

  • key:共享内存段的键值。
  • shmid:共享内存段的标识符。
  • owner:创建该共享内存段的用户ID。
  • perms:共享内存段的权限。
  • bytes:共享内存段的大小(字节数)。
  • nattch:连接到该共享内存段的进程数量。
  • status 字段表示共享内存段的状态

其中

status 值及其含义如下:

  • 0:表示共享内存段当前未被使用或已被释放。
  • dest:表示共享内存段标记为准备删除状态。这意味着共享内存段即将被销毁,但仍然有进程连接到它,只有当所有连接到该共享内存段的进程都脱离连接时,它才会被完全删除。
  • out:表示共享内存段处于被卸载状态。这意味着该共享内存段已经被脱离连接,但仍然存在于系统中。可以通过手动操作或进程退出来释放该共享内存段。
  • err:表示共享内存段状态异常,可能由于系统错误或其他问题导致无法正常访问和管理。

 

 这说明了共享内存生命周期是随操作系统的,不是随进程。用就是说如果我们没有主动调用shmcl函数去控制删除共享内存空间,那么我们后面就要自去删除一下就可以在次运行了。

ipcrm -m shmid

 

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

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

相关文章

基于Ubuntu Server编译YTM32 SDK工程

基于Ubuntu Server编译YTM32 SDK工程 文章目录 基于Ubuntu Server编译YTM32 SDK工程需求下载软件包安装与配置配置虚拟机联网模式启用ssh连接启用ftp连接安装armgcc编译工具链确认make工具 验证 需求 在Linux系统环境下搭建SDK的编译环境&#xff1a; 方便加入到持续集成工具…

16.The Tensor Product:Vector/Covector combinations

本节将概括目前为止所学的张量积知识。并讨论一般张量&#xff0c;它可以由任意数量的向量和协向量的任意组合来生成。 同样&#xff0c;也是使用的非标准的符号。 (2&#xff0c;0)阶张量&#xff0c; 由两个向量生成的。 &#xff08;1&#xff0c;2&#xff09;阶张…

C++学习之多态详解

目录 多态的实现 例题 重载 重写 重定义的区别 抽象类 多态实现原理 多态的实现 C中的多态是指&#xff0c;当类之间存在层次结构&#xff0c;并且类之间是通过继承关联时&#xff0c;就会用到多态。多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执…

Spring IOC之@ComponentScan

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

[SWPUCTF 2023 秋季新生赛] web题解

文章目录 colorful_snakeNSS_HTTP_CHEKER一键连接!ez_talkPingpingpingUnS3rialize查查needRCE-PLUSbackup colorful_snake 打开题目&#xff0c;查看js源码 直接搜flag 把那三行代码复制到控制器&#xff0c;得到flag NSS_HTTP_CHEKER 都是http请求基本知识 抓包按照要求来&…

企业知识库管理系统怎么做?

21世纪&#xff0c;一个全新的信息化时代&#xff0c;从最初的传统办公到现在的信息化办公&#xff0c;一个世纪的跨越造就了各种大数据的诞生。 知识库系统 在这个数据横行的时代&#xff0c;文档管理产品市场逐渐兴盛起来&#xff0c;企业知识库管理系统作为企业的智慧信息的…

计算机组成原理new15 磁盘

文章目录 磁盘的结构磁盘的性能指标磁盘阵列固态硬盘SSD关于机械硬盘和固态硬盘的地址 这里计算传输时间和数据传输率是难点 磁盘的结构 注&#xff1a;磁盘的基本读取单位是扇区&#xff0c;但是每次只能读取1bit&#xff0c;这里我们应该理解为磁盘每次的读写操作至少要持续…

初始 c++(1)

目录: 目录 1: 命名空间的详解 2:c的输入与输出关键字及理解 3:详细讲解第一个c程序(每段代码的意思) 4:缺省参数的理解 5:函数重载 引言&#xff1a;从今天开始我们就开始学习c了&#xff0c;让我们一起开始新的知识吧&#xff01; 1&#xff1a;命名空间 所谓的命名空间…

深度学习学习笔记-模型的修改和CRUD

目录 1.打印模型,理解模型结构2.模型保存与加载3.模型的模块CRUD和模块的层的CRUD 1.打印模型,理解模型结构 import torchclass MyModel(torch.nn.Module):def __init__(self):super().__init__()self.layer1 torch.nn.Sequential(torch.nn.Linear(3, 4),torch.nn.Linear(4, …

云计算——网络虚拟化简介

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 前期回顾 前言 一.网络虚拟化 二.网络虚拟化介绍 三.为什么要网络虚拟化 四,网络…

STM32+2.9inch微雪墨水屏(电子纸)实现显示

本篇文章从硬件原理以及嵌入式编程等角度完整的介绍了墨水屏驱动过程&#xff0c;本例涉及的墨水屏为2.9inch e-Paper V2,它采用的是“微胶囊电泳显示”技术进行图像显示&#xff0c;其基本原理是悬浮在液体中的带电纳米粒子受到电场作用而产生迁移&#xff0c;从而改变显示屏各…

【C++初阶】小白入门C++

目录 前言&#xff1a;1、C关键字2、命名空间2.1命名空间是什么2.2为什么要有命名空间2.3命名空间怎么使用2.3.1命名空间的写法2.3.2命名空间是可以嵌套的2.3.3使用命名空间的三种方式 3、C输入和输出3.1初识cout和cin3.2C的输入输出可以自动识别变量类型 4、缺省参数4.1缺省参…

Linux:用户和权限

Linux&#xff1a;用户和权限 1. 认知root用户1.1 root用户&#xff08;超级管理员&#xff09;1.2 su和exit命令1.3 sudo命令1.3.1 为普通用户配置sudo认证 2. 用户、用户组管理2.1 用户组管理2.2 用户管理2.3 getent命令 3. 查看权限控制3.1 认知权限信息3.1.1 案例 4. 修改权…

react 实战- 玩转 react 全家桶(进阶)学习

一个命令是怎么运行起来的? Shell运行一个命令,实际上是运行一个脚本 环境变量 装了node以后,node的路径,就被注册到了环境变量里. 一个js的东西,可以注册? bin Webpack配置 构建 import A from A , const Arequire(A) 为什么可以这么写?为哈都行?本质上,是构建工…

人人自媒体的时候,Ai绘画还值得踏入吗?

前言 先说结论&#xff0c;如果你不打算涉足自媒体&#xff0c;平时也从不上网发什么内容去展示自己的话&#xff0c;其实AI绘画对你来说意义不大。但如果你对自媒体感兴趣&#xff0c;会涉及发作品&#xff0c;发内容&#xff0c;甚至去设计图片&#xff0c;那么AI绘画值得你…

【【萌新的SOC学习之自定义IP核的学习与设计】】

萌新的SOC学习之自定义IP核的学习与设计 本章为了更加深入的了解FPGA的自定义IP和IP封装测试等问题 参考了正点原子 第六讲自定义IP核呼吸灯实验 和 第十九章 IP封装与接口定义实验 为了更好的理解自定义IP核 我们先介绍一个带AXI主从接口的IP核 我们可以展开AXI从接口 下…

Vue--》简易资金管理系统后台项目实战(后端)

今天开始使用 node vue3 ts搭建一个简易资金管理系统的前后端分离项目&#xff0c;因为前后端分离所以会分两个专栏分别讲解前端与后端的实现&#xff0c;后端项目文章讲解可参考&#xff1a;前端链接&#xff0c;我会在前后端的两类专栏的最后一篇文章中会将项目代码开源到我…

转行做程序员,多晚都不晚

大家好啊&#xff0c;我是董董灿。 最近有不少小伙伴加我微信咨询一些问题&#xff0c;有同学想了解AI行业的现状&#xff0c;想着转行的&#xff0c;也有在校生想了解毕业后工作方向的&#xff0c;当然也有想学习编程知识的。 诚惶诚恐&#xff0c;没想到之前写的文章&#…

【CHI】CHI协议,transaction事务汇总

前言 CHI协议最难的是什么&#xff0c;就是那一堆各种各样的事务&#xff0c;你不知道什么场景应该使用什么合适的事务&#xff0c;收到X事务又该回复什么事务。相当于CHI给你制定了很多种&#xff08;尽可能覆盖完全&#xff09;场景及事务&#xff0c;你需要去了解&#xff0…

英语——分享篇——每日200词——2401-2600

2401——moisture——[mɔɪstʃə(r)]——n.潮气&#xff0c;湿气&#xff0c;水分——moisture——moist潮湿的(熟词)ur你的(编码your)e鹅(编码)——潮湿的地方你的鹅一身潮气——Moisture in the atmosphere condensed into dew during the night.——大气中的水分在夜间凝结…