【进程通信】Syetem V 共享内存(结合代码模拟通信)

简介: 【进程通信】Syetem V 共享内存(结合代码模拟通信)

前言

进程之间除了使用管道来进行通信,还能通过申请共享内存的方式使多个进程访问同一片共享资源。下面介绍System V标准下的共享内存是如何使进程间通信的。

共享内存的原理

其实每一个进程的虚拟空间分布中都有一块区域专门用来存放公共资源,这个区域就是“数据共享区‘。

值得注意的是,这个数据共享区里的数据不属于任何一个进程,而是被多个进程共享。当一个进程连接到共享内存区域时,操作系统会将这块共享内存段映射到进程的虚拟地址空间中,使得进程可以直接访问这块共享内存。共享内存的生命周期是随操作系统的,也就意味着,如果我们不主动删除共享内存,即使创建共享内存的进程已经终止,该共享内存也会一直存在,直到重启或者用指令删除。

所以使用共享内存进行通信有以下步骤:

  1. 创建共享内存
  2. 连接共享内存
  3. 访问共享内存
  4. 分离共享内存
  5. 删除共享内存。

下面将一一介绍上述步骤。

创建共享内存区域

进程可以通过调用shmget函数来创建一个共享内存标识符,并且指定内存大小和权限等参数。该标识符类似与文件描述符fd,是操作系统给用户使用的,用来标识共享内存区域。

关于getshm函数:

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

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

  • key是一个键值,用于唯一标识共享内存区域。通常使用ftok函数来生成一个键值。
  • size是要创建的共享内存的大小,一般建议是4096的整数倍。
  • shmflg是权限标志,用来指定共享内存的权限和创建的方式,和打开文件的系统调用类似。
  • 创建成功返回一个非负整数(shmid),否则返回-1.

key和shmget返回值的区别

简单来说,key是一个键值,用来标识共享内存。而shmget的返回值即shmid主要用于操作共享内存的标识符。一个是用户在创建时交给操作系统的,另一个则是创建后操作系统给用户来操作的。就好像我们的文件系统中fd与inode的区别。一个文件没有被打开的时候,需要通过inode来找到并打开,而打开之后给用户一个描述符fd用于后续对文件的操作。

参数shmflag

shmflag是一个位图结构,通过按位|来传递多个选项信息。常见的选项有:

  1. IPC_CREAT:如果指定了这个选项,并且键值key对应的共享内存区不存在,则创建一个新的共享内存区。如果已经存在,就返回这个共享内存标识符。
  2. IPC_EXCL:与IPC_CREAT一起使用时,如果指定的共享内存区存在,shmget就返回错误。如果不存在,就创建一个。IPC_EXCL不单独使用。
  3. 一般来说,创建共享内存的进程需要使用IPC_CREAT|IPC_EXCL选项,因为这样创建出来的共享内存一定是最新的,就是目前没有其它进程使用的。而想使用这个已经被创建出来的共享内存的进程就使用IPC_CREAT.

参数key

我们可以通过ftok函数来获取一个key

#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char*pathname,int proj_id);

ftok函数可以根据一个已存在的文件路径pathname和一个整型的proj_id来转换成一个key。我们可以将pathname和proj_id的组合看成是一个规则,同样的规则生成的key相同。也就意味着,当我们创建shm之后,后面要是有进程想使用这个shm,我们就可以通过同样的pathname和proj_id的组合来获得同一个key,用同一个key获得的shmid也就是一样的。这样就能让不同的进程对同一个共享内存操作了!(通信的前提)

共享内存数据结构

为了管理这些共享内存,操作系统对这些共享内存做了描述并组织。下面给出共享内存是如何被描述的:

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 */
};

结构体shmid_ds其实就是一个共享内存的所有属性集合。包括了共享内存的创建时间、权限、修改时间、挂接数等。操作系统把这些shmid_ds集合在一起管理,所有的shmid_ds组合在一起就形成了一个数组。于是对共享内存的管理就变成了对shmid_ds数组的管理。而shmget函数的返回值shmid其实就是这个数组的下标。

用指令查询共享内存

使用ipcs -m指令可以查看所有的共享内存的信息。

同样,我们也可以使用ipcrm -m shmid来删除标识符为shmid的共享内存。

连接共享内存

我们已经知道如何创建一个共享内存了,那么该如何使用共享内存呢?我们需要将创建的共享内存段连接到进程的虚拟地址空间,并通过页表建立与物理内存空间的映射 。这样进程就能使用共享内存了。如何连接共享内存呢?可以使用shmat函数。

#include <sys/types.h>
#include <sys/shm.h>

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

  • shmid,共享内存的用户级标识符。
  • shmaddr,用于指定shm映射到进程虚拟地址空间的位置。如果设置为NULL,系统会自动找一个合适的位置来加载shm。如果不为空,且shmlag选项包括了SHM_RND,系统就会在地址shmaddr处加载shm.
  • shmflag,用于设置连接shm的一些属性,是一个位图,可以表示多个选项。
  • 如果shmat调用成功,返回shm加载到进程地址空间的起始位置,否则返回(void*)-1

分离共享内存

既然我们能通过shmat连接一个shm,也能”卸载“这个shm。

通过shmdt函数分离shm:

#include<sys/types.h>
#include<sys/shm.h>

int shmdt(const void* shmaddr);

shmaddr即进程地址空间中的shm的起始地址

删除共享内存

既然能创建一个共享内存,同样也能删除一个共享内存。

使用shmctl函数来释放shm.

#include<sys/ipc.h>
#include<sys/shm.h>

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


  1. cmd表示对共享内存的操作。具体操作有以下几种:
  • IPC_STAT:将与shmid相关的内核级数据结构shmid_ds的数据拷贝到buf中
  • IPC_SET:将buf指向的shmid_ds的值拷贝到shmid的数据中
  • IPC_RMID:删除共享内存
  1. buf一个指向shimd_ds结构的指针

用共享内存模拟Server与Client的通信

Shm.hpp

用来描述封装共享内存的操作集和属性集

#ifndef __SHM_HPP__
#define __SHM_HPP__

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

using namespace std;
const int gCreater = 1;
const int gUser = 2;
const string pathname = "/home/tsx/linux/24-5-2/myShm/4.shm";
const int proj_id = 0x66;
const size_t gShmSize = 4096;

class Shm
{
private:
    key_t GetKey()
    { // 获得key
        key_t k = ftok(_pathname.c_str(), _proj_id);
        if (k < 0)
        {
            perror("ftok");
        }
        return k;
    }

    int GetShm(key_t key, int size, int flag)
    { // 通过系统调用获得shm
        int shmid = shmget(key, size, flag);
        if (shmid < 0)
        {
            perror("shmget");
        }
        return shmid;
    }

    string PrintWho()
    {
        if (_who == 1)
            return "gCrearter";
        else if (_who == 2)
            return "gUser";
        else
            return "None";
    }

    void *AttachShm()
    {
        if (_shmaddr != nullptr)
        {
            DetachShm();
        }
        void *shmaddr = shmat(_shmid, nullptr, 0);
        if (shmaddr == nullptr)
        {
            perror("shmat");
        }
        cout << "who: " << PrintWho() << " attachshm :" << endl;
        return shmaddr;
    }

    void DetachShm()
    {
        if (_shmaddr == nullptr)
        {
            return;
        }
        shmdt(_shmaddr);
        cout << "who: " << PrintWho() << " deattachshm :" << endl;
    }

    void DeleteShm()
    {
        if (_who == gCreater)
        {
            int res = shmctl(_shmid, IPC_RMID, nullptr);
            if (res == -1)
            {
                perror("shmclt");
            }
        }
    }

public:
    Shm(const string &pathname, const int proj_id, int who)
        : _pathname(pathname), _proj_id(proj_id), _who(who), _shmaddr(nullptr)
    {
        // 获得_key
        _key = GetKey();
        // 创建shm
        if (_who == gCreater)
        {
            GetShmForCreater();
        }
        else if (_who == gUser)
        {
            GetShmForUse();
        }
        _shmaddr = AttachShm(); // 连接
    }
    ~Shm()
    {
        DetachShm(); // 分离连接shm
        DeleteShm(); // 删除shm
    }

    bool GetShmForCreater()
    { // server创建shm
        _shmid = GetShm(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);
        if (_shmid < 0)
            return false;
        cout << "server成功创建shm" << endl;
        return true;
    }

    void InitShmaddr()
    {
        if (_shmaddr)
        {
            memset(_shmaddr, 0, sizeof gShmSize);
        }
    }
    void *GetShmaddr()
    {
        return _shmaddr;
    }
    bool GetShmForUse()
    {
        _shmid = GetShm(_key, gShmSize, IPC_CREAT | 0666);
        if (_shmid < 0)
            return false;
        cout << "client成功获得shm" << endl;
        return true;
    }

private:
    key_t _key;
    int _shmid;
    int _id;
    string _pathname;
    int _who;
    int _proj_id;
    void *_shmaddr;
};

#endif

NamePipe.hpp

封装了命名管道的属性集和方法集,在共享内存中使用命名管道可以保证数据的同步。

#include <iostream>
#include <string>
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

const string comm_path = "/home/tsx/linux/24-5-2/myShm/myfifo";
#define Creater 1 // 身份码,1表示创建管道者,2表示使用者
#define User 2
#define Defaultfd -1
#define Read_Mode O_RDONLY
#define Write_Mode O_WRONLY
#define BaseSize 4096 // 默认读管道的数据大小

class NamePipe
{
private:
  void CreatFifo()
  {
    int res = mkfifo(_fifo_path.c_str(), 0777);
    if (res != 0)
    {
      perror("mkfifo");
    }
  }

  bool OpenNameFifo(int mode)
  {
    _fd = open(_fifo_path.c_str(), mode);
    if (_fd < 0)
    {
      cout << _fd << endl;
      return false;
    }

    return true;
  }

public:
  NamePipe(const string &path, int who) // 构造函数,根据身份码来决定谁创建管道
      : _fifo_path(path), _id(who), _fd(Defaultfd)
  {
    if (_id == Creater)
    {
      CreatFifo(); // 创建管道
      cout << "creat a fifo" << endl;
    }
  }

  // 读写操作
  bool OpenForWrite()
  {
    return OpenNameFifo(Write_Mode);
  }

  bool OpenForRead()
  {
    return OpenNameFifo(Read_Mode);
  }

  int ReadNamePipe(string &out)
  { // 向管道中读取数据
    char buffer[BaseSize];
    int n = read(_fd, buffer, sizeof(buffer) - 1);
    if (n > 0)
    {
      buffer[n] = '\0';
      out = buffer;
    }
    return n;
  }

  int WriteNamePipe(string in)
  { // 像管道中写数据
    return write(_fd, in.c_str(), in.size());
  }

  ~NamePipe() // 析构,删除管道文件,关闭文件
  {
    if (_id == Creater)
    {
      int res = unlink(_fifo_path.c_str()); // 删除管道文件
      if (res != 0)
      {
        perror("unlink");
      }
    }
    if (_fd != Defaultfd)
    { // 关闭文件
      close(_fd);
    }
    }
  void Print()
  {
    cout << _fd << " " << _fifo_path << " " << _id << endl;
  }

private:
  int _fd;                 // FIFO描述fu
  const string _fifo_path; // 管道路径
  int _id;                 // 身份码
};

Server.cpp

接收数据,当命名管道中有数据的时候才会读共享内存的数据。

#include "shm.hpp"
#include "NamePipe.hpp"
#include <iostream>

using namespace std;

int main()
{
    // 创建共享内存x
    Shm shm(pathname, proj_id, gCreater);
    char *shmaddr = (char *)shm.GetShmaddr();
    shm.InitShmaddr();
    sleep(3);
    // 创建命名管道
    NamePipe fifo(comm_path, Creater);
    fifo.OpenForRead();

    while (true)
    {
        string message = "";
        fifo.ReadNamePipe(message);
        cout << "shm memory content: " << shmaddr << endl; // 读取共享内存中的数据
    }
    return 0;
}

Client.cpp

发送数据,先用命名管道给读端发送,提示共享内存已经被写入了数据。

#include "shm.hpp"
#include "NamePipe.hpp"
#include <iostream>

using namespace std;

int main()
{
    // 创建共享内存
    Shm shm(pathname, proj_id, gUser);
    // shm.InitShmaddr();
    char *shmaddr = (char *)shm.GetShmaddr();

    sleep(3);
    // 创建命名管道
    NamePipe fifo(comm_path, User);
    fifo.OpenForWrite();

    char ch = 'A';
    while (ch <= 'Z')
    {
        shmaddr[ch - 'A'] = ch;
        string message = "weakup";
        fifo.WriteNamePipe(message);
        cout << "add: " << ch << " to Server" << endl;
        sleep(2);
        ch++;   
    }
    return 0;
}

总结

共享内存是进程通信最快的方法,因为没有额外的拷贝过程,直接对内存进行读取。此外,共享内存并不保证通信的同步,数据在读取之后不会自动清空释放,也就意味着无论何时只要想读就能读。所以很多时候,为了保证数据的同步性,即写入端进程写入数据后才让读端进程读取数据,我们可以让管道做一个提示作用。当有数据要写入的时候,就让管道发出一个提示,读端读到提示之后才从shm中读取数据。否则,读端就会堵塞等待。

此外,根据共享内存的特性,通信双方都可以同时向shm中读或者写数据。这也就意味着共享内存通信的模式是全双工的。

相关文章
|
25天前
|
安全
【进程通信】信号的捕捉原理&&用户态与内核态的区别
【进程通信】信号的捕捉原理&&用户态与内核态的区别
|
9天前
|
存储 安全 Python
进程通信 , 信号量 , 队列 , 管道 , 共享内存
进程通信 , 信号量 , 队列 , 管道 , 共享内存
|
13天前
|
监控 Rust 安全
Rust代码在公司电脑监控软件中的内存安全监控
使用 Rust 语言开发的内存安全监控软件在企业中日益重要,尤其对于高安全稳定性的系统。文中展示了如何用 Rust 监控内存使用:通过获取向量长度和内存大小来防止泄漏和溢出。此外,代码示例还演示了利用 reqwest 库自动将监控数据提交至公司网站进行实时分析,以保证系统的稳定和安全。
58 2
|
17天前
|
消息中间件 Linux C语言
进程通信:管道与队列
进程通信:管道与队列
|
17天前
|
存储 缓存 监控
深度解析操作系统中的核心组件:进程管理与内存优化
【5月更文挑战第29天】 在现代计算技术的心脏,操作系统扮演着至关重要的角色。它不仅管理和控制计算机硬件资源,还为应用程序提供了一个运行环境。本文将深入探讨操作系统中的两个核心组件——进程管理和内存管理,并分析它们对系统性能的影响以及如何通过技术手段实现优化。通过对操作系统内部机制的剖析,我们将揭示这些组件是如何相互作用,以及它们如何共同提升系统的响应速度和稳定性。
|
18天前
|
存储 Java 编译器
Java方法的基本内存原理与代码实例
Java方法的基本内存原理与代码实例
18 0
|
18天前
|
移动开发 监控 Android开发
构建高效Android应用:从内存优化到电池寿命代码之美:从功能实现到艺术创作
【5月更文挑战第28天】 在移动开发领域,特别是针对Android系统,性能优化始终是关键议题之一。本文深入探讨了如何通过细致的内存管理和电池使用策略,提升Android应用的运行效率和用户体验。文章不仅涵盖了现代Android设备上常见的内存泄漏问题,还提出了有效的解决方案,包括代码级优化和使用工具进行诊断。同时,文中也详细阐述了如何通过减少不必要的后台服务、合理管理设备唤醒锁以及优化网络调用等手段延长应用的电池续航时间。这些方法和技术旨在帮助开发者构建更加健壮、高效的Android应用程序。
|
22天前
|
存储 安全 调度
【操作系统】进程控制与进程通信
【操作系统】进程控制与进程通信
28 3
|
24天前
|
消息中间件 存储 安全
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(下)
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(下)
|
24天前
|
消息中间件 算法 Linux
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)

相关实验场景

更多