首页 > 系统相关 >【Linux】System V 共享内存

【Linux】System V 共享内存

时间:2024-06-02 14:57:55浏览次数:31  
标签:IPC int System key Linux shmid 共享内存 shm

一、共享内存

1.1 共享内存的原理:

       两个进程,操作系统在内存空间中创建一个共享内存。在之前学习库的时候,有一个共享库的概念。我们可以按照其概念来了解共享内存的概念:将共享内存映射到页表中,和进程的地址空间建立联系。我们可以将共享内存的虚拟地址交给用户。两个进程之间就可以通过虚拟地址找到同一个共享内存进行通信。

1.2 理解:

  • 上面说的操作,只能是操作系统来做
  • 操作系统必须提供上面步骤的系统调用,供系统A、B进行调用
  • 共享内存在系统中有多份,供不同个数,不同对的操作系统同时进行通信
  • 操作系统要对共享内存进行管理,先描述再组织
  • 共享内存并不是简单的一段空间,也要有描述并管理共享内存的数据结构和匹配的算法
  • 共享内存 = 共享空间(数据) + 共享内存的属性

二、共享内存的数据结构

struct shmid_ds {
    struct ipc_perm shm_perm; /* operation perms */
    int shm_segsz; /* size of segment (bytes) */
    __kernel_time_t shm_atime; /* last attach time */
    __kernel_time_t shm_dtime; /* last detach time */
    __kernel_time_t shm_ctime; /* last change time */
    __kernel_ipc_pid_t shm_cpid; /* pid of creator */
    __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
    unsigned short shm_nattch; /* no. of current attaches */
    unsigned short shm_unused; /* compatibility */
    void *shm_unused2; /* ditto - used by DIPC */
    void *shm_unused3; /* unused */
};

三、共享内存的使用

3.1 一些有关函数

3.1.1 shmget函数

函数的原型:

函数的参数:

  • key:这个共享内存端的名字
  • size:共享内存的大小
  • shmfig:由九个权限表标志构成,用法和创建文件时使用的mode模式标志是一样的,利用位图进行传参。

在这里的标识码有两个:IPC_CREAT和IPC_EXCL。

  • IPC_CREAT:如果内核中不存在键值和key相同的共享内存,则新建一个共享内存;如果存在这样的共享内存,则返回该共享内存的标识码(可以获得一个存在的共享内存)
  • IPC_EXCL:单独使用没有意义,只有和IPC_CREAT一起使用才有意义
  • IPC_CREAT | IPC_EXCL:如果内核中不存在键值和key相同的共享内存,则新建一个共享内存;如果存在这样的共享内存,则报错(如果我成功返回,这个shm则是一个新的共享内存)

函数的返回值:

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

3.1.2 ftok函数 

ftok函数的原型:

函数的功能:

        系统建立IPC通讯(如消息队列、共享内存等)  必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
函数的参数:

  • pathname:必须是一个已经存在且程序可范围的文件。
  • proj_id:虽然定义为一个整数,其实实际只有8个bit位有效,即如果该参数大于255,则只有后8bit有效。 

函数的返回值:

       当函数执行成功,则会返回key_t键值,否则返回-1。在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值。

3.1.3 stmctl函数

shmctl函数的原型:

函数的功能:

       用来控制共享内存

函数的参数:

  • shmid:有shmget函数返回的共享内存的标识码
  • cmd:将要采取的动作(有三个可取值)
命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内粗的当前关联值设为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段
  • buf:指向一个保存着恭喜昂内存的模式状态和访问权限的数据结构

函数的返回值:

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

3.1.4 shmat函数

函数的原型:

函数的功能:

       将创建好的共享内存连接到某个进程,并指定内存空间
函数的参数:

  • shmid:shmget返回的共享内存标识
  • shmaddr:把共享内存连接到当前进程去的时候准备放置它的那个地址
shmaddr:把共享内存连接到当前进程去的时候准备放置它的那个地址,如果设为NULL为让系统自动选择。

    shmaddr为NULL,核心自动选择一个地址

    shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。

    shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:           shmaddr - (shmaddr % SHMLBA)
  • shmflg:其是一组按位OR(或)在一起的标志。它的两个可能取值是SHM_RND和SHM_RDONLY,shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

函数的返回值:

       如果成功返回一个指针,指向共享内存的第一个字节;如果失败,则返回-1

3.1.5 shmdt函数

函数的原型:

函数的功能:

       将共享内存段与当前进程脱离
函数的参数

  • shmaddr:由shmat返回的指针

函数的返回值:

       如果成功返回0;失败则返回-1 

3.2 获取key值 

       只要拥有相同的文件路径和一个数字,我们可以使用这个函数来使服务端和客户端形成的key是一样的,代码如下:

key_t GetCommonKey(const std::string& pathname, const int proj_id)
{
    key_t k = ftok(pathname.c_str(), proj_id);

    if(k < 0)
    {
        perror("GetCommonKey");
    }
    return k;
}

3.3 有关共享内存的一些指令 

       共享内存不会随着进程的结束而自动释放,如果不进行释放,该共享内存会一直存在,需要手动进行释放, 共享内存的生命周期随内核 | 文件的生命周期是随进程的。

ipcs -m; // 进行共享内存的查看
ipcrm -m key // 进行共享内存的删除
  • 我们可以使用 ipcs -m指令查看共享内存
  • 我们可以使用 ipcrm -m key指令删除共享内存

3.4 key VS shmid

       key属于用户形成,内核使用的字段,用户不能使用key来进行shm的管理,内核进行区分shm的唯一性的

       shmid属于内核给用户返回的一个标识符,用来进行用户及对共享内存进行管理的id值。

3.5下面来进行对shm的准备工作

3.5.1 shm的创建和销毁工作

3.5.1.1 使进程之间获取一个相同的key值
// 获取一个相同的key值,需要访问一个相同的文件路径和一个随便的数字
const std::string pathname = "/home/hrx/code/111/lesson3/shm";
const int proj_id = 0x66;

key_t GetKeyCommon()
{
    key_t k = ftok(_pathname.c_str(), _proj_id);

    if (k < 0)
    {
        perror("GetCommonKey");
    }
    return k;
}
3.5.1.2 通过key值打开共享内存块
    int Shmget(int key, int size, int flag)  // 将key值的共享内存创建出来
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmid");
        }
        return shmid;
    }

    std::string ToHex(int u)  // 将一个数字转换为十六进制的格式
    {
        char buff[128];
        snprintf(buff, sizeof buff, "0x%x", u);
        return buff;
    }

    Shm(const std::string &pathname, int proj_id, int who)  // 构造函数
        : _pathname(pathname), _proj_id(proj_id), _who(who)
    {
        _key = GetKeyCommon();
        if (who == Creater)
            GetShmCeaterUse();
        else
            GetShmCeaterUse();
    }

    bool GetShmCeaterUse()
    {
        if (_who == Creater)
        {
            _shmid = Shmget(_key, 1024, IPC_CREAT | IPC_EXCL);
            sleep(10);
            if (_shmid > 0)
                return true;
        }
        return false;
    }

    bool GetShmForUse()
    {
        if (_who == User)
        {
            _shmid = Shmget(_key, 1024, IPC_CREAT);
            if (_shmid > 0)
                return true;
        }
        return false;
    }
3.5.1.3 销毁共享内存
    ~Shm()
    {
        if (_who == Creater)
        {
            // 进行共享库的删除
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "Shm remove done..." << std::endl;
    }

3.5.2 进行shm的挂接和取消挂接

3.5.2.1 进行shm的挂接
    void* Attach()
    {
        void* attaddr = shmat(_shmid, nullptr, 0);
        if(attaddr == nullptr)
        {
            perror("attaddr");
        }
        std::cout << "name: " << GetName() << " attach shm..." << std::endl;
        return attaddr;
    }
3.5.2.2 进行shm的取消挂接
    void Dattach(const char* addr)
    {
        if(addr == nullptr) return;
        shmdt(addr);
        std::cout << "name: " << GetName() << "dis attach shm..." << std::endl;
    }
3.5.2.3 进行效果的演示

       刚开始的时候,我们会发现在共享内存的页面没有任何数字的变化,因为共享内存的全西安没有关闭,我们需要在创建共享内存的时候,为共享内存的权限设为可以进行操作。

// 修改共享内存的代码:
_shmid = Shmget(_key, 1024, IPC_CREAT | IPC_EXCL | 0x666);

        当我们开始实行代码后,会发现这个共享内存的挂接数量从0到1到2,最后,又逐渐减少。注意: 将共享内存段与当前进程脱离不等于删除共享内存,只是取消了当前进程与该共享内存之间的联系。

在服务器端和客户端一起进行挂接,下面是现象:

3.6 对shm的完成封装

#pragma once

#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <cstdio>
#include <string>
#include <sys/types.h>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>

#define Creater 1
#define User 2
const std::string pathname = "/home/hrx/code/111/lesson3/shm";
const int proj_id = 0x66;

class Shm
{
public:
    key_t GetKeyCommon()
    {
        key_t k = ftok(_pathname.c_str(), _proj_id);

        if (k < 0)
        {
            perror("GetCommonKey");
        }
        return k;
    }

    int Shmget(int key, int size, int flag)
    {
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmid");
        }
        return shmid;
    }

public:

    int getkey()
    {
        return _key;
    }

    int getshmid()
    {
        return _shmid;
    }

    std::string GetName()
    {
        if(_who == Creater) return "Creater";
        else if(_who == User) return "User";
        else return "None";
    }

    std::string ToHex(int u)
    {
        char buff[128];
        snprintf(buff, sizeof buff, "0x%x", u);
        return buff;
    }

    Shm(const std::string &pathname, int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who)
    {
        _key = GetKeyCommon();
        if (who == Creater)
            GetShmCeaterUse();
        else
            GetShmForUse();
        _addr = Attach(); // 直接进行挂接
    }

    bool GetShmCeaterUse()
    {
        if (_who == Creater)
        {
            _shmid = Shmget(_key, 1024, IPC_CREAT | IPC_EXCL | 0666);
            std::cout << "key:" << _key << ' ' << "shmid:" << _shmid << std::endl;
            if (_shmid > 0)
                return true;
        }
        return false;
    }

    bool GetShmForUse()
    {
        if (_who == User)
        {
            _shmid = Shmget(_key, 1024, IPC_CREAT);
            std::cout << "key:" << _key << ' ' << "shmid:" << _shmid << std::endl;
            if (_shmid > 0)
                return true;
        }
        return false;
    }

    ~Shm()
    {
        if (_who == Creater)
        {
            // 进行共享库的删除
            int res = shmctl(_shmid, IPC_RMID, nullptr);
        }
        std::cout << "Shm remove done..." << std::endl;
    }

    void* Addr()
    {
        return _addr;
    }

    void Zero()
    {
        if(_addr)
            memset(_addr, 0, sizeof _addr);
    }

    void* Attach()
    {
        if(_addr != nullptr) Dattach((char*)_addr); 
        void* attaddr = shmat(_shmid, nullptr, 0);
        if(attaddr == nullptr)
        {
            perror("attaddr");
        }
        std::cout << "name: " << GetName() << " attach shm..." << std::endl;
        return attaddr;
    }

    void Dattach(const char* addr)
    {
        if(addr == nullptr) return;
        shmdt(addr);
        std::cout << "name: " << GetName() << "dis attach shm..." << std::endl;
    }

private:
    key_t _key;
    int _who;
    int _shmid;
    void* _addr;
    const std::string _pathname;
    const int _proj_id;
};

四、进行进程之间的通信

4.1 直接进行通信

在将服务端与客户端进行挂接成功后,我们要来进行通信,将共享内存看成数组来进行通信:

服务端:

    Shm shm(pathname, proj_id, Creater);
    char* shmaddr = (char*)shm.Addr();
    while (true)
    {
        std::cout << shmaddr << std::endl;
        sleep(1);
    }

客户端 

    Shm shm(pathname, proj_id, User);
    shm.Zero();
    char* shmaddr = (char*)shm.Addr();
    sleep(3);
    char ch = 'a';
    while (ch <= 'z')
    {
        shmaddr[ch - 'a'] = ch;
        ch++;
        sleep(2);
    }

4.2 共享内存的一些知识点 

       在进行共享内存的使用时,共享内存不提供任何保护机制,数据不一致问题。我在写一个字符串,但是还没有写完就被读取了一半,造成了数据不一致问题。我们在写共享内存没有使用任何系统调用,共享内存是进程间通信速度最快的,因为共享内存大大地减少了拷贝次数。 

                         

4.3 保护共享内存

       管道提供了保护措施,所以,我们可以建立一个管道,将数据放在管道中,当写完后,直接将数据发送给另一个进程,共享内存的容量大小:如果容量大小为4097,共享内存的大小可以i自己随意定制,但是建议共享内存的大小为4096的整数倍。,如果不是整数倍,操作系统会给你整数倍的容量。

    // 服务器端
    Shm shm(pathname, proj_id, Creater);
    char* shmaddr = (char*)shm.Addr();

    // 2.创建管道
    NamePipe fifo(comepath, Creater);

    fifo.openforread();

    while (true)
    {
        std::string tmp;
        fifo.ReadNamePipe(&tmp);
        std::cout << shmaddr << std::endl;
    }
    // 客户端
    NamePipe fifo(comepath, User);
    fifo.operforwrite();
    char ch = 'a';
    while (ch <= 'z')
    {
        std::string tmp = "once";
        fifo.WriteNamePipe(tmp);
        shmaddr[ch - 'a'] = ch;
        ch++;
        sleep(2);
    }

五、共享内存与管道的对比

       当共享内存创建好后就不再需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。实际上,共享内存是所有进程间通信方式中最快的一种通信方式。

我们先来看看管道通信:

       从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作:

  • 服务端将信息从输入文件复制到服务端的临时缓冲区中。
  • 将服务端临时缓冲区的信息复制到管道中。
  • 客户端将信息从管道复制到客户端的缓冲区中。
  • 将客户端临时缓冲区的信息复制到输出文件中。

我们再来看看共享内存通信:

       从这张图可以看出,使用共享内存进行通信,将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作:

  • 从输入文件到共享内存。
  • 从共享内存到输出文件。

       所以共享内存是所有进程间通信方式中最快的一种通信方式,因为该通信方式需要进行的拷贝次数最少。

       但是共享内存也是有缺点的,我们知道管道是自带同步与互斥机制的,但是共享内存并没有提供任何的保护机制,包括同步与互斥。

标签:IPC,int,System,key,Linux,shmid,共享内存,shm
From: https://blog.csdn.net/2301_77868664/article/details/139360298

相关文章

  • linux:进程状态
    往往计算机的cpu只有一个,一个cpu有多个核心,一个cpu在同一时间只能被一个进程占用,又因为计算机的cpu数量总是少于进程数的,因此cpu是通过时钟轮片的方式处理每个进程的任务, 这些进程并不是凌乱的放在操作系统中的,他们是被一个数据结构有组织,有规律的管理起来的,所以一个进......
  • 详解C语言system()函数,一个函数让初学者的代码有趣(一)
    一.为什么一定要学习system()函数             对于绝大多数初学者来说,我们在学习C语言的过程中,所写出来的内容大多都只能展现在冰冷的黑白程序框中,所实现的功能也是千篇一律,如果只是完成学习任务,那就够了,但是对于一个希望写出来一点高级东西的程序员,那是远......
  • Linux目录的基本结构(RHEL8系统基本使用之文件操作)
    1.Linux的目录树结构2.各目录的功能介绍3.理解文件路径表示方法Who?——>当前登录的用户Where?——>路径我要在哪儿创建文件?我要删除什么地方的什么文件?我所要查看的文件在哪里?What?——>操作命令How?——>理清思路,找到方法,做就对了绝对路径1.一定是以"/"(根)开......
  • SSH远程连接Linux服务器
    1.1SSH(SecureShell)是一种网络协议,用于加密方式远程登录到服务器。以下是通过SSH连接Linux服务器的基本步骤:安装SSH客户端:Windows10及以上版本自带了OpenSSH客户端安装SSH服务端:在服务器端安装OpenSSH,需要在服务器终端进行。#安装sshsudoaptinstallopenssh-server......
  • 在Linux中,如何进行集群管理?
    在Linux中,进行集群管理涉及到多个步骤和考虑因素。以下是一个详细的指南,帮助你理解并执行Linux集群管理:一、理解集群管理的基本概念集群定义:集群是一组相互独立的、通过高速网络互联的计算机,它们构成一个组并以单一系统的模式加以管理。集群技术可以在较低成本下提供高性能、可......
  • 在Linux中,如何进行系统性能瓶颈分析?
    在Linux中进行系统性能瓶颈分析是一个系统性的过程,涉及多个方面。以下是一个详细的步骤说明,用于分析和诊断Linux系统性能瓶颈:1.确定性能指标CPU利用率:检查CPU是否成为瓶颈。可以使用top、htop等工具查看CPU的使用情况,包括用户态、内核态和空闲态的占比。内存使用:检查内存是否......
  • 在Linux中,如何进行系统故障恢复?
    在Linux系统中进行故障恢复是一个涉及诊断问题、制定恢复计划并执行恢复步骤的过程。以下是一些基本步骤和策略,帮助您应对不同类型的系统故障:1.初步诊断与隔离识别症状:首先,观察并记录故障的具体表现,比如系统无法启动、服务异常、性能下降等。查看系统日志:使用dmesg命令查看内......
  • 在Linux中,如何进行高可用性配置?
    在Linux环境中实现高可用性(HighAvailability,HA)通常涉及多个层面的策略和技术,以确保系统、服务或应用在面对硬件故障、软件错误或维护操作时能够持续运行。以下是构建Linux高可用性环境的一些关键步骤和组件:1.理解高可用性概念目标:最小化停机时间,提高系统或服务的可用性。......
  • 在Linux中,如何进行网络故障排查?
    在Linux中进行网络故障排查时,通常需要遵循一系列步骤来确保能够准确定位并解决问题。以下是一个详细的网络故障排查流程:1.检查物理连接确保网线连接稳固,无物理损坏,尝试更换网线或端口。检查网络设备(如交换机、路由器)的端口状态和连接,确保它们正常工作。2.使用ping命令测试......
  • FFmpeg开发笔记(二十六)Linux环境安装ZLMediaKit实现视频推流
    ​《FFmpeg开发实战:从零基础到短视频上线》一书在第10章介绍了轻量级流媒体服务器MediaMTX,通过该工具可以测试RTSP/RTMP等流媒体协议的推拉流。不过MediaMTX的功能实在是太简单了,无法应用于真实直播的生产环境,真正能用于生产环境的流媒体服务器还要看SRS或者ZLMediaKit。ZLMedia......