Posix共享内存初始化

6
我的问题涉及使用shm_open()mmap()初始化内存。在几个地方我看到的一个普遍建议是使用标志O_CREAT|O_EXCL调用shm_open():如果成功,那么我们是共享内存的第一个用户并且可以初始化它,否则我们不是第一个用户并且共享内存已经被另一个进程初始化。
然而,从我对shm_open的理解以及我在Linux上进行的测试中,这种方法行不通:即使最后一个共享内存对象的用户取消映射并关闭了它,共享内存对象仍会留存在系统中。一个简单的测试程序调用shm_openO_CREAT|O_EXCL,然后关闭描述符并退出,将在第一次运行时成功,但即使此时没有其他人使用共享内存,第二次运行仍将失败。
实际上,在我测试的系统上,shm_open的行为似乎与open()基本相同:如果我修改我的简单测试程序通过mmap获取指针将某些内容写入共享内存,然后退出,那么共享内存对象将持续保留其内容(我可以运行另一个简单程序来读取我之前写入的数据)。
所以,关于使用带有O_CREAT|O_EXCL标志的shm_open的建议是否错误,还是我遗漏了什么?
我知道可以使用shm_unlink()删除共享内存对象,但这似乎只会导致更多问题:
1.如果一个进程在调用shm_unlink()之前死亡,则我们又回到了上面描述的问题。
2.如果一个进程调用shm_unlink(),而其他一些进程仍然映射到相同的共享内存中,则这些其他进程仍将像往常一样继续使用它。现在,如果另一个进程来并使用与旧共享内存对象相同的名称和指定了O_CREAT,它实际上将成功创建具有相同名称的新共享内存对象,该对象与其他进程仍在使用的旧共享内存对象完全无关。现在我们有一个进程试图通过共享内存与其他进程通信,并且完全不知道它正在使用错误的通道。
我习惯于Windows语义,其中共享内存对象仅存在于至少有一个句柄打开的情况下,因此这个Posix东西非常令人困惑。

1
看起来你在完成后没有设置shm段进行删除。因此,它会在你的进程中“存活”。你可以在终端中使用ipcs命令查看当前在系统中存在哪些IPC内容(信号量、shm段等)。 - Rerito
1
但是我该如何设置它以进行删除?有没有一些标志要传递给shm_open()函数?我在手册页上找不到任何信息。 - Yevgeniy P
@Rerito:你对楼主对你的评论有什么想法吗? - alk
在共享内存的实际使用情况中,可能共享同一段的两个进程将同时处于“活动”状态。因此,在需要时调用shm_unlink()是没有理由不这样做的。 - Rerito
由于我在回答中做出了一些假设,您能否提供更多关于您使用共享内存的原因的信息? - Rerito
1
如果您只与子进程共享内存,则可以使用shm_open()获取描述符,并在此之后立即使用shm_unlink()将其删除。然后,您可以通过fork()共享描述符。 - GreenScape
3个回答

2

由于您使用了O_EXCL标志,我会假设您有一组进程聚集在一个主进程周围(即段的创建者)。

然后,您的主进程将使用调用shm_open来创建共享内存段:

shmid = shm_open("/insert/name/here", O_CREAT|O_EXCL, 0644);
if (-1 == shmid) {
    printf("Oops ..\n");
}

在这里,从属进程已准备好使用该段。由于主进程必须创建该段,因此从属进程调用时无需使用O_CREAT标志。你只需要在从属进程调用时处理可能的错误,如果该段尚未创建或已被销毁。

当任何一个进程完成对该段的操作后,应调用shm_unlink()。在这种架构中,通常是主进程提供数据给从属进程。当主进程没有更多内容可提供时,它就会停止提供。接下来,从属进程有责任优雅地处理相应的错误。

正如你所说,如果进程在调用shm_unlink之前死亡,则该段将继续存在。为了避免这种情况,在某些情况下,你可以定义自己的信号处理程序,以便在接收到诸如SIGINT之类的信号时执行该操作。无论如何,你都无法解决在向进程发送SIGKILL时出现的问题。

编辑: 更具体地说,当不需要时,使用O_CREAT | O_EXCL是错误的。通过上面的小例子,你可以看到主进程必须创建该段,因此需要这些标志。另一方面,从属进程中没有一个必须创建它。因此,你绝对禁止在相关调用中使用O_CREAT

现在,如果另一个进程在该段已经被创建时调用shm_open(..., O_CREAT, ...),它只会检索与该段相关的文件描述符。因此,如果它有权这样做(请参见mode参数),它将在正确的通道上。


谢谢回复。然而,在我的情况下,该进程不知道它是“主”还是“从”。整个问题在于找出它是哪一个。基本上,一旦该进程映射到共享内存,它需要回答问题“我是第一个吗?”如果是,则调用初始化例程。因为进程可能会被杀死(如果使用SIGKILL,则无法拦截它),我们不能真正假设文件系统中的共享内存段意味着任何事情。 - Yevgeniy P
我有一个解决方案,使用文件锁来确保正确的共享内存初始化(实际上使用了2个文件锁来避免某些竞争条件)。然而,仍然存在一些疑虑,与异常进程终止有关。我在这里发布了相关问题:https://dev59.com/c3jZa4cB1Zd3GeqPfY0x - Yevgeniy P
@YevgeniyP 通常,信号量与共享内存一起使用以确保正确使用。但是,如果其中一些进程被SIGKILL杀死,您无法防止进程集的良好行为。无论如何,我现在找不到任何解决方案... - Rerito
是的,我找不到在所有情况下都能正确运行的解决方案。就这一点而言,Windows 更好——它保证对于共享内存段的第一个用户,该段将被初始化为零(当然,除非它由实际文件支持)。因此,在 Windows 中,我只需在初始化完成后编写一些标志,然后其他进程就可以检查它(例如,第一个字节是否为 0)。 POSIX 似乎没有这个功能,特别是如果进程被终止后该段被留在文件系统中。 - Yevgeniy P

0
你可以这样做: int test = shmget(key_t key,size,0); 将此代码放在每个进程的开头。在这里,零标志尝试打开已有的共享内存,如果它还没有被创建,test将等于-1,因此您可以在此语句之后进行检查,如果test为-1,则创建一个共享内存,否则您只是得到了一个现有共享内存的ID......希望这能帮到你。

0
在某些情况下,您可能希望使用O_CREAT | O_TRUNC来清除数据?或者通过显式赋值给结构成员来重置内存映射数据?
另外,您可以选择通过在共享内存中跟踪pid(或者只是一个临时文件)来控制"master",然后验证该pid是否仍然有效。

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