【问题标题】:Detaching from shared memory before removing it在删除之前从共享内存中分离
【发布时间】:2025-12-07 05:05:02
【问题描述】:

当我有多个进程在使用共享内存时,我将它们全部分离,但只有一个。

  1. 在使用shmctl()(与该进程)删除共享内存之前分离最后一个进程是否有意义?
  2. 如果没有意义,是否可以在与共享内存分离后将其删除?

【问题讨论】:

  • 我实际上并不知道这一点,而且 POSIX 规范似乎并没有说这两种方式,但我会期望这种方式工作是共享内存段上的shmctl(..., IPC_RMID, ...) 对任何附加它的进程没有任何影响。该段应保持分配状态并可供这些进程访问,直到它们全部退出或分离它。如果我是对的,那么您的问题的答案是“没关系,您可以按任意顺序进行操作,整体效果都是一样的。”
  • (请注意,shm_unlink 被明确指定为我所说的我希望shmctl(..., IPC_RMID, ...) 的行为方式——这表明这是 Unix 系统接口的正常情况,并且它可能不是 SysV IPC 的行为总是有点怪异。)
  • 有争议的重复:*.com/questions/13939349/…(但答案引用了 Linux 联机帮助页而不是 POSIX,所以我认为我们还没有关于可移植代码的答案。)

标签: c linux sysv-ipc


【解决方案1】:

shmctl() 的手动条目没有说明“最多有一个进程使用它”或“没有附加进程”。但是,系统不能完全中断已连接到共享内存段的正在运行的进程。

你只需要shmget()返回的shmid(共享内存段ID);您不需要附加共享内存(因此您可能已经运行 shmdt())。

在 Mac (macOS Sierra 10.12.3, GCC 6.3.0) 上使用来自上一个问题 (Making a shared data structure in C) 的代码进行测试,我添加了一个选项 -t time 以使进程休眠一段指定的时间。然后我创建了一个共享内存段并让进程保持打开状态。我使用ipcs -m 来查看该段是否存在。然后我删除了该段;它是成功的。通过ipcs -m 重新检查,该段已从共享更改为IPC_PRIVATE。当休眠进程完成时,共享内存段被自动删除(就像私有段一样)。

$  shm-master -f shm-master -s 1024 -x -t 120 &
[1] 14392
$ ID: 0, File: shm-master
Key: 0x00041BF7
ShmID: 1441795
Shared memory allocated at 0x10F2B4000
Sleeping for 120 seconds

$ ipcs -m
IPC status from <running system> as of Wed Feb 15 11:56:37 PST 2017
T     ID     KEY        MODE       OWNER    GROUP
Shared Memory:
m  65536 0x00fedc64 --rw-rw-rw-     root    wheel
m  65537 0x0052e2c1 --rw------- postgres   daemon
m  65538 0x52042973 --rw-------     root    wheel
m 1441795 0x00041bf7 --rw------- jleffler    staff

$ shm-master -f shm-master -s 1024 -d
ID: 0, File: shm-master
Key: 0x00041BF7
ShmID: 1441795
Shared memory removed
$ ipcs -m
IPC status from <running system> as of Wed Feb 15 11:56:47 PST 2017
T     ID     KEY        MODE       OWNER    GROUP
Shared Memory:
m  65536 0x00fedc64 --rw-rw-rw-     root    wheel
m  65537 0x0052e2c1 --rw------- postgres   daemon
m  65538 0x52042973 --rw-------     root    wheel
m 1441795 0x00000000 --rw------- jleffler    staff

$ sleep 120; ipcs -m
Detached from shared memory
[1]+  Done                    shm-master -f shm-master -s 1024 -x -t 120
IPC status from <running system> as of Wed Feb 15 11:58:57 PST 2017
T     ID     KEY        MODE       OWNER    GROUP
Shared Memory:
m  65536 0x00fedc64 --rw-rw-rw-     root    wheel
m  65537 0x0052e2c1 --rw------- postgres   daemon
m  65538 0x52042973 --rw-------     root    wheel

$

这是否是在其他系统上发生的情况,我不确定,但它看起来是合理的行为。可能会发生类似的事情。

顺便说一句,运行一个进程来创建段和第二个进程只是附加到它,都在睡眠模式下运行,并没有真正改变观察到的结果。共享内存段键变为 0,进程不受影响,在都完成后删除段。

【讨论】:

  • IPC_RMID 的行为直接类似于unlink(2) 的行为。如果您这样做:fd = open(file,...); unlink(file);fd 将可供进程访问,但 file 将被删除。 inode 将一直保留到进程关闭fd。如果另一个进程打开file,它将获得一个不同的 inode。 (即)shm 段旨在在这方面模仿文件。也就是说,inode 和 shmseg 都有 refcounts,当它们被创建时,refcount 是 1
  • @CraigEstey:这是一个很好的类比——这也是为什么我对其他系统的行为有一定的信心的原因。我没有四处检查过一堆系统,而且我不确定其他系统是否一定会像在 macOS 上那样将密钥更改为 0,所以我对我所声称的内容持谨慎态度。跨度>
  • 我很确定 macOS 的行为是半通用的。当您执行unlink 时,目录条目被删除,inode 引用计数减少,但打开的描述符保留 inode 引用计数&gt;0。使用close(fd),inode 引用计数会递减。在这里,inode 是一个“占位符”。做等价的唯一方法。 shmseg 的行为(因为它只有一个“inode”并且没有 dir 条目)是将密钥更改为 IPC_PRIVATE(即 0)以获得“占位符”行为。这允许第二个进程立即创建一个具有相同密钥的新段,但第一个进程继续使用旧副本。
  • 您声称IPC_PRIVATE 段始终会被自动删除。 shmget() 的手册页似乎没有提到这一点。你有参考来证实你的主张吗?
  • @Bernard — 当最后一个引用特定 IPC_PRIVATE 共享内存段的进程退出时,系统将清理它,因为其他进程无法访问它。可以再次附加常规的非私有段。系统不会把资源浪费在不能用的东西上。
【解决方案2】:

shmctl(,... IPC_RMID, ...) 的行为没有被SingleUnix 明确定义:

从系统中移除shmid指定的共享内存标识符,并销毁共享内存段和与之关联的shmid_ds数据结构。

有人可能会争辩说,IPC_RMID 应该立即使对共享内存段的所有引用无效。在实践中,大多数实现将删除延迟到最后一个进程分离,这更符合人们期望的典型语义,例如从一个先进先出。该名称已被删除,但真正的内核对象只有在所有引用都被释放后才会消失。

在Linux上,销毁是惰性的,段只会在last process detaches被销毁一次:

该段只有在最后一个进程将其分离后才会真正被销毁(即,当关联结构 shmid_ds 的 shm_nattch 成员为零时)。

FreeBSD 上存在相同的行为:

直到所有附加该段的进程都退出后,删除才会生效。

Solaris 有它similarly,但向后解释:

如果调用IPC_RMID时该段没有附加到任何进程,它将立即销毁。

【讨论】: