当多个进程使用该段时,Posix 共享内存使用 mremap 调整大小

Posix shared memory resize with mremap when several processes are using the segment

本文关键字:内存 共享 mremap 调整 Posix 段时 进程      更新时间:2023-10-16

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

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

最初,流程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(),这是执行此操作的正确方法吗?

进程

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

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

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

关于映射的基址

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

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

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

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

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

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

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


在理解所有这些时,记住

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

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

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

如果必须通知进程 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()