【问题标题】:how to attach to an existing shared memory segment如何附加到现有的共享内存段
【发布时间】:2014-04-11 07:19:16
【问题描述】:

我在共享内存方面遇到问题。我有一个进程可以很好地创建和写入共享内存段。但是我无法获得第二个过程来附加相同的现有段。如果我使用 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)。但是为了获得 shmid 而必须传递给 shmget() 例程的段的大小似乎毫无意义,因为我不知道实际分配了多少内存(由于页面大小问题)所以我不能肯定是一样的。 (2) 我在第二个进程中使用的段大小是否必须与最初在第一个进程中创建段的段的大小相同?我试图将其指定为 0,但仍然出现错误。 (3) 同样,权限是否必须相同?也就是说,如果共享段是用用户/组/世界的读/写创建的,第二个进程可以只为用户使用读吗? (两个进程的相同用户)。 (4) 当两个进程都明显存在文件“/dev/null”时,为什么 shmget() 会失败并出现“没有这样的文件或目录”错误?我假设第一个进程没有在该节点上设置某种锁,因为那是没有意义的。

感谢任何人提供的任何帮助。我已经为此苦苦挣扎了好几个小时——这意味着我可能正在做一些非常愚蠢的事情,当有人指出我的错误时,我最终会让自己感到尴尬:-)

谢谢, -安德烈斯

【问题讨论】:

  • 当附加(现有)段的进程调用shmget时,它应该将大小指定为0。这样就消除了您的抱怨之一。页面大小等,不包括在内。这应该与这些目的无关。权限类似于文件权限。仅仅因为您为用户创建了一个带有 r/w 的文件并不意味着用户必须在每个 pgm 中以 r/w 的形式打开它。您可以选择以只读或只写方式打开它。权限是给你,嗯,权限,而不是提出要求。
  • 感谢您的回复鸭子。在第二个进程 shmget() 中使用 0 作为大小对我来说是有意义的——感谢您确认,所以我知道这不是问题。
  • 由于这似乎围绕ftok 展开,您是否查看过在调试器或 printf 中返回的 key_t?我认为它只是 typedef 后面的一个 int。
  • 不是调试器,但我已经打印出创建共享段的第一个进程和尝试附加到它的第二个进程的密钥,它们确实是完全相同的密钥--即使在多次运行的程序中,它们都是一样的,这是有道理的。

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


【解决方案1】:

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

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

另一个怪癖是:我正在调用以下内容:

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

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

希望这对遇到此问题的其他人有所帮助。

而且,非常感谢 Sigismondo 抽出时间来帮助我——我从我们的聊天中学到了很多东西!

-安德烈斯

【讨论】:

    【解决方案2】:

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

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

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

    (3) 模式标志仅在您第一次创建段时才相关(大部分情况下是肯定的)。

    (4) shmget() 因“没有这样的文件或目录”而失败的事实仅意味着它没有找到具有该 key 的段(现在是迂腐的:不是 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 
    

    解决方案 - 聊天后(哇,有聊天了!)讨论:

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

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

    【讨论】:

    • 感谢您的回复 Sigismondo。我已经验证了第一个和第二个进程的 tKey 值确实是相同的。这就是为什么这对我来说很奇怪。感谢您还确认该模式仅与创建过程相关 - 这是有道理的。也为了澄清这个错误与 ftok() 调用中使用的文件无关,这似乎也是合理的。我在第二个过程中对 shemget() 的调用仍然返回 -1,所以我仍然感到困惑。
    • 那么硬编码和让它用 ftok() 进行评估应该有什么区别? :|
    • 感谢 Sigismondo 抽出时间来解决我的问题。您的代码运行良好,但它创建了一个新的和不同的共享段。使用第一个进程创建的共享段仍然显示仅附加了 1 个进程,并且创建的新共享段也显示了仅附加了 1 个进程。
    • 没问题...新的和不同的?如果密钥相同,怎么可能是“可能的” - 请参阅新编辑中的附加输出
    • 哇,你的作品。您是否使用“ipcs -m”验证确实存在一个大小为 10000 且附加了 2 个进程的共享段?这就是我要验证这两个进程确实附加到同一段的方法。编辑:这可能对您的流程有问题,因为它们会返回。我的应用程序没有返回,因此它们保持活动状态,因此我可以使用 ipcs 检查它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-01
    • 2017-03-22
    • 2012-10-26
    • 2016-05-08
    • 1970-01-01
    • 2014-03-17
    • 1970-01-01
    相关资源
    最近更新 更多