【问题标题】:Posix shared memory resize with mremap when several processes are using the segment当多个进程正在使用该段时,Posix 共享内存使用 mremap 调整大小
【发布时间】:2018-08-22 07:13:12
【问题描述】:

我将一些数据存储在多个进程使用的共享内存数组中。在某些时候,我想扩大阵列。

假设进程之间已经存在同步机制

最初,进程 1 将创建段,进程 2 将打开它。

过程 1
shm_open() O_CREAT
ftruncate()
mmap() MAP_SHARED

过程 2
shm_open()
mmap()

在某些时候,一个进程想要扩大数组并调整共享段的大小。

处理 1 个调用
ftruncate()
mremap() MREMAP_MAYMOVE

是否应该通知进程 2 调整大小并调用mremap() 来更新它自己的虚拟地址?

如果必须通知进程 2,我正在考虑打开第二个共享内存段,其中包含一些元数据,例如表的容量和互斥体。 每个进程最初都从共享内存中存储表的容量,并且在每个操作中都会根据共享内存元数据值检查本地值。如果值发生变化,它将调用mremap()

如果在调整大小后必须在每个进程上调用mremap(),这是一种正确的方法吗?

【问题讨论】:

  • 我相信目标进程应该按需更新自己的内存映射(当它想要访问超出其边界的东西时),但它必须跟踪当前映射的大小。但如果阵列可能缩小,此选项将失效。如果不是,这是无锁的。
  • 因为这个问题的重点是mremap(),它是 Linux 特有的,[linux] 是比 [posix] 更适用的标签(重新标记)。
  • 这个问题有进展吗?

标签: c++ c linux shared-memory mmap


【解决方案1】:

是否应该通知进程 2 调整大小并调用mremap() 来更新它自己的虚拟地址?

进程 2 已映射共享内存段的特定区域。另一个增加段大小的过程不会使该映射无效。这也不会改变在进程 2 中映射的共享内存段的区域——即使进程 2 最初映射了整个段,该段原始末尾之外的部分也不会在进程 2 中自动映射。

因此,只要进程 2 不需要访问段的其他页面,它就根本不需要更新其映射。但是,如果它想访问那个额外的共享内存,那么它确实需要更新它的映射。它可以尝试通过特定于 Linux 的mremap() 或通过munmap() 后跟mmap() 来做到这一点。后者要求它仍然具有该段的打开文件描述符。无论哪种方式,都可能无法从相同的基地址开始映射更大的空间。就此而言,可能根本无法映射更大的空间,无论是否有任何其他进程能够这样做。

关于映射的基地址

默认情况下,mremap() 将尝试修改映射区域而不更改其基地址,并且可以通过从 mmap() 请求原始基地址并传递MAP_FIXED 标志。如果无法在该地址扩展映射,这些将失败。在后一种情况下,这也会使该段完全未映射。

您可以通过在mremap() 标志中指定MREMAP_MAYMOVE 或通过避免 指定MAP_FIXEDmmap() 来允许选择新的基地址。当在同一地址扩展映射失败时,此类尝试可能会成功,但请注意,基地址的更改会使该进程指向原始映射的所有指针无效,无论存储在何处。

因此,如果您要更改基地址,那么您最好不要将任何指针存储到映射中,除了一个指向基地址的指针。在这种情况下,通过该指针或相对于它的其他方式访问内容。

如果必须通知进程 2,我正在考虑打开第二个 带有一些元数据的共享内存段,例如表的容量和 互斥体。每个进程最初存储表的容量 共享内存,并在每个操作上检查本地值与 共享内存元数据值。如果值已更改,它将调用 mremap()

如果每个人都必须调用mremap(),这是否是正确的方法? 调整大小后的处理?

如果您正确同步对元数据的访问,您的方案听起来是可行的。事实上,尽管可能有其他原因这样做,但问题中没有任何内容表明甚至有必要将元数据放在单独的共享内存段中。扩展不应影响段的原始页面的内容或它们在进程 2 中的映射,因此如果元数据存储在那里,那么进程 2 在扩展后应该仍然能够访问它们。

但是请注意,如果用于同步访问该段的互斥体、信号量或类似物在该段内,那么您需要考虑mremap() 可能会移动它,并且munmap() / @对于munmap() 成功但后续mmap() 失败的情况,987654339@ 将使您需要更复杂的恢复方案。


在理解所有这些时,记住这一点可能很有用

  • 共享内存段的大小是段的属性,由内核维护。

  • 段的每个映射的特征,包括映射的区域(特定范围的页面)和映射到的基地址,是特定进程的属性,独立于所有其他映射在相同或其他进程中细分。

  • 段的每个映射的特征也很大程度上独立于段本身。特别是,映射区域的偏移量和大小不会随着段大小的变化而变化。

【讨论】:

  • 我要澄清的是,使用mremap() 可能会更改进程内映射的基本虚拟地址,从而使进程本身可能存储在某处的指针无效。事实上,即使我们尝试将munmap()+mmap()MAP_FIXED 和原始基址一起使用,调用也可能会失败并且errno 将被设置为EINVAL。有关更多信息,请参见 mmap 的手册页。
  • 谢谢@SRG,我在“关于映射的基地址”标题下添加了一个讨论这些问题的部分。
  • 完美@JohnBollinger,这太棒了。我试图给你 +50 赏金,但显然我不能直到明天,哈哈 :)。不过我会做的,这个答案太棒了,非常感谢。
【解决方案2】:

如果必须通知进程 2,我正在考虑打开第二个共享内存段,其中包含一些元数据,例如表的容量和互斥体。每个进程最初都从共享内存中存储表的容量,并且在每个操作中都会根据共享内存元数据值检查本地值。如果值发生了变化,它将调用mremap()

除了 John Bollinger 的出色回答之外,我还要指出,有一种方法可以不使用元数据共享内存段。例如,如果您有shm_open() 提供给您的文件描述符,另一种解决方案是强制您的其他进程使用fstat 检查段的大小:

...

struct stat statbuf = { 0 };
int fd = shm_open(...);

...

// Check whether the shared segment has changed
fstat(fd, &statbuf);

if (statbuf.st_size != current_size) // current_size is stored somewhere
{
    // Remap the shared memory segment using statbuf.st_size here
}

···

您必须将共享内存段视为系统中某处的文件。出于参考目的,shm_overview 页面建议为此使用 fstat()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 2011-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多