如何附加到现有共享内存段

时间:2014-03-07 22:36:13

标签: c++ linux posix ipc shared-memory

我遇到共享内存问题。我有一个创建和写入共享内存段的进程就好了。但我无法获得第二个进程来附加相同的现有段。如果我使用IPC_CREATE标志,我的第二个进程可以创建一个新的共享段,但我需要附加到第一个进程创建的现有共享段。

这是我在第二个过程中的代码:

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;

问题是第二个进程总是在调用shmget()时出错,并且出现“No such file or directory”错误。但这是我在第一个过程中使用的完全相同的代码,它在那里工作得很好。在创建共享段的第一个进程中,我可以写入内存段,我可以用“ipcs -m”看到它。另外,如果我从段的“ipcs -m”命令获取shmid并硬编码在我的第二个过程和第二个过程可以附加到它就好了。因此,问题似乎是生成两个进程用于标识单个共享段的公共ID。

我有几个问题:

(1)是否有更简单的方法来获取现有共享内存段的shmid?我似乎很疯狂,我必须将三个单独的参数从第一个进程(创建该段)传递到第二个进程,这样第二个进程才能获得相同的共享段。我可以忍受必须传递2个参数:文件名如“/ dev / null”和相同的共享ID(我的代码中的nSharedMemoryID)。但是为了获得shmid而必须传递给shmget()例程的段的大小似乎毫无意义,因为我不知道实际分配了多少内存(因为页面大小问题)所以我不能确定它是一样的。 (2)我在第二个过程中使用的分段大小是否必须与第一个过程中用于初始创建分段的分段大小相同?我试图将其指定为0,但我仍然遇到错误。 (3)同样,权限是否必须相同?也就是说,如果共享段是使用用户/组/世界的读/写创建的,那么第二个进程是否只能对用户使用读取? (两个进程的用户相同)。 (4)为什么当两个进程明显存在文件“/ dev / null”时,shmget()会因“没有这样的文件或目录”错误而失败?我假设第一个进程没有对该节点进行某种锁定,因为这将是毫无意义的。

感谢任何人都能给予的帮助。我一直在努力奋斗这几个小时 - 这意味着我可能正在做一些非常愚蠢的事情,并且当有人指出我的错误时最终会让自己难堪: - )

感谢, -Andres

2 个答案:

答案 0 :(得分:3)

(1)作为一种不同的方式:附加过程扫描用户的现有段,尝试附加所需的大小,检查段开头的“魔术字节序列”(排除其他程序)同一个用户)。或者,您可以检查附加的过程是否是您期望的过程。如果其中一个步骤失败,这是第一个,并将创建该段......繁琐的是,我在70年代的代码中看到它。

最终您可以评估使用符合POSIX标准的shm_open()替代方案 - 应该更简单或至少更现代......

(2)关于大小,重要的是指定的大小小于/等于现有段的大小,因此如果它舍入到下一个内存页大小则没有问题。只有在EINVAL变大时才会出现EINVAL错误。

(3)模式标志仅在您第一次创建分段时(大多数是确定的)相关。

(4)shmget()因“没有这样的文件或目录”而失败的事实意味着它没有找到具有该键的片段(现在是迂腐的:不是 id - 使用id我们通常会引用shmget()后面的值returnsnet,随后使用) - 你检查过tKey是否相同?您的代码在我的系统上正常运行。刚刚添加了一个main()。

编辑:附上工作程序

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

编辑:输出

$ ./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 

解决方案 - 在聊天之后(哇SO聊天!)讨论:

最后问题是,在原始代码中,他在稍后调用shmctl()告诉将该段分离为最后一个进程分离它,然后再附加另一个进程。

问题在于,这实际上使得该段是私有的。它的密钥被ipcs -m标记为0x00000000,并且不再被其他进程附加 - 它实际上标记为延迟删除。

答案 1 :(得分:1)

我只想发布Sigismondo给我的所有帮助的结果,并将解决方案发布到此问题,以防万一其他人遇到同样的问题。

线索是使用“ipcs -m”并注意到键值为0,这意味着共享段是私有的,因此第二个进程无法附加到它。

这是一个额外的怪癖:我打电话给以下人员:

int nReturnCode = shmctl(id,IPC_RMID,&amp; m_stCtrlStruct);

我的目的是为段设置模式,以便在使用它的所有进程都退出时删除它。但是,此调用具有使段成为私有的副作用,即使它是在不使用IPC_EXCL标志的情况下创建的。

希望这可以帮助其他人解决这个问题。

而且,非常感谢Sigismondo花时间帮助我 - 我从聊天中学到了很多东西!

-Andres