快速调整mmap文件大小

9
我需要对一个非常大的mmap文件进行无拷贝的重新调整大小,同时仍允许读取线程并发访问。
简单的方法是在同一进程中使用两个MAP_SHARED映射(扩展文件,然后创建包括扩展区域的第二个映射),然后在所有可以访问它的读取器完成后取消映射旧映射。不过,我想知道下面的方案是否可行,如果可行,是否有任何优势。
1. 使用MAP_PRIVATE映射文件。 2. 在多个线程中对此内存进行只读访问。 3. 要么获取文件的互斥锁,写入内存(假设这是以一种不会影响可能正在读取该内存的读取器的方式完成的)。 4. 要么获取互斥锁,但增加文件的大小,并使用mremap将其移动到新地址(调整映射大小而不复制或不必要的文件IO)。
疯狂的部分出现在(4)。如果移动内存,则旧地址变为无效,而仍在读取它的读取器可能会突然遇到访问冲突。如果我们修改读取器以捕获此访问冲突,然后重新启动操作(即不重新读取错误的地址,而是重新计算给定偏移量和来自mremap的新基地址的地址),那会怎么样?是的,我知道这很邪恶,但在我看来,读取器只能在旧地址成功读取数据或遇到访问违规并重试。如果采取足够的谨慎,那应该是安全的。由于调整大小不会经常发生,因此读取器最终会成功而不会陷入重试循环。
如果在读取器仍然拥有指向它的指针时重新使用了旧地址空间,则可能会出现问题。然后将没有访问冲突,但数据将不正确,并且程序进入未定义行为的独角兽和糖果填充的领域(通常既没有独角兽也没有糖果)。
但是,如果您完全控制分配并确保在此期间发生的任何分配都不会再次使用旧地址空间,则这不应该成为问题,行为也不应该是未定义的。
我对吗?这可行吗?与使用两个MAP_SHARED映射相比,是否有任何优势?

你可以使用读写锁,在写锁的保护下重新映射。 - fge
我猜瓶颈在磁盘上。你确定值得麻烦吗?从磁盘复制大文件总是需要时间的,因为磁盘是慢速机械设备。 - Basile Starynkevitch
fge,是的,但在这种情况下锁定读取线程是不可行的。 - Eloff
@BasileStarynkevitch 不一定。文件或者至少其中的热点部分应该完全适合内存,因此重新调整映射大小不需要涉及任何文件IO,除了实际扩展文件之外(而且有方法可以缓解这个问题,例如在后台线程中提前增加文件大小,远离空间耗尽的情况)。 - Eloff
我把“非常大的文件”理解为“内存无法存放该文件”。 - Basile Starynkevitch
1个回答

6
我很难想象有什么情况下你不知道文件的上限大小。假设是这样,你可以通过在第一次使用mmap()映射文件时提供该大小来为文件的最大大小“保留”地址空间。当然,任何超出实际文件大小的访问都会导致访问冲突,但这正是您希望它起作用的方式--您可以认为保留额外的地址空间确保访问违规,而不是将该地址范围保留给其他调用mmap()或malloc()等函数的使用。
总之,我的解决方案是,您从未移动地址范围,只改变其大小,现在您的锁定围绕着为每个线程提供当前有效大小的数据结构。
如果您有很多文件,每个文件的最大映射使您耗尽地址空间,则我的解决方案无法工作,但这是64位地址空间的时代,因此您的最大映射大小希望没有问题。
(只是为了确保我没有忘记某些愚蠢的事情,我编写了一个小程序,以使自己确信创建大于文件大小的映射会在您尝试访问文件大小之外时产生访问违规,并且一旦您使用ftruncate()将文件扩大,所有这些都可以使用同一次mmap()调用返回的地址正常工作。)

1
这是我的第一个想法,但现代操作系统中地址空间仅限于8TB,这意味着如果有很多内存映射文件都保留了最大可能的空间,你可能会用完地址空间。可以在程序启动时使用带有MAP_NORESERVE选项的mmap来保留尽可能多的地址空间,然后使用MAP_FIXED从中分配,最初均匀分配,然后在用尽时从使用最少的映射中获取。如果mmap用完空间并且没有保留相邻空间,则使用上述策略或标准memcpy。 - Eloff
你从哪里得到了8TB的数字? - camelccc
4
Linux可以在128TB的地址空间内管理64TB的物理内存。不确定您是否有许多大于128TB的文件(即使我的网络共享上完整的RAID卷也仅为24TB,但您的情况可能有所不同)。你不是谷歌吧? - Damon

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