Posix 当多个进程正在使用该段时,使用 mremap 调整共享内存大小
Posix shared memory resize with mremap when several processes are using the segment
我正在将一些数据存储在多个进程使用的共享内存阵列中。在某些时候我想扩大阵列。
假设进程之间已经存在同步机制
最初进程 1 将创建段,进程 2 将打开它。
进程 1
shm_open() O_CREAT
ftruncate()
mmap() MAP_SHARED
进程 2
shm_open()
mmap()
在某一时刻,一个进程想要增大数组并调整共享段的大小。
处理 1 调用
ftruncate()
mremap() MREMAP_MAYMOVE
是否应该通知进程 2 调整大小并调用 mremap()
来更新它自己的虚拟地址?
如果必须通知进程 2,我正在考虑使用一些元数据打开第二个共享内存段,例如 table 的容量和互斥体。
每个进程最初从共享内存存储 table 的容量,并且在每个操作中根据共享内存元数据值检查本地值。如果值已更改,它将调用 mremap()
如果在调整大小后必须在每个进程上调用 mremap()
,这是执行此操作的正确方法吗?
Shall Process 2 be notified of the resize and call mremap()
to update it’s own virtual address too ?
进程 2 映射了共享内存段的特定区域。另一个进程 增加 段的大小不会使该映射无效。这也不会改变进程 2 中映射的共享内存段的区域——即使进程 2 最初映射了整个段,超出该段原始末尾的部分也不会自动映射到进程 2 中。
因此,只要进程 2 不需要访问该段的其他页面,它就根本不需要更新其映射。然而,如果它想要访问那个额外的共享内存,那么它确实需要更新它的映射。它可以尝试通过 Linux-specific mremap()
或 munmap()
后跟 mmap()
来实现。后者要求它仍然有该段的打开文件描述符。无论哪种方式,都可能无法从相同的基地址开始映射更大的 space。就此而言,可能根本无法映射更大的 space,无论是否有任何其他进程能够这样做。
关于映射的基地址
默认情况下,mremap()
将尝试修改映射区域而不更改其基址,并且可以通过请求原始基址来要求 munmap()
+ mmap()
执行相同的操作来自 mmap()
的地址并传递 MAP_FIXED
标志。如果无法在该地址扩展映射,这些将失败。在后一种情况下,这也会使该段完全未映射。
您可以通过在 mremap()
标志中指定 MREMAP_MAYMOVE
或通过 避免 指定 [=18] 来允许选择新的基地址=] 到 mmap()
。当在同一地址扩展映射失败时,此类尝试可能会成功,但 请注意 基地址的更改会使该进程指向原始映射,无论存储在何处。
因此,如果您要提供基地址的更改,那么您最好不要将任何指针存储到映射中,除了一个指向基地址的指针。在这种情况下,通过该指针或相对于它的其他方式访问内容。
If Process 2 has to be notified I’m thinking of opening a second
shared memory segment with some metadata e.g the table’s capacity and
a mutex. Each process stores the table’s capacity initially from
shared memory and on each operation checks the local value against the
shared memory metadata value. If the value has changed it will call
mremap()
Is this a proper way to do this if mremap()
has to be called on each
process after resizing?
只要您正确同步对元数据的访问,您的方案听起来是可行的。事实上,尽管这样做可能有其他原因,但问题中没有任何内容表明甚至有必要将元数据放在单独的共享内存段中。扩展不应影响段原始页面的内容或其在进程 2 中的映射,因此如果元数据存储在那里,则进程 2 在扩展后仍应能够访问它们。
但是请注意,如果用于同步访问该段的互斥锁、信号量或类似物位于该段内,那么您需要考虑 mremap()
可能移动它的事实,并且 munmap()
/ mmap()
将使您需要更复杂的恢复方案,以应对 munmap()
成功但随后的 mmap()
失败的情况。
在理解所有这些内容时,记住以下内容可能会有用
共享内存段的大小是一个属性的段,由内核维护
段的每个映射的特性,包括映射的区域(特定范围的页)和映射到的基地址,是特定进程的属性,独立于所有该段在同一进程或其他进程中的其他映射。
段的每个映射的特征也很大程度上独立于段本身。特别是,映射区域的偏移量和大小不会随着段大小的变化而变化。
If Process 2 has to be notified I’m thinking of opening a second shared memory segment with some metadata e.g the table’s capacity and a mutex. Each process stores the table’s capacity initially from shared memory and on each operation checks the local value against the shared memory metadata value. If the value has changed it will call 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()
。
我正在将一些数据存储在多个进程使用的共享内存阵列中。在某些时候我想扩大阵列。
假设进程之间已经存在同步机制
最初进程 1 将创建段,进程 2 将打开它。
进程 1
shm_open() O_CREAT
ftruncate()
mmap() MAP_SHARED
进程 2
shm_open()
mmap()
在某一时刻,一个进程想要增大数组并调整共享段的大小。
处理 1 调用
ftruncate()
mremap() MREMAP_MAYMOVE
是否应该通知进程 2 调整大小并调用 mremap()
来更新它自己的虚拟地址?
如果必须通知进程 2,我正在考虑使用一些元数据打开第二个共享内存段,例如 table 的容量和互斥体。
每个进程最初从共享内存存储 table 的容量,并且在每个操作中根据共享内存元数据值检查本地值。如果值已更改,它将调用 mremap()
如果在调整大小后必须在每个进程上调用 mremap()
,这是执行此操作的正确方法吗?
Shall Process 2 be notified of the resize and call
mremap()
to update it’s own virtual address too ?
进程 2 映射了共享内存段的特定区域。另一个进程 增加 段的大小不会使该映射无效。这也不会改变进程 2 中映射的共享内存段的区域——即使进程 2 最初映射了整个段,超出该段原始末尾的部分也不会自动映射到进程 2 中。
因此,只要进程 2 不需要访问该段的其他页面,它就根本不需要更新其映射。然而,如果它想要访问那个额外的共享内存,那么它确实需要更新它的映射。它可以尝试通过 Linux-specific mremap()
或 munmap()
后跟 mmap()
来实现。后者要求它仍然有该段的打开文件描述符。无论哪种方式,都可能无法从相同的基地址开始映射更大的 space。就此而言,可能根本无法映射更大的 space,无论是否有任何其他进程能够这样做。
关于映射的基地址
默认情况下,mremap()
将尝试修改映射区域而不更改其基址,并且可以通过请求原始基址来要求 munmap()
+ mmap()
执行相同的操作来自 mmap()
的地址并传递 MAP_FIXED
标志。如果无法在该地址扩展映射,这些将失败。在后一种情况下,这也会使该段完全未映射。
您可以通过在 mremap()
标志中指定 MREMAP_MAYMOVE
或通过 避免 指定 [=18] 来允许选择新的基地址=] 到 mmap()
。当在同一地址扩展映射失败时,此类尝试可能会成功,但 请注意 基地址的更改会使该进程指向原始映射,无论存储在何处。
因此,如果您要提供基地址的更改,那么您最好不要将任何指针存储到映射中,除了一个指向基地址的指针。在这种情况下,通过该指针或相对于它的其他方式访问内容。
If Process 2 has to be notified I’m thinking of opening a second shared memory segment with some metadata e.g the table’s capacity and a mutex. Each process stores the table’s capacity initially from shared memory and on each operation checks the local value against the shared memory metadata value. If the value has changed it will call
mremap()
Is this a proper way to do this if
mremap()
has to be called on each process after resizing?
只要您正确同步对元数据的访问,您的方案听起来是可行的。事实上,尽管这样做可能有其他原因,但问题中没有任何内容表明甚至有必要将元数据放在单独的共享内存段中。扩展不应影响段原始页面的内容或其在进程 2 中的映射,因此如果元数据存储在那里,则进程 2 在扩展后仍应能够访问它们。
但是请注意,如果用于同步访问该段的互斥锁、信号量或类似物位于该段内,那么您需要考虑 mremap()
可能移动它的事实,并且 munmap()
/ mmap()
将使您需要更复杂的恢复方案,以应对 munmap()
成功但随后的 mmap()
失败的情况。
在理解所有这些内容时,记住以下内容可能会有用
共享内存段的大小是一个属性的段,由内核维护
段的每个映射的特性,包括映射的区域(特定范围的页)和映射到的基地址,是特定进程的属性,独立于所有该段在同一进程或其他进程中的其他映射。
段的每个映射的特征也很大程度上独立于段本身。特别是,映射区域的偏移量和大小不会随着段大小的变化而变化。
If Process 2 has to be notified I’m thinking of opening a second shared memory segment with some metadata e.g the table’s capacity and a mutex. Each process stores the table’s capacity initially from shared memory and on each operation checks the local value against the shared memory metadata value. If the value has changed it will call
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()
。