【问题标题】:IPC shared memory vs posix shared memoryIPC 共享内存与 posix 共享内存
【发布时间】:2014-10-08 03:55:37
【问题描述】:

我目前正在实现一个使用 posix 共享内存的小型 C 函数(shm_open()、ftruncate()、mmap() 和 shm_unlink()。

我面临的问题是我的应用程序正常存在,我的清理例程调用shm_unlink()。

但是,如果我的进程得到kill -9,我将面临 shm 段仍然存在的问题。我的应用程序使用fork(),甚至可以在多个实例中启动。那么,如果在启动过程中我检测到有一个共享内存段,我如何确定它是否是崩溃的遗留物,以便我可以重置它或其他进程可能仍在使用它?

在 SystemV IPC 共享内存集功能中,一旦我执行了 shmget() 和后续的 shmat(),我就有了 shm_nattch。

有没有和posix共享内存类似的东西?

【问题讨论】:

  • 也许你可以用处理程序拦截信号并优雅退出?
  • 某种心跳系统是我唯一能想到的。也许在 shm 段中存储一个活动 PID 列表,并轮询它们以确保它们还活着?
  • @webuster 你不能拦截SIGKILL,这是kill -9 提出的
  • 嗯...看来System V IPC Shared Memory设计得更好?

标签: c++ c posix ipc shared-memory


【解决方案1】:

POSIX 共享内存实际上是映射内存的一种变体。主要区别在于使用 shm_open() 打开共享内存对象(而不是调用 open())和使用 shm_unlink() 关闭和删除对象(而不是调用不删除对象的 close())。 shm_open() 中的选项比 open() 中提供的选项数量要少很多。

IPC 共享内存是一种在程序之间传递数据的有效方式。一个程序将创建一个内存部分,其他进程(如果允许)可以访问。

恕我直言,它们之间的差异并不大,甚至不是很大,因为 POSIX 共享内存只是共享内存概念的后期和更标准的实现。 kill -9 问题确实是个问题。您不能设置任何处理程序,甚至不调用 atexit。但是您可以为 SIGTERM 设置处理程序并使用命令终止您的进程:

kill -SIGTERM <pid>

atexit 和信号处理程序的示例代码:

#include <signal.h>
#include <unistd.h>
#include <cerrno>
#include <system_error>
#include <iostream>

static sigset_t theMask;

static void
signalWrapper(
    int         theSignalNumber,
    siginfo_t*  theSignalDescription,
    void*       theUserContext)
{
  if (theSignalNumber == SIGTERM)
  {
    std::cerr << "Clear shared memory and exit" << std::endl;
    exit(1);
  }

  // Reinstall handler (usefull for other signals)
  struct ::sigaction sa;
  sa.sa_sigaction = &signalWrapper;
  sa.sa_mask = theMask;
  sa.sa_flags = SA_SIGINFO;

  try
  {
    if (::sigaction(theSignalNumber, &sa, NULL) == -1)
      throw std::error_code(errno, std::system_category());
  }
  catch (const std::error_code& ec)
  {
    std::cerr << ec << std::endl;
  }
}

void
setupSignalHandlers()
{
  struct ::sigaction sa;

  // Prepare mask
  sigemptyset(&theMask);
  sigaddset(&theMask, SIGTERM);
  // Add some more if you need it to process

  sa.sa_mask      = theMask;
  sa.sa_flags     = SA_SIGINFO;
  sa.sa_sigaction = &signalWrapper;

  // Perform setup
  try
  {
    if (::sigaction(SIGTERM, &sa, NULL) == -1)
      throw std::error_code(errno, std::system_category());
  }
  catch (const std::error_code& ec)
  {
    std::cerr << ec << std::endl;
  }
}

void
bye()
{
  std::cout << "Bye!" << std::endl;
}

int
main()
{
  std::cout << "Set handler!" << std::endl;
  setupSignalHandlers();
  if (std::atexit(bye))
  {
    std::cerr << "Failed to register atexit" << std::endl;
    return 2;
  }

  std::cout << "Emit SIGTERM signals" << std::endl;
  kill(getpid(), SIGTERM);
  sleep(100);

  return 0;
}

另一个有趣的方法是在 shm_open() 之后使用 shm_unlink() 并使用 O_CLOEXEC。我写了一个小代码示例来说明 unlink 和 POSIX 共享内存重新打开的使用:

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <cerrno>
#include <cstring>

#include <system_error>
#include <iostream>

// Simple ShMem with RAII
struct ShMem
{
  ShMem(
    const std::string& theName)
  : _handle(-1),
    _mode(S_IRUSR | S_IWUSR),
    _name(theName)
  {
    // Here we try to create the object with exclusive right to it
    // If the object exists - it must fail.
    // File opened as 0600. The open mode might be set with
    // class enum and additional paramter to constructor
    if ((_handle = shm_open(_name.c_str(), O_CREAT | O_RDWR | O_EXCL, _mode)) < 0)
    {
      std::cerr << strerror(errno) << std::endl;

      std::cout << "File " << _name << "exists. Try to reopen it!" << std::endl;
      if ((_handle = shm_open(_name.c_str(), O_RDWR | O_TRUNC, _mode)) < 0)
      {
        std::cerr << strerror(errno) << std::endl;
        throw std::error_code(errno, std::system_category());
      }
      else
        std::cout << _name << " reopened OK";
    }
    else
      std::cout << _name << " created OK";

    // Unlink but keep descriptor
    shm_unlink(_name.c_str());
  }

 ~ShMem()
  {
  }

  const int
  handle()
  {
    return _handle;
  }

private:

  int         _handle;
  mode_t      _mode;
  std::string _name;
};

使用 O_CLOEXEC 您可以完全删除 shm_unlink() 并查看 kill -9 会发生什么。

【讨论】:

  • 谢谢,我正在试一试。除此之外,我还使用 posix 信号量 (sem_open()) 来保护我的共享内存段。所以我想对于信号量我应该采用同样的方法。
  • 我发现一旦你做了一个 shm_unlink() 一个并行进程调用 shm_open 与 O_CREAT | O_EXCL 不会失败,但会创建一个全新的 shm 段,因此我写入 shm 的信息不会跨进程传输。在我执行 shm_unlink 之后,所有非 O_CREAT shm_open() 调用都将失败。
  • 嗯。好的,您可以尝试将 O_CLOEXEC 与您的创建标志结合起来,看看会发生什么。然后 shm_unlink() 必须进入析构函数。
  • @Tanuki 我没有在 O_CLOEXEC 上关注你。我在代码中所做的是 shm_open()、ftruncate() 和 mmap()。在 mmap() 之后,我将关闭我的文件描述符。所以你的意思是我应该用 O_CLOEXEC 做一个 fcntl() 吗?顺便说一句,我把 shm_unlink() 放到我的析构函数中,在 SIGINT 和 SIGTERM 上调用了析构函数,现在剩下的唯一问题是崩溃或 kill -9。
  • 我尝试了 shm_open(_name.c_str(), O_CREAT | O_RDWR | O_EXCL | O_CLOEXEC, _mode),它在我的系统上运行正常。这意味着您可以在创建时启用 O_CLOEXEC。
猜你喜欢
  • 2012-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
  • 2011-04-01
  • 2015-10-10
  • 1970-01-01
  • 2011-01-16
相关资源
最近更新 更多