快速调整 mmap 文件的大小
我需要对一个非常大的 mmap 文件进行无副本的重新大小,同时仍然允许对读取器线程的并发访问。
简单的方法是在同一个进程中对同一个文件使用两个 MAP_SHARED 映射(增长文件,然后创建包含增长区域的第二个映射),然后在所有可以访问该文件的读取器完成后取消旧映射的映射。但是,我很好奇下面的方案是否可行,如果可行的话,它有什么好处吗?
- 使用 MAP_PRIVATE mmap 文件
- 在多个线程中对此内存进行只读访问,
- 或者获取文件的互斥体,写入内存(假设这样做的方式是,可能正在读取该内存的读取器不会被弄乱)
- 或获取互斥体,但增加文件的大小并使用 mremap 将其移动到新地址(调整映射大小,而不复制或不必要的文件 IO。)
疯狂的部分出现在 (4)。如果移动内存,旧地址将变得无效,并且仍在读取它的读者可能会突然出现访问冲突。如果我们修改读取器以捕获此访问冲突,然后重新启动操作(即不重新读取错误地址,重新计算给定偏移量的地址和来自 mremap 的新基地址)会怎么样。)是的,我知道这是邪恶的,但在我看来,读者只能成功读取旧地址的数据,否则会因访问冲突而失败并重试。如果采取足够的措施,应该是安全的。由于重新调整大小不会经常发生,因此读者最终会成功并且不会陷入重试循环。
如果重新使用旧地址空间而读者仍然有指向它的指针,则可能会出现问题。那么就不会出现访问冲突,但数据将不正确,并且程序会进入充满独角兽和糖果的未定义行为的土地(其中通常既没有独角兽也没有糖果。)
但是如果您完全控制分配并可以确保任何在此期间发生的分配不会重新使用旧的地址空间,那么这不应该成为问题,并且行为不应该是未定义的。
我说得对吗?这行得通吗?与使用两个 MAP_SHARED 映射相比,这有什么优点吗?
I need a copy-free re-size of a very large mmap file while still allowing concurrent access to reader threads.
The simple way is to use two MAP_SHARED mappings (grow the file, then create a second mapping that includes the grown region) in the same process over the same file and then unmap the old mapping once all readers that could access it are finished. However, I am curious if the scheme below could work, and if so, is there any advantage to it.
- mmap a file with MAP_PRIVATE
- do read-only access to this memory in multiple threads
- either acquire a mutex for the file, write to the memory (assume this is done in a way that the readers, which may be reading that memory, are not messed up by it)
- or acquire the mutex, but increase the size of the file and use mremap to move it to a new address (resize the mapping without copying or unnecessary file IO.)
The crazy part comes in at (4). If you move the memory the old addresses become invalid, and the readers, which are still reading it, may suddenly have an access violation. What if we modify the readers to trap this access violation and then restart the operation (i.e. don't re-read the bad address, re-calculate the address given the offset and the new base address from mremap.) Yes I know that's evil, but to my mind the readers can only successfully read the data at the old address or fail with an access violation and retry. If sufficient care is taken, that should be safe. Since re-sizing would not happen often, the readers would eventually succeed and not get stuck in a retry loop.
A problem could occur if that old address space is re-used while a reader still has a pointer to it. Then there will be no access violation, but the data will be incorrect and the program enters the unicorn and candy filled land of undefined behavior (wherein there is usually neither unicorns nor candy.)
But if you controlled allocations completely and could make certain that any allocations that happen during this period do not ever re-use that old address space, then this shouldn't be a problem and the behavior shouldn't be undefined.
Am I right? Could this work? Is there any advantage to this over using two MAP_SHARED mappings?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我很难想象您不知道文件大小上限的情况。假设这是真的,您可以通过在首次使用 mmap() 映射文件时提供该大小来“保留”文件最大大小的地址空间。当然,任何超出文件实际大小的访问都会导致访问冲突,但这就是您希望它工作的方式 - 您可能会认为保留额外的地址空间确保访问冲突而不是而不是让该地址范围开放以供其他对 mmap() 或 malloc() 等调用使用。
不管怎样,重点是我的解决方案,你永远不会移动地址范围,你只改变它的大小,现在你的锁定是围绕为每个线程提供当前有效大小的数据结构。
如果您有如此多的文件,以至于每个文件的最大映射都会耗尽地址空间,那么我的解决方案将不起作用,但这是 64 位地址空间的时代,因此希望您的最大映射大小没有问题。
(只是为了确保我没有忘记一些愚蠢的事情,我确实编写了一个小程序来说服自己创建大于文件大小的映射,当您尝试访问超出文件大小时会出现访问冲突,然后工作正常一旦您 ftruncate() 文件变得更大,所有文件都具有从第一次 mmap() 调用返回的相同地址。)
It is hard for me to imagine a case where you don't know the upper bound on how large the file can be. Assuming that's true, you could "reserve" the address space for the maximum size of the file by providing that size when the file is first mapped in with mmap(). Of course, any accesses beyond the actual size of the file will cause an access violation, but that's how you want it to work anyway -- you could argue that reserving the extra address space ensures the access violation rather than leaving that address range open to being used by other calls to things like mmap() or malloc().
Anyway, the point is with my solution, you never move the address range, you only change its size and now your locking is around the data structure that provides the current valid size to each thread.
My solution doesn't work if you have so many files that the maximum mapping for each file runs you out of address space, but this is the age of the 64-bit address space so hopefully your maximum mapping size is no problem.
(Just to make sure I wasn't forgetting something stupid, I did write a small program to convince myself creating the larger-than-file-size mapping gives an access violation when you try to access beyond the file size, and then works fine once you ftruncate() the file to be larger, all with the same address returned from the first mmap() call.)