进程间通信-共享内存(C++)

前面我们已经在进程间通信-共享内存讨论过SytemV APIPOSIX API的共享内存实现,由于是C接口,所以在使用上不是那么方便。这篇文章将使用Boost库来看下共享内存的相关API及使用方法。

Boost.InterprocessBoost的一个进程间通信的类库,我们要用到的共享内存就是它的实现类,它分别有以下几种常用实现:

  • boost::interprocess::shared_memory_object:普通共享内存对象
  • boost::interprocess::windows_shared_memory:Windows平台的共享内存对象
  • boost::interprocess::mamaged_shared_memory:自动托管的共享内存对象

我们还会用到以下几个类进行共享内存的操作:

  • boost::interprocess::mapped_region:映射共享内存到当前进程地址空间的对象
  • boost::interprocess::allocator:共享内存分配器
  • boost::interprocess::named_mutex:命名互斥对象,存储在操作系统
  • boost::interprocess::interprocess_mutex:匿名互拆对象,存储在共享内存
  • boost::interprocess::named_condition:命名条件变量
  • boost::interprocess::interprocess_condition:匿名条件变量
  • boost::interprocess::named_semaphore:命名信号量
  • boost::interprocess::interprocess_semaphore:匿名信号量
  • boost::interprocess::file_lock:文件锁

创建共享内存

先来段代码,使用boost::interprocess::shared_memory_object创建一块共享内存,写入数据后在另一程序中读取出来。

#include 
#include 
#include 

int main(int argc, char** argv) 
{
    boost::interprocess::shared_memory_object shdmem(boost::interprocess::open_or_create,
        "shm_data", boost::interprocess::read_write);
    shdmem.truncate(1024);
    std::cout << shdmem.get_name() << std::endl;
    boost::interprocess::offset_t size;
    if (shdmem.get_size(size)) 
    {
        std::cout << size << std::endl;
    }
    boost::interprocess::mapped_region region(shdmem, boost::interprocess::read_write);
    char *s = static_cast(region.get_address());
    strcpy(s, "Hello, world!");
    boost::interprocess::mapped_region region2(shdmem, boost::interprocess::read_only);
    char *i2 = static_cast(region2.get_address());
    std::cout << i2 << std::endl;
    boost::interprocess::shared_memory_object::remove("shm_data");
    return 0;
}

boost::interprocess::shared_memory_object接受三个参数进行共享内存对象的构造,第一个为创建的标志,boost::interprocess::open_or_create打开或者创建,即如果存在就直接打开,否则就创建。第二个参数为共享内存的名称,第三个为创建或打开时,设置的权限,我们用的boost::interprocess::read_write表示直接拥有读写权限。在这里申请到的共享内存大小默认是为0的,所以我们需要调用truncate方法来设置共享内存大小,这里我们设置成了1024。其实如果熟悉POSIX API的话,这里和shm_openftruncate的操作是一一对应的,它底层调用的就是这些API。通过get_nameget_size我们可以获取到共享内存的名称及大小。

boost::interprocess::mapped_region是把共享内存映射到当前进程地址空间,同时指定对它的操作权限 。所以第一个参数为共享内存,第二个为权限标志,我们用到的read_only其实就是将它映射为只读内存。映射到当前进程地址空间后,通过get_address可以以字节的方式访问和读写共享内存,例子里的第一次映射是往共享内存写入了"Hello, world!",第二次映射是读取共享内存里的内容。boost::interprocess::mapped_region会自动调用到mmapmunmap来映射及解除映射共享内存。

boost::interprocess::shared_memory_object::remove是用来移除共享内存的,只需提供共享内存的名称即可。它对应的其实就是shm_unlink操作。

进程间通信

还是读写的例子,不同的是,这次我们将用到C++的面向对象特性来使用共享内存。

比如这次我们要在共享内存中放一个字符串,这个字符串在写进程中通过标准输入写入,在读进程中通过标准输出读出。

示例代码

  • 读程序

    shmread.cpp

    #include 
    #include "shmutils.h"
    
    int main()
    {
      using namespace boost::interprocess;
    
      shm_remove remover;
      mutex_remove mutex_remover;
      condition_remove condition_remover;
      typedef allocator CharAllocator;
      typedef basic_string, CharAllocator> string;
      bool running = true;
      named_mutex named_mtx(open_or_create, MUTEX_KEY);
      named_condition named_cnd(open_or_create, CONDITION_KEY);
      scoped_lock lock(named_mtx);
      managed_shared_memory segment(open_or_create, SHM_KEY, SHM_SIZE);
      while (running)
      {
          std::cout << "Reader notify." << std::endl;
          named_cnd.notify_all();
          std::cout << "Reader wait ..." << std::endl;
          named_cnd.wait(lock);
          // read share memory    
          std::cout << "==========" << std::endl;
          std::pair p = segment.find("Hello");
          if (p.first != NULL)
          {
              std::cout << "read string: [" << *p.first << "]" << std::endl;
              if (*p.first == "end") 
              {
                  running = false;
              }
          }
      }
      named_cnd.notify_all();
      return 0;
    }
    

  • 写程序

    shmwrite.cpp

    #include 
    #include "shmutils.h"
    
    int main()
    {
      using namespace boost::interprocess;
    
      shm_remove remover;
      mutex_remove mutex_remover;
      condition_remove condition_remover;
      typedef allocator CharAllocator;
      typedef basic_string, CharAllocator> string;
      bool running = true;
      named_mutex named_mtx(open_or_create, MUTEX_KEY);
      named_condition named_cnd(open_or_create, CONDITION_KEY);
      scoped_lock lock(named_mtx);
      managed_shared_memory segment(open_or_create, SHM_KEY, SHM_SIZE);
      while (running)
      {
          // write share memory
          std::cout << "----------" << std::endl;
          string *s = segment.find_or_construct("Hello")("", segment.get_segment_manager());
          s->clear();
          std::cout << "wait input: " << std::endl;
          std::cin >> *s;
          std::cout << "write string: [" << *s << "]" << std::endl;
          if (*s == "end")
          {
              running = false;
          }
          std::cout << "Writer notify." << std::endl;
          named_cnd.notify_all();
          std::cout << "Writer wait ..." << std::endl;
          named_cnd.wait(lock);
      }
      named_cnd.notify_all();
      return 0;
    }
    

  • 执行结果

    alpine:~$ ./shmread &
    alpine:~$ Reader notify.
    Reader wait ...
    
    alpine:~$ ./shmwrite
    ----------
    wait input: 
    HelloABC   
    write string: [HelloABC]
    Writer notify.
    Writer wait ...
    ==========
    read string: [HelloABC]
    Reader notify.
    Reader wait ...
    ----------
    wait input: 
    end
    write string: [end]
    Writer notify.
    Writer wait ...
    ==========
    read string: [end]
    [1]+  Done                       ./shmread
    alpine:~$ 
    

完整代码可从我的GitHub获取。

这里我们有使用到boost::interprocess::managed_shared_memory, boost::interprocess::allocator, boost::interprocess::managed_shared_memory::segment_manager, boost::interprocess::named_mutex, boost::interprocess::named_condition, boost::interprocess::scoped_lock这些类,我们详细分析下它们分别可以用来做什么。

相关API

  • boost::interprocess::managed_shared_memory

    managed_shared_memory即托管共享内存,相比shared_memory_object更符合我们C++的风格,因为前者只能按字节去读写,而它可以直接写入或读取对象,并且隐藏了相关的实现细节。我们可以很灵活地通过key和类型往共享内存里写入或读取数据,上面的例子就是往共享内存里读写string。当然,这个string并非标准库里的string,而是boost::container里实现的string,相比STL的要更灵活,可以指定更灵活的allocator。托管共享内存需要使用contruct()函数,接收一个数据类型为模板参数,以及一个名称用来表示在共享内存中创建的对象。我们例子中用的是自定义string类型,keyHello,初始值为空。在读进程里,使用find方法来查找对应的key来进行操作。find会返回一个std::pair对象,first是指向对象的指针(没找到时为空),second为对象个数。由于我们没有构造为数组,所以second只会是0或1。

  • boost::interprocess::allocator

    可以使用共享内存进行内存分配的分配器,它接收一个SegmentManager的模板参数,并且在构造函数中需传入对应的SegmentManager,这里我们就直接使用了managed_shared_memory对象提供的segment_manager获取到该对象。

  • boost::interprocess::managed_shared_memory::segment_manager

    托管内存段管理器,用于管理位于托管共享内存之内的内存。我们在查找及创建共享内存对象时,都需要提供对应的segment_manager来让应用程序知道哪个段管理器应该被访问。

  • boost::interprocess::named_mutex

    命名互斥对象,其实与线程里的互斥对象类似,只不过是用在进程中。命名互斥对象由系统管理,直接通过名字可以查找到相应的对象,与interprocess_mutex不同的是后者需要通过共享内存来进行访问。它有lock(), unlock(), try_lock()等方法。

  • boost::interprocess::named_condition

    命名条件对象,与线程里的条件对象类似,也是用在进程中。由系统管理,直接通过名字可以查找到相应的对象。有wait(), notify(), notify_all()等方法。

  • boost::interprocess::scoped_lock

    自释放锁,用于包装互斥锁一类的锁在析构时进行自动释放管理。

总结

使用boost::interprocess的共享内存实现时,可以很方便地使用到面向对象的一些高级特征而不用去关心底层实现。它其实是基于POSIX API来实现的共享内存管理,通过更高维度的抽象带来了更便捷的使用方式和灵活度。

原文链接:https://wangqun.info/2017/08/08/%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1-%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%EF%BC%88C++%EF%BC%89/

你可能感兴趣的:(进程间通信-共享内存(C++))