如何连接到现有的共享内存段

3

我在使用共享内存时遇到了问题。我有一个进程可以创建并成功地写入到一个共享内存段中。但是我无法让第二个进程附加到同一个现有段。如果我使用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()时总是出现“没有这样的文件或目录”错误。但这正是我在第一个进程中使用的相同代码,在那里它能够很好地工作。在创建共享段的第一个进程中,我可以写入内存段,我可以用“ipcs -m”看到它。 同时,如果我从ipcs -m命令中获取了片段的shmid,并在我的第二个进程中硬编码它,那么第二个进程就可以顺利地附加到它上面。因此,问题似乎在于两个进程都用来识别单个共享段的公共ID的生成。
我有几个问题:
(1)是否有更简单的方法来获取现有共享内存段的shmid?对我而言,我必须从第一个进程(创建该段)传递三个不同的参数给第二个进程,以便第二个进程可以得到相同的共享段。我可以接受需要传递2个参数:像“/dev/null”这样的文件名和相同的共享id(在我的代码中为nSharedMemoryID)。但是必须传递到shmget()例程中的段大小似乎是毫无意义的,因为由于页面大小问题,我不知道实际分配了多少内存,因此我不能确定它是相同的。
(2)我在第二个进程中使用的段大小是否必须与最初在第一个进程中创建该段时使用的段大小相同?我尝试将其指定为0,但仍然出现错误。
(3)同样,权限必须相同吗?也就是说,如果共享段是以用户/组/全局的读取/写入方式创建的,那么第二个进程只能使用用户的读取权限吗?(两个进程使用相同的用户)
(4)shmget()为什么在文件“/dev/null”对于两个进程显然存在时失败并出现“没有这样的文件或目录”错误?我假设第一个进程没有对该节点进行某种锁定,因为那将是无意义的。
感谢任何人能够给予的帮助。我已经为此挣扎了数小时 - 这意味着我可能做了一些真正愚蠢的事情,并且当有人指出我的错误时最终会使自己感到尴尬。

当连接(现有)段的进程调用shmget时,它应该将大小指定为0。这样就消除了你的一个抱怨。页面大小等与此无关。这对这些目的来说应该是不相关的。权限类似于文件权限。仅因为您为用户创建了具有r/w的文件并不意味着该用户必须在每个pgm中以r/w打开它。您可以选择只读或只写方式打开它。权限给予您许可,而不是提出要求。 - Duck
感谢您的回复,Duck。在第二个进程的shmget()函数中使用0作为大小对我来说是有意义的——感谢您确认这一点,这样我就知道问题不在这里了。 - Andres Gonzalez
既然似乎涉及到 ftok,您是否使用调试器或 printf 查看了返回的 key_t?我认为它只是在 typedef 后面的一个 int。 - Duck
我不是调试器,但我已经打印出了第一个创建共享段的进程和第二个尝试连接到它的进程的密钥,它们确实是完全相同的密钥——即使在程序的多次运行中也是如此,这是有道理的。 - Andres Gonzalez
2个回答

3

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

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

(2) 关于大小,重要的是指定的大小小于等于现有段的大小,因此如果其舍入为下一个内存页面大小,则不会出现问题。仅当大于时才会收到EINVAL错误。

(3) 模式标志仅在第一次创建段时相关(基本上确定)。

(4) shmget()失败并显示“没有这样的文件或目录”仅意味着它未找到具有该的段(现在很严谨:不是id - 通常我们用id指代被shmget()返回的值,随后使用)-您是否检查了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()来告诉最后一个进程分离该段,然后再附加其他进程。

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


那么在硬编码和使用ftok()进行评估之间应该有什么区别呢? :| - Sigi
谢谢Sigismondo抽出时间来帮我解决问题。你的代码很好用,但是它创建了一个新的不同的共享段。第一个进程创建的共享段仍然只显示有1个进程连接,而新创建的共享段也只显示有1个进程连接。 - Andres Gonzalez
哇,你的程序可用。你使用“ipcs -m”验证了确实有一个大小为10000的共享段,并且有2个进程附加吗?这是我验证这两个进程是否确实附加到同一段的方法。编辑:由于您的进程返回,这可能会有问题。我的应用程序不返回,因此它们保持活动状态,因此我可以使用ipcs检查它们。 - Andres Gonzalez
让我们在聊天中继续这个讨论。链接 - Sigi
非常感谢Sigismondo的所有帮助。在Stack Overflow上的这次聊天太棒了!!! - Andres Gonzalez
显示剩余3条评论

1
我只想发布Sigismondo给我的所有帮助的结果,并在这里发布解决方案,以防其他人遇到同样的问题。
关键是使用“ipcs -m”,注意到键值为0,这意味着共享段是私有的,因此第二个进程无法附加到它上面。
另一个怪事是:我调用了以下内容:
int nReturnCode = shmctl(id, IPC_RMID, &m_stCtrlStruct);
我的意图是设置段的模式,以便当使用它的所有进程退出时将其删除。但是,这个调用的副作用是使段变为私有,即使没有使用IPC_EXCL标志创建它。
希望这可以帮助任何遇到此问题的人。
非常感谢Sigismondo花时间帮助我 - 我从我们的聊天中学到了很多!
-Andres

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接