【问题标题】:Remove posix shared memory when not in use?不使用时删除posix共享内存?
【发布时间】:2012-11-02 21:35:16
【问题描述】:

是否有任何方法,无论是否特定于 linux,在没有进程使用它们时删除 posix 共享内存段(通过shm_open() 获得)。即对它们进行引用计数,并在引用变为 0 时让系统删除它们

几点说明:

  • 如果程序崩溃,则无法建立 atexit 处理程序来删除它们。

  • 目前,linux特有的方式,我在段名中嵌入了pid,并尝试通过在外部程序中走/dev/shm来找到未使用的段。这样做的缺点是必须定期在外部以相当骇人的方式清理它们。

  • 由于程序可以运行多个副本,因此为程序在启动时重用的段使用明确定义的名称是不可行的。

【问题讨论】:

  • 您是在问是否有系统库方法来解决这个问题,而不是手动完成?
  • 您可以使用 gdb 来调试您的应用程序,使其不会崩溃?这缓解了崩溃的应用程序无法自行清理的问题......

标签: c linux shared-memory


【解决方案1】:

如果您的程序执行中有一个众所周知的点,即所有需要打开共享内存段的进程都已经这样做了,您可以安全地取消链接。取消链接会将对象从全局命名空间中移除,但只要至少有一个进程保持其文件描述符处于打开状态,它就会一直存在。如果在那之后发生崩溃,文件描述符会自动关闭,并且引用计数会减少。一旦没有打开未链接共享内存块的描述符,它就会被删除。

这在以下场景中很有用:进程创建一个共享内存块,取消链接,然后分叉。子进程继承文件描述符,可以使用共享内存块与父进程通信。一旦两个进程都终止,块会在两个文件描述符都关闭时自动删除。

在取消链接时,共享内存块无法供其他进程打开。同时,如果使用shm_open()与未链接块同名,则会创建一个新的完全不同的共享内存块。

【讨论】:

    【解决方案2】:

    不——至少在 Linux 上,内核不包含任何可以做到这一点的东西。某些应用程序需要在某个时候调用 shm_unlink() 以摆脱共享内存段。

    【讨论】:

      【解决方案3】:

      我找到了一种使用系统命令和 Linux 命令“fuser”的方法,它允许列出打开文件的进程。这样,你可以检查共享内存文件(位于/dev/shm")是否还在使用,如果没有就删除它。注意检查/删除/创建操作必须包含在进程间临界区中使用命名互斥锁或命名信号量或文件锁。

              std::string shm_file = "/dev/shm/" + service_name + "Shm";
              std::string cmd_line = "if [ -f " + shm_file + " ] ; then if ! fuser -s " + shm_file + " ; then rm -f " + shm_file + " ; else exit 2 ; fi else exit 3 ; fi";
              int res = system(cmd_line.c_str());
              switch (WEXITSTATUS(res)) {
              case 0: _logger.warning ("The shared memory file " + shm_file + " was found orphan and is deleted");         break;
              case 1: _logger.critical("The shared memory file " + shm_file + " was found orphan and cannot be deleted");  break;
              case 2: _logger.trace   ("The shared memory file " + shm_file + " is linked to alive processes");            break;
              case 3: _logger.trace   ("The shared memory file " + shm_file + " is not found");                            break;
              }
      

      【讨论】:

        【解决方案4】:

        对于使用 sysV API 创建的共享内存,可能会有这样的行为。仅在 Linux 上。它不是 POSIX 共享内存,但可能对您有用。

        The Linux Programming Interface 一书中,shmctl() 的一个可能参数是这样描述的。

        IPC_RMID 标记共享内存段及其关联的 shmid_ds 用于删除的数据结构。如果当前没有进程拥有 附加段,立即删除;否则,该段为 在所有进程都与它分离后删除(即,当 shmid_ds 数据结构中 shm_nattch 字段的值下降到 0)。在某些应用程序中,我们可以确保共享内存 通过标记在应用程序终止时整齐地清除段 在所有进程都将其附加到后立即删除它 他们的虚拟地址空间与 shmat()。这类似于 打开文件后取消链接。在 Linux 上,如果一个共享段 已使用 IPC_RMID 标记为删除,但尚未 删除,因为某些进程仍然附加它,然后它是 另一个进程可以附加该段。然而,这 行为不可移植:大多数 UNIX 实现阻止新的 附加到标记为删除的段。 (SUSv3 对什么保持沉默 在这种情况下应该会发生这种情况。)一些 Linux 应用程序具有 来依赖这种行为,这就是为什么 Linux 没有 更改为匹配其他 UNIX 实现。

        【讨论】:

        • 但 System V IPC 现在已被弃用。不建议使用这种机制编写新程序。
        【解决方案5】:

        让我们假设最复杂的情​​况:

        • 您有多个进程通过共享内存进行通信
        • 他们可以随时开始和结束,甚至可以多次。这意味着没有主进程,也没有可以初始化共享内存的专用“第一个”进程。
        • 这意味着,例如,您无法安全地取消链接共享内存,因此 Sergey'sHristo's 的答案都不起作用。

        我看到了两种可能的解决方案,并欢迎对它们提供反馈,因为互联网对这个问题非常沉默:

        1. 将写入共享内存的最后一个进程的 pid(或者更具体的进程标识符,如果有的话)存储在共享内存中作为锁。然后你可以做某事。像下面的伪代码:

           int* pshmem = open shared memory()
          
           while(true) 
               nPid = atomic_read(pshmem)
               if nPid = 0 
                  // your shared memory is in a valid state
                  break
               else 
                  // process nPid holds a lock to your shared memory
                  // or it might have crashed while holding the lock
                  if process nPid still exists 
                    // shared memory is valid
                    break
                  else 
                    // shared memory is corrupt
                    // try acquire lock 
                    if atomic_compare_exchange(pshmem, nPid, my pid) 
                       // we have the lock
                       reinitialize shared memory
                       atomic_write(pshem, 0) // release lock
                    else 
                       // somebody else got the lock in the meantime
                       // continue loop
          

          这验证了最后一位作家在写作时没有死。共享内存的持续时间仍然比您的任何进程都长。

        2. 使用读/写文件锁来确定是否有任何进程是第一个打开共享内存对象的进程。然后第一个进程可能会重新初始化共享内存:

           // try to get exclusive lock on lockfile
           int fd = open(lockfile, O_RDONLY | O_CREAT | O_EXLOCK | O_NONBLOCK, ...)
           if fd == -1
               // didn't work, somebody else has it and is initializing shared memory
               // acquire shared lock and wait for it
               fd = open(lockfile, O_RDONLY | O_SHLOCK)
               // open shared memory
           else 
               // we are the first
               // delete shared memory object
               // possibly delete named mutex/semaphore as well
          
               // create shared memory object (& semaphore)
               // degrade exclusive lock to a shared lock
               flock(fd, LOCK_SH)
          

          文件锁似乎是 POSIX 系统上唯一(?)在进程终止时自动清除的机制。不幸的是,the list of caveats to use them is very, very long。该算法假定至少在本地机器上的底层文件系统支持flock。该算法不关心锁是否对 NFS 文件系统上的其他进程实际可见。它们只需要对访问共享内存对象的所有进程可见。

          This solution has been implemented on top of boost.interprocess.

        【讨论】:

        • 共享文件锁很有意思,谢谢Sebastian!一个问题。该示例假定该文件不存在,但情况可能并非如此。然后会发生什么?
        • open(lockfile, O_RDONLY | O_CREAT | O_EXLOCK | O_NONBLOCK, ...) 将在文件不存在时创建文件,但在文件已存在时将其打开。
        • 是的,但我的意思是,如果文件已经存在,那么锁的所有者是谁?那不是意味着每个进程都采用“else-statement”吗?
        • 我仍然不确定我是否理解。如果文件存在与否在这里没有区别。唯一重要的是,如果有人打开它并获得了锁。我尝试打开文件并同时获取排他锁(使用 EX_LOCK 打开)。只有当文件尚未打开时才会成功。只有第一个进程负责初始化锁才会成功。然后它将锁切换到共享锁,所有其他进程都可以在 if-case 中继续进行。
        • 非常感谢@Sebastian!我在上一个问题中搞砸了一点(我的意思是 if-case),但您的回复仍然让我更好地理解它,再次感谢您的澄清!
        【解决方案6】:

        您不能只使用全局计数信号量来引用计数吗?包装附加和分离调用,以便信号量在附加到内存时递增,在分离时递减。当分离将信号量减少到零时释放段。

        【讨论】:

        • 这与在 atexit 处理程序中删除它具有相同的含义(atexit 对于我的特定用途来说很好,因为只有进程写入共享内存,并且该进程可以删除它)。如果例如,这不会处理段。进程崩溃。
        • 您可以捕获崩溃信号并减少信号处理程序中的引用计数吗?
        【解决方案7】:

        不确定,以下是否可行或可行。但我的尝试。

        为什么不执行帮助程序,每次程序崩溃时都会执行该程序。

        即:

        /proc/sys/kernel/core_pattern  to  /path/to/Myprogram %p
        

        Myprogram 在进程崩溃时执行,或许您可以进一步探索。

        man 5 core.  for more information. 
        

        希望这在一定程度上有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-10-08
          • 1970-01-01
          • 2013-05-21
          • 2020-10-18
          • 2011-04-01
          • 2015-10-10
          • 1970-01-01
          • 2013-03-14
          相关资源
          最近更新 更多