C++ – How to attach to an existing shared memory segment

How to attach to an existing shared memory segment… here is a solution to the problem.

How to attach to an existing shared memory segment

I’m having trouble with shared memory. I have a process that creates and writes shared memory segments well. But I can’t get a second process to attach the same existing segment. If I use the IPC_CREATE flag, my second process can create a new shared segment, but I need to attach to the existing shared segment created by the first process.

Here is my code in the second procedure:

int nSharedMemoryID = 10;
key_t tKey = ftok("/dev/null", nSharedMemoryID);
if (tKey == -1)  {
    std::cerr << "ERROR: ftok(id: " << nSharedMemoryID << ") failed, " << strerror(errno) << std::endl;
    exit(3);
}
std::cout << "ftok() successful " << std::endl;

size_t nSharedMemorySize = 10000;
int id = shmget(tKey, nSharedMemorySize, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (id == -1)  {
    std::cerr << "ERROR: shmget() failed, " << strerror(errno) << std::endl << std::endl;
    exit(4);
}
std::cout << "shmget() successful, id: " << id << std::endl;

unsigned char *pBaseSM = (unsigned char *)shmat(id, (const void *)NULL, SHM_RDONLY);
if (pBaseSM == (unsigned char *)-1)  {
    std::cerr << "ERROR: shmat() failed, " << strerror(errno) << std::endl << std::endl;
    exit(5);
}
std::cout << "shmat() successful " << std::endl;

The problem is that the second process always gets an error when calling shmget() with a “no such file or directory” error. But here’s the exact same code I used in the first procedure and worked just fine there. In the first process where the shared segment is created, I can write to the memory segment, I can view it with “ipcs -m” Also, if I take shmid from the “ipcs -m” command of the segment and hardcode it in my second process, the second process attaches to it nicely. So the problem seems to be generating the public IDs that both processes use to identify a single shared segment.

I have a few questions :

(1) Is there an easier way to get the shmid of an existing shared memory segment? It seems crazy to me that I have to pass three separate arguments from the first process (creating segments) to the second process so that the second process gets the same shared segment. I can live with having to pass 2 arguments: a filename like “/dev/null” and the same shared ID (nSharedMemoryID in my code). But having to pass to the shmget() routine to get the size of the segment of shmid seems pointless, since I don’t know how much memory is actually allocated (due to page size issues) so I can’t of course be the same.
(2) Does the segment size I use in the second procedure have to be the same as the segment size I used to create the segment in the first procedure? I tried to specify it as 0 and I still get the error.
(3) Similarly, do the permissions have to be the same? That is, if the shared segment is a read/write created for a user/group/world, can the second process use read only for the user? (Same user for both processes).
(4) Why does shmget() fail with a “no such file or directory” error when the file “/dev/null” is clearly present in both processes? I’m assuming the first process won’t put some kind of lock on that node because that’s pointless.

Thank you for anyone who can help. I’ve been struggling with this for hours – which means I’m probably doing something really stupid and I end up embarrassing myself when someone points out my mistake 🙂

Thank you
– Andres

Solution

(1) As a different way: the additional process scans the user’s existing segment, tries to append the desired size, and checks the “magic byte sequence” at the beginning of the segment (to exclude other programs from the same user). Alternatively, you can check if the attached process is the one you expect. If one of the steps fails, which is the first step, the segment will be created… It’s troublesome, yes, I saw it in code from the 70s.

Eventually you can evaluate using a POSIX-compatible shm_open() alternative – it should be simpler or at least more modern….

(2) Regarding size, it is important that the specified size is less than/equal to the size of the existing segment, so there is no problem if it is rounded to the next memory page size. The EINVAL error occurs only if it is larger.

(3) The pattern flag is only relevant when you first create a segment (most of the time positive).

(4) The fact that shmget()

fails because “there is no such file or directory” only means that it didn’t find a segment with that key (now it’s pedantic: not id – we usually refer to the id return value via shmget() and use it later) – have you checked that tKey is the same? Your code works fine on my system. Just added a main() around it.

Edit: Attach the working procedure

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char **argv) {

int nSharedMemoryID = 10;
  if (argc > 1) {
    nSharedMemoryID = atoi(argv[1]);
  }

key_t tKey = ftok("/dev/null", nSharedMemoryID);
  if (tKey == -1)  {
    std::cerr << "ERROR: ftok(id: " << nSharedMemoryID << ") failed, " << strerror(errno) << std::endl;
    exit(3);
  }
  std::cout << "ftok() successful. key = " << tKey << std::endl;

size_t nSharedMemorySize = 10000;
  int id = shmget(tKey, nSharedMemorySize, 0);
  if (id == -1)  {
    std::cerr << "ERROR: shmget() failed (WILL TRY TO CREATE IT NEW), " << strerror(errno) << std::endl << std::endl;
    id = shmget(tKey, nSharedMemorySize, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | IPC_CREAT);
    if (id == -1)  {
      std::cerr << "ERROR: shmget() failed, " << strerror(errno) << std::endl << std::endl;
      exit(4);
    }
  }
  std::cout << "shmget() successful, id: " << id << std::endl;

unsigned char *pBaseSM = (unsigned char *)shmat(id, (const void *)NULL, SHM_RDONLY);
if (pBaseSM == (unsigned char *)-1)  {
    std::cerr << "ERROR: shmat() failed, " << strerror(errno) << std::endl << std::endl;
    exit(5);
}
std::cout << "shmat() successful " << std::endl;
}

Edit: Output

$ ./a.out 33
ftok() successful. key = 553976853
ERROR: shmget() failed (WILL TRY TO CREATE IT NEW), No such file or directory

shmget() successful, id: 20381699
shmat() successful 
$ ./a.out 33
ftok() successful. key = 553976853
shmget() successful, id: 20381699
shmat() successful 

Solution – after the chat (wow, SO has a chat!) Discuss:

The final problem is that in the original code, he later called shmctl() to tell the segment to be detached when the last process detached it before attaching the other processes.

The problem is that this effectively makes the segment private. Its key is marked as 0x00000000 by ipcs -m and can no longer be appended by other processes – it is effectively marked for delayed deletion.

Related Problems and Solutions