【Linux】System V 共享内存

打印 上一主题 下一主题

主题 540|帖子 540|积分 1620

一、共享内存

1.1 共享内存的原理:

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



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

  1. struct shmid_ds {
  2.     struct ipc_perm shm_perm; /* operation perms */
  3.     int shm_segsz; /* size of segment (bytes) */
  4.     __kernel_time_t shm_atime; /* last attach time */
  5.     __kernel_time_t shm_dtime; /* last detach time */
  6.     __kernel_time_t shm_ctime; /* last change time */
  7.     __kernel_ipc_pid_t shm_cpid; /* pid of creator */
  8.     __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
  9.     unsigned short shm_nattch; /* no. of current attaches */
  10.     unsigned short shm_unused; /* compatibility */
  11.     void *shm_unused2; /* ditto - used by DIPC */
  12.     void *shm_unused3; /* unused */
  13. };
复制代码
三、共享内存的使用

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:把共享内存连接到当进步程去的时候准备放置它的那个地址
  1. shmaddr:把共享内存连接到当前进程去的时候准备放置它的那个地址,如果设为NULL为让系统自动选择。
  2.     shmaddr为NULL,核心自动选择一个地址
  3.     shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
  4.     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是一样的,代码如下:
  1. key_t GetCommonKey(const std::string& pathname, const int proj_id)
  2. {
  3.     key_t k = ftok(pathname.c_str(), proj_id);
  4.     if(k < 0)
  5.     {
  6.         perror("GetCommonKey");
  7.     }
  8.     return k;
  9. }
复制代码
3.3 有关共享内存的一些指令 

       共享内存不会随着进程的竣事而自动开释,如果不进行开释,该共享内存会一直存在,需要手动进行开释, 共享内存的生命周期随内核 | 文件的生命周期是随进程的。
  1. ipcs -m; // 进行共享内存的查看
  2. 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值

  1. // 获取一个相同的key值,需要访问一个相同的文件路径和一个随便的数字
  2. const std::string pathname = "/home/hrx/code/111/lesson3/shm";
  3. const int proj_id = 0x66;
  4. key_t GetKeyCommon()
  5. {
  6.     key_t k = ftok(_pathname.c_str(), _proj_id);
  7.     if (k < 0)
  8.     {
  9.         perror("GetCommonKey");
  10.     }
  11.     return k;
  12. }
复制代码
3.5.1.2 通过key值打开共享内存块

  1.     int Shmget(int key, int size, int flag)  // 将key值的共享内存创建出来
  2.     {
  3.         int shmid = shmget(key, size, flag);
  4.         if (shmid < 0)
  5.         {
  6.             perror("shmid");
  7.         }
  8.         return shmid;
  9.     }
  10.     std::string ToHex(int u)  // 将一个数字转换为十六进制的格式
  11.     {
  12.         char buff[128];
  13.         snprintf(buff, sizeof buff, "0x%x", u);
  14.         return buff;
  15.     }
  16.     Shm(const std::string &pathname, int proj_id, int who)  // 构造函数
  17.         : _pathname(pathname), _proj_id(proj_id), _who(who)
  18.     {
  19.         _key = GetKeyCommon();
  20.         if (who == Creater)
  21.             GetShmCeaterUse();
  22.         else
  23.             GetShmCeaterUse();
  24.     }
  25.     bool GetShmCeaterUse()
  26.     {
  27.         if (_who == Creater)
  28.         {
  29.             _shmid = Shmget(_key, 1024, IPC_CREAT | IPC_EXCL);
  30.             sleep(10);
  31.             if (_shmid > 0)
  32.                 return true;
  33.         }
  34.         return false;
  35.     }
  36.     bool GetShmForUse()
  37.     {
  38.         if (_who == User)
  39.         {
  40.             _shmid = Shmget(_key, 1024, IPC_CREAT);
  41.             if (_shmid > 0)
  42.                 return true;
  43.         }
  44.         return false;
  45.     }
复制代码
3.5.1.3 销毁共享内存

  1.     ~Shm()
  2.     {
  3.         if (_who == Creater)
  4.         {
  5.             // 进行共享库的删除
  6.             int res = shmctl(_shmid, IPC_RMID, nullptr);
  7.         }
  8.         std::cout << "Shm remove done..." << std::endl;
  9.     }
复制代码
3.5.2 进行shm的挂接和取消挂接

3.5.2.1 进行shm的挂接

  1.     void* Attach()
  2.     {
  3.         void* attaddr = shmat(_shmid, nullptr, 0);
  4.         if(attaddr == nullptr)
  5.         {
  6.             perror("attaddr");
  7.         }
  8.         std::cout << "name: " << GetName() << " attach shm..." << std::endl;
  9.         return attaddr;
  10.     }
复制代码
3.5.2.2 进行shm的取消挂接

  1.     void Dattach(const char* addr)
  2.     {
  3.         if(addr == nullptr) return;
  4.         shmdt(addr);
  5.         std::cout << "name: " << GetName() << "dis attach shm..." << std::endl;
  6.     }
复制代码
3.5.2.3 进行结果的演示

       刚开始的时候,我们会发现在共享内存的页面没有任何数字的变化,由于共享内存的全西安没有关闭,我们需要在创建共享内存的时候,为共享内存的权限设为可以进行操纵。
  1. // 修改共享内存的代码:
  2. _shmid = Shmget(_key, 1024, IPC_CREAT | IPC_EXCL | 0x666);
复制代码
        当我们开始实行代码后,会发现这个共享内存的挂接数量从0到1到2,末了,又逐渐淘汰。注意: 将共享内存段与当进步程脱离不即是删除共享内存,只是取消了当进步程与该共享内存之间的联系。

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

3.6 对shm的完成封装

  1. #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 2const 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()
  2.     {
  3.         if (_who == Creater)
  4.         {
  5.             // 进行共享库的删除
  6.             int res = shmctl(_shmid, IPC_RMID, nullptr);
  7.         }
  8.         std::cout << "Shm remove done..." << std::endl;
  9.     }    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)
  10.     {
  11.         if(addr == nullptr) return;
  12.         shmdt(addr);
  13.         std::cout << "name: " << GetName() << "dis attach shm..." << std::endl;
  14.     }private:    key_t _key;    int _who;    int _shmid;    void* _addr;    const std::string _pathname;    const int _proj_id;};
复制代码
四、进行进程之间的通信

4.1 直接进行通信

在将服务端与客户端进行挂接成功后,我们要来进行通信,将共享内存看成数组来进行通信:
   服务端:
  1.     Shm shm(pathname, proj_id, Creater);
  2.     char* shmaddr = (char*)shm.Addr();
  3.     while (true)
  4.     {
  5.         std::cout << shmaddr << std::endl;
  6.         sleep(1);
  7.     }
复制代码
   客户端 
  1.     Shm shm(pathname, proj_id, User);
  2.     shm.Zero();
  3.     char* shmaddr = (char*)shm.Addr();
  4.     sleep(3);
  5.     char ch = 'a';
  6.     while (ch <= 'z')
  7.     {
  8.         shmaddr[ch - 'a'] = ch;
  9.         ch++;
  10.         sleep(2);
  11.     }
复制代码
4.2 共享内存的一些知识点 

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

4.3 掩护共享内存

       管道提供了掩护措施,以是,我们可以创建一个管道,将数据放在管道中,当写完后,直接将数据发送给另一个进程,共享内存的容量巨细:如果容量巨细为4097,共享内存的巨细可以i自己随意定制,但是建议共享内存的巨细为4096的整数倍。,如果不是整数倍,操纵系统会给你整数倍的容量。
  1.     // 服务器端
  2.     Shm shm(pathname, proj_id, Creater);
  3.     char* shmaddr = (char*)shm.Addr();
  4.     // 2.创建管道
  5.     NamePipe fifo(comepath, Creater);
  6.     fifo.openforread();
  7.     while (true)
  8.     {
  9.         std::string tmp;
  10.         fifo.ReadNamePipe(&tmp);
  11.         std::cout << shmaddr << std::endl;
  12.     }
复制代码
  1.     // 客户端
  2.     NamePipe fifo(comepath, User);
  3.     fifo.operforwrite();
  4.     char ch = 'a';
  5.     while (ch <= 'z')
  6.     {
  7.         std::string tmp = "once";
  8.         fifo.WriteNamePipe(tmp);
  9.         shmaddr[ch - 'a'] = ch;
  10.         ch++;
  11.         sleep(2);
  12.     }
复制代码
五、共享内存与管道的对比

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

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


  • 服务端将信息从输入文件复制到服务端的临时缓冲区中。
  • 将服务端临时缓冲区的信息复制到管道中。
  • 客户端将信息从管道复制到客户端的缓冲区中。
  • 将客户端临时缓冲区的信息复制到输出文件中。
我们再来看看共享内存通信:

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


  • 从输入文件到共享内存。
  • 从共享内存到输出文件。
       以是共享内存是所有进程间通信方式中最快的一种通信方式,由于该通信方式需要进行的拷贝次数最少。
       但是共享内存也是有缺点的,我们知道管道是自带同步与互斥机制的,但是共享内存并没有提供任何的掩护机制,包括同步与互斥。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

不到断气不罢休

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表