不使用时删除posix共享内存?

时间:2012-11-14 11:30:08

标签: c linux shared-memory

有没有任何方法,特定于Linux,在没有进程使用时删除posix共享内存段(使用shm_open()获取)。即,将它们引用计数并让系统在引用变为0时删除它们

一些注意事项:

  • 如果程序崩溃,建立一个删除它们的atexit处理程序不起作用。

  • 目前,以linux为特定的方式,我将pid嵌入到段名中,并尝试通过在外部程序中遍历/ dev / shm来查找未使用的段。其缺点是不得不以相当狡猾的方式定期对外进行清理。

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

8 个答案:

答案 0 :(得分:5)

否 - 至少在Linux上,内核不包含任何可以执行此操作的内容。某些应用程序可以在某个时刻调用shm_unlink()来摆脱共享内存段。

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:2)

我找到了一种使用系统命令和Linux命令“fuser”的方法,它允许列出打开文件的进程。这样,您可以检查共享内存文件(位于/ dev / shm中)是否仍在使用中,如果没有,则将其删除。请注意,check / delete / create的操作必须包含在进程间关键部分中使用命名的互斥锁或命名信号量或文件锁。

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

答案 3 :(得分:2)

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

  • 您有多个进程通过共享内存进行通信
  • 他们可以随时开始和结束,甚至多次。这意味着没有主进程,也没有可以初始化共享内存的专用“第一”进程。
  • 这意味着,例如,没有必要可以安全地取消链接共享内存,因此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文件系统上的其他进程可见。它们只需对访问共享内存对象的所有进程可见。

答案 4 :(得分:1)

对于使用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实现匹配。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

不确定,如果以下工作或可行。但我试试。

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

即:

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

当进程崩溃时执行Myprogram,可能您可以进一步探索。

man 5 core.  for more information. 

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

答案 7 :(得分:0)

对于我们的用例,我们有一组共享某些内存段的进程,这些进程由进程组负责人创建。如果进程组中的进程停止,则应从磁盘中删除共享内存段,但不要早于此。

我现在的方法很复杂并且相对昂贵,但是至少可以确保清除共享内存段,即使在信号通知了进程组中的每个进程的情况下。

方法是从创建共享内存段的进程中fork()开始,然后在父进程创建的某些管道上执行阻塞read()。如果父进程停止(无论是否正常)停止,分叉进程中对read()的调用将返回,指示该进程可以清除共享内存段。

负责清理的分叉进程应将自己置于与父进程不同的进程组中。不过,SIGKILL仍可能是分叉的负责清理的过程。

今天在寻找更好的解决方案时再三考虑,实际上我想知道是否真的应该担心将这些文件留在后面。假设内存中不包含任何敏感信息,并且使用正确的权限创建了文件,并且每个用户都是唯一的(以某种方式包含用户ID)。