3

When I have several processes going on using a shared memory, and I detach all of them but one.

  1. Does it make sense to detach the last process before removing the shared memory with shmctl() (with that process)?
  2. If it doesn't make sense, is it possible to remove the shared memory after detaching from it?
zwol
  • 135,547
  • 38
  • 252
  • 361
Raz Moshe
  • 59
  • 1
  • 6
  • 1
    I don't actually _know_ this, and the POSIX spec doesn't appear to say either way, but the way I would _expect_ this to work is that `shmctl(..., IPC_RMID, ...)` on a shared memory segment has _no effect whatsoever_ on any processes that have it attached. The segment should remain allocated and accessible to those processes, until they all exit or detach it. If I'm right, the answer to your question is "It doesn't matter, you can do the operations in either order and the overall effect will be the same." – zwol Feb 15 '17 at 19:45
  • (Note that [shm_unlink](http://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html) is explicitly specified to behave the way I said I would expect `shmctl(..., IPC_RMID, ...)` to behave -- which demonstrates both that that's the normal thing for Unix system interfaces, and that it might _not_ be how SysV IPC, which has always been a bit weird, behaves.) – zwol Feb 15 '17 at 19:47
  • arguable duplicate: https://stackoverflow.com/questions/13939349/cleanly-destroy-a-system-v-shared-memory-segment?rq=1 (but the answers cite the Linux manpages rather than POSIX, so I don't think we have an answer for portable code yet.) – zwol Feb 15 '17 at 19:50
  • An annoying quirk which is not mentioned in the documentation is that `shmctl(..., IPC_RMID, ...)` on Linux seems to also detach the current process. – Jonathan Woollett-light Sep 11 '22 at 20:37

2 Answers2

2

The manual entry for shmctl() doesn't say anything about 'at most one process using it' or 'no processes attached to it'. However, the system can't completely disrupt already running processes that are attached to the shared memory segment.

You only need the shmid (shared memory segment ID) returned by shmget(); you don't need the shared memory to be attached (so you could already have run shmdt()).

Testing on a Mac (macOS Sierra 10.12.3, GCC 6.3.0) with code derived from a previous question (Making a shared data structure in C), I added an option -t time to make the process sleep for a specifiable period. Then I created a shared memory segment and left the process holding it open. I used ipcs -m to see that the segment existed. Then I deleted the segment; it was successful. Rechecking with ipcs -m, the segment had changed from shared to IPC_PRIVATE. When the sleeping process completed, the shared memory segment was automatically deleted (as private segments always are).

$  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

$

Whether this is what happens on other systems, I'm not sure, but it looks like plausible behaviour. Something similar is likely to occur.

Incidentally, running one process to create the segment and a second to just attach to it, both running in sleep mode, didn't really change the observed results. The shared memory segment key changed to 0, the processes were unaffected, and the segment was deleted after both had completed.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • The behavior of `IPC_RMID` is directly analogous to the behavior of `unlink(2)`. If you do: `fd = open(file,...); unlink(file);`, `fd` will be accessible to the process, but `file` will be removed. The inode will remain until the process closes `fd`. If another process opens `file`, it will get a _different_ inode. (i.e.) shm segments are intended to mimic files in this regard. That is, both inodes and shmseg's have refcounts, and when they are created, the refcount is 1 – Craig Estey Feb 15 '17 at 20:07
  • @CraigEstey: That's a good analogy — it's also why I'm moderately confident that other systems will behave similarly. I haven't gone around and checked a bunch of systems, and I'm not sure that other systems will necessarily change the key to 0 as it did on macOS, so I'm being cautious in what I'm claiming. – Jonathan Leffler Feb 15 '17 at 20:12
  • I'm pretty sure the macOS behavior is semi-universal. When you do `unlink`, the directory entry is removed, the inode refcount is decremented, but the open descriptor(s) keep the inode refcount `>0`. With `close(fd)`, the inode refcount is decremented. Here, the inode is a "place holder". The only way to do equiv. behavior for a shmseg (because it only has an "inode" and no dir entry) is to change the key to `IPC_PRIVATE` (i.e. 0) to get the "place holder" behavior. This allows 2nd process to create a fresh segment with same key immediately, but 1st process(es) continue to use the old copy. – Craig Estey Feb 15 '17 at 20:36
  • You claim that `IPC_PRIVATE` segments are always automatically deleted. The manpage for [`shmget()`](http://man7.org/linux/man-pages/man2/shmget.2.html) does not seem to mention this. Do you have a reference to substantiate your claim? – Bernard Sep 25 '18 at 04:23
  • @Bernard — when the last process with a reference to a specific IPC_PRIVATE shared memory segment exits, the system will clean it up as there’s no way for another process to get access to it. The regular, non-private segments can be attached to again. The system won’t waste resources on what cannot be used. – Jonathan Leffler Sep 25 '18 at 04:44
  • @JonathanLeffler It makes sense not to waste resources storing something that is no longer referenceable. However, it doesn't really guarantee that it's what happens in practice. Also various examples floating on the Internet do explicitly delete the shared memory created with `IPC_PRIVATE`, e.g. [this one](https://stackoverflow.com/a/36244366/1021959) which is the first example I can find now. Perhaps I can only get an answer for the particular OS I'm using by looking into the kernel. – Bernard Sep 25 '18 at 05:15
  • @CraigEstey some OSes (such as Linux but not macOS) allow you to attach to a removed segment so long as there is a process attached keeping it alive (see https://stackoverflow.com/a/42479369/2732969 ) so there are OS specific corner cases where the semantics don't match those that you described for a file... – Anon Jun 10 '19 at 07:51
1

The behavior of shmctl(,... IPC_RMID, ...) is not explicitly defined by SingleUnix:

Remove the shared memory identifier specified by shmid from the system and destroy the shared memory segment and shmid_ds data structure associated with it.

One could argue, that IPC_RMID should invalidate all references to a shared memory segment immediately. In practice, the removal is delayed until the last process detaches by most implementations, which is more in line with the typical semantics one would expect e.g. from a FIFO. The name is removed, but the real kernel object only goes away once all references are released.

On Linux, destruction is lazy, the segment will only be destroyed once the last process detaches:

The segment will only actually be destroyed after the last process detaches it (i.e., when the shm_nattch member of the associated structure shmid_ds is zero).

The same behavior is present on FreeBSD:

The removal will not take effect until all processes having attached the segment have exited.

Solaris has it similarly, but explained backwards:

If the segment is not attached to any process when IPC_RMID is invoked, it will be destroyed immediately.

dhke
  • 15,008
  • 2
  • 39
  • 56